如何生成 XML 使用 Builder 循环遍历 XML 中的结构

How to generate XML using Builder to loop over structures inside XML

原版post乱七八糟,希望这次编辑更清楚!

我希望从嵌套在数组中的散列生成 XML。我曾尝试使用 Nokogiri 生成器,但不太正确。多亏了铁皮人的帮助,我现在离它更近了一点,但我的例子还远未清晰到无法破译。我还遗漏了一些关键信息。

这是我需要生成的 XML:

  <?xml version="1.0" encoding="UTF-8"?>
    <Report Tool="FirewallParserv1">
      <Firewalls>
        <Firewall>
          <issues>
            <issue id="1" Category="2">
              <Data mode="table">
                <Row>
                  <column>ACL</columnumn>
                  <column>Rule</column>
                  <column>Source</column>
                  <column>Dest</column>
                  <column>Service</column>
                  <column>Log</column>
                </Row>
                <Row>
                  <column>inside_access_in</column>
                  <column>1</column>
                  <column>10.10.10.1</column>
                  <column>192.168.1.2</column>
                  <column>SMTP</column>
                  <column>YES</column>
                </Row>
                <Row>
                  <column>inside_access_in</column>
                  <column>2</column>
                  <column>172.16.2.1</column>
                  <column>192.168.100.10</column>
                  <column>HTTP</column>
                  <column>NO</column>
                </Row>
                <Row>
                  <column>inside_access_in</column>
                  <column>3</column>
                  <column>172.16.2.200</column>
                  <column>10.10.60.1</column>
                  <column>TELNET</column>
                  <column>NO</column>
                </Row>
              </Data>
            </issue>
          </issues>
        </Firewall>
      </Firewalls>
     <Firewalls>
        <Firewall>
          <issues>
            <issue id="2" Category="2">
              <Data mode="table">
                <Row>
                  <column>ACL</columnumn>
                  <column>Rule</column>
                  <column>Source</column>
                  <column>Dest</column>
                  <column>Service</column>
                  <column>Log</column>
                </Row>
                <Row>
                  <column>outside_access_in</column>
                  <column>8</column>
                  <column>195.92.195.92</column>
                  <column>192.168.1.2</column>
                  <column>SYSLOG</column>
                  <column>YES</column>
                </Row>
                <Row>
                  <column>outside_access_in</column>
                  <column>9</column>
                  <column>8.8.8.8</column>
                  <column>192.168.100.10</column>
                  <column>SSH</column>
                  <column>NO</column>
                </Row>
                <Row>
                  <column>outside_access_in</column>
                  <column>10</column>
                  <column>172.16.3.200</column>
                  <column>10.10.90.1</column>
                  <column>PROXY</column>
                  <column>NO</column>
                </Row>
              </Data>
            </issue>
          </issues>
        </Firewall>
      </Firewalls>
    </Report>

到目前为止,我已经接近使用以下代码,但是嵌套循环意味着我最终得到了太多 iterations/repetition 每个 'issue',准确地说是三个。

rule_array1 = [
  {:id => '1', :aclname => 'inside_access_in', :Rule => '1', :Source => '10.10.10.1',   :Destination => '192.168.1.2',    :port => 'SMTP',   :Log => 'YES'},
  {:id => '1', :aclname => 'inside_access_in', :Rule => '2', :Source => '172.16.2.1',   :Destination => '192.168.100.10', :port => 'HTTP',   :Log => 'NO'},
  {:id => '1', :aclname => 'inside_access_in', :Rule => '3', :Source => '172.16.2.200', :Destination => '10.10.60.1',     :port => 'TELNET', :Log => 'NO'}
]

rule_array2 = [
  {:id => '2', :aclname => 'outside_access_in', :Rule => '8', :Source => '195.92.195.92',   :Destination => '192.168.1.2',    :port => 'SYSLOG',   :Log => 'YES'},
  {:id => '2', :aclname => 'outside_access_in', :Rule => '9', :Source => '8.8.8.8',   :Destination => '192.168.100.10', :port => 'SSH',   :Log => 'NO'},
  {:id => '2', :aclname => 'outside_access_in', :Rule => '10', :Source => '172.16.3.200', :Destination => '10.10.90.1',     :port => 'PROXY', :Log => 'NO'}
]

array_of_arrays = rule_array1, rule_array2

builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
  xml.Report('Tool' => 'FirewallParserv1') {
    array_of_arrays.each do |outer|
      outer.each do |rule|
          xml.Firewalls {
            xml.Firewall {
              xml.issues {
                xml.issue('id' => rule[:id], 'Category' => '2') {
                  xml.Data('mode' => "table") {
                    xml.Row {
                      xml.column("ACL")
                      xml.column("Rule")
                      xml.column("Source")
                      xml.column("Dest")
                      xml.column("Service")
                      xml.column("Log")
                    }
                    outer.each do |rule|
                      xml.Row {
                        xml.column(rule[:aclname])
                        xml.column(rule[:Rule])
                        xml.column(rule[:Source])
                        xml.column(rule[:Destination])
                        xml.column(rule[:port])
                        xml.column(rule[:Log])
                      }
                    end
                  }
                }
              }
            }     
          }
      end    
    end
  }
end

puts builder.to_xml

如何在循环中循环并且只 return 正确的 'issues' 数量?应该有两个,每个都有三个规则,每个规则都取自 XML 示例中的嵌套哈希数组之一。

我希望这个问题现在更清楚了,对于原来的混乱表示歉意。

更新:所以我基本上把我想要的拼凑在一起:

issue_id = 1
builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
    xml.Report('Tool' => 'FirewallParserv1') {
      xml.Firewalls {
        xml.Firewall {
          array_of_arrays.each do |outer|
            xml.issues {
              xml.issue('id' => issue_id, 'Category' => '2') {
                xml.Data('mode' => "table") {
                  xml.Row {
                    xml.column("ACL")
                    xml.column("Rule")
                    xml.column("Source")
                    xml.column("Dest")
                    xml.column("Service")
                    xml.column("Log")
                  }
                  outer.each do |rule|
                    xml.Row {     
                      xml.column(rule[:aclname])
                      xml.column(rule[:Rule])
                      xml.column(rule[:Source])
                      xml.column(rule[:Destination])
                      xml.column(rule[:port])
                      xml.column(rule[:Log])
                    }
                  end
                }
              }
            }
            issue_id +=1
          end    
        } 
      }
    }
end


puts builder.to_xml

不过,为了到达那里,我不得不放弃从散列访问 :id 值,因为在启动 outer.each do |rule| 位之前我不会访问该散列。作为临时避难所,我只是为 issue_id 分配一个值并在每个循环中递增它。有没有办法在我遍历哈希之前获取 :id 值?还是我的逻辑有问题?

编辑 33: 似乎按以下方式分配 ID 值有效:

xml.issue('id' => outer[0][:id], 'Category' => '2') {

规则一:正确保持缩进。有许多很棒的代码编辑器可以帮助您,在您编写代码时 indenting/outdenting,或者让您 运行 格式化程序。通过这样做,您可以更容易地看到循环和块中的问题。

您想要的代码示例是:

builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
  rule_array.each do |rule|
  xml.Report('Tool' => 'FirewallParserv1') {
    xml.Firewalls {
      xml.Firewall {
        xml.issues {
          xml.issue('id' => rule[:id], 'Category' => '2') {
            end
            xml.Data('mode' => "table") {
              xml.Row {
                xml.columnumn("ACL")
                xml.column("Rule")
                xml.column("Source")
                xml.column("Dest")
                xml.column("Service")
                xml.column("Log")
              }
              rule_array.each do |rule|
              xml.Row {
                xml.column(rule[:aclname])
                xml.column(rule[:Rule])
                xml.column(rule[:Source])
                xml.column(rule[:Destination])
                xml.column(rule[:port])
                xml.column(rule[:Log])
              }
          end
            }
          }
        }
      }
    }
  }
end

让 vim 重新缩进后,我得到:

builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
  rule_array.each do |rule|
    xml.Report('Tool' => 'FirewallParserv1') {
      xml.Firewalls {
        xml.Firewall {
          xml.issues {
            xml.issue('id' => rule[:id], 'Category' => '2') {
  end
  xml.Data('mode' => "table") {
    xml.Row {
      xml.columnumn("ACL")
      xml.column("Rule")
      xml.column("Source")
      xml.column("Dest")
      xml.column("Service")
      xml.column("Log")
    }
    rule_array.each do |rule|
      xml.Row {
        xml.column(rule[:aclname])
        xml.column(rule[:Rule])
        xml.column(rule[:Source])
        xml.column(rule[:Destination])
        xml.column(rule[:port])
        xml.column(rule[:Log])
      }
    end
  }
            }
          }
        }
      }
    }
end
puts builder.to_xml

这立即表明存在问题,因为 XML 生成代码,如要输出的 XML,必须正确嵌套。

调整 XML 根和与 rule_array 关联的 end 以便它们正确嵌套导致:

builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
  xml.Report('Tool' => 'FirewallParserv1') {
    rule_array.each do |rule|
      xml.Firewalls {
        xml.Firewall {
          xml.issues {
            xml.issue('id' => rule[:id], 'Category' => '2') {
              xml.Data('mode' => "table") {
                xml.Row {
                  xml.columnumn("ACL")
                  xml.column("Rule")
                  xml.column("Source")
                  xml.column("Dest")
                  xml.column("Service")
                  xml.column("Log")
                }
                rule_array.each do |rule|
                  xml.Row {
                    xml.column(rule[:aclname])
                    xml.column(rule[:Rule])
                    xml.column(rule[:Source])
                    xml.column(rule[:Destination])
                    xml.column(rule[:port])
                    xml.column(rule[:Log])
                  }
                end
              }
            }
          }
        }
      }
    end
  }
end

但这不是很有效。稍微调整一下,我会使用:

#!/usr/bin/env ruby

require 'nokogiri'

HEADERS = %w(ACL Rule Source Dest Service Log)
FIELDS = %i(aclname Rule Source Destination port Log)

rule_array = [
  {:id => '1', :aclname => 'inside_access_in', :Rule => '1', :Source => '10.10.10.1',   :Destination => '192.168.1.2',    :port => 'SMTP',   :Log => 'YES'},
  {:id => '2', :aclname => 'inside_access_in', :Rule => '2', :Source => '172.16.2.1',   :Destination => '192.168.100.10', :port => 'HTTP',   :Log => 'NO'},
  {:id => '3', :aclname => 'inside_access_in', :Rule => '3', :Source => '172.16.2.200', :Destination => '10.10.60.1',     :port => 'TELNET', :Log => 'NO'}
]

builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
  xml.Report('Tool' => 'FirewallParserv1') {
    rule_array.each do |rule|
      xml.Firewalls {
        xml.Firewall {
          xml.issues {
            xml.issue('id' => rule[:id], 'Category' => '2') {
              xml.Data('mode' => "table") {
                xml.Row {
                  HEADERS.each do |h|
                    xml.column(h)
                  end
                }
                xml.Row {
                  rule.values_at(*FIELDS).each do |f|
                    xml.column(f)
                  end
                }
              }
            }
          }
        }
      }
    end
  }
end
puts builder.to_xml

其中,当 运行 时,输出:

# >> <?xml version="1.0" encoding="UTF-8"?>
# >> <Report Tool="FirewallParserv1">
# >>   <Firewalls>
# >>     <Firewall>
# >>       <issues>
# >>         <issue id="1" Category="2">
# >>           <Data mode="table">
# >>             <Row>
# >>               <column>ACL</column>
# >>               <column>Rule</column>
# >>               <column>Source</column>
# >>               <column>Dest</column>
# >>               <column>Service</column>
# >>               <column>Log</column>
# >>             </Row>
# >>             <Row>
# >>               <column>inside_access_in</column>
# >>               <column>1</column>
# >>               <column>10.10.10.1</column>
# >>               <column>192.168.1.2</column>
# >>               <column>SMTP</column>
# >>               <column>YES</column>
# >>             </Row>
# >>           </Data>
# >>         </issue>
# >>       </issues>
# >>     </Firewall>
# >>   </Firewalls>
# >>   <Firewalls>
# >>     <Firewall>
# >>       <issues>
# >>         <issue id="2" Category="2">
# >>           <Data mode="table">
# >>             <Row>
# >>               <column>ACL</column>
# >>               <column>Rule</column>
# >>               <column>Source</column>
# >>               <column>Dest</column>
# >>               <column>Service</column>
# >>               <column>Log</column>
# >>             </Row>
# >>             <Row>
# >>               <column>inside_access_in</column>
# >>               <column>2</column>
# >>               <column>172.16.2.1</column>
# >>               <column>192.168.100.10</column>
# >>               <column>HTTP</column>
# >>               <column>NO</column>
# >>             </Row>
# >>           </Data>
# >>         </issue>
# >>       </issues>
# >>     </Firewall>
# >>   </Firewalls>
# >>   <Firewalls>
# >>     <Firewall>
# >>       <issues>
# >>         <issue id="3" Category="2">
# >>           <Data mode="table">
# >>             <Row>
# >>               <column>ACL</column>
# >>               <column>Rule</column>
# >>               <column>Source</column>
# >>               <column>Dest</column>
# >>               <column>Service</column>
# >>               <column>Log</column>
# >>             </Row>
# >>             <Row>
# >>               <column>inside_access_in</column>
# >>               <column>3</column>
# >>               <column>172.16.2.200</column>
# >>               <column>10.10.60.1</column>
# >>               <column>TELNET</column>
# >>               <column>NO</column>
# >>             </Row>
# >>           </Data>
# >>         </issue>
# >>       </issues>
# >>     </Firewall>
# >>   </Firewalls>
# >> </Report>

看起来还算合理,虽然效率不是很高XML,但是没有遗漏,但oh-so-important想要的输出,它就足够了。

请注意,Nokogiri 将在散列中使用 Ruby 的符号键,有助于减少散列定义中的视觉噪声。