如何使用Builder生成XML以循环遍历XML

时间:2017-02-05 19:31:14

标签: ruby xml nokogiri

原帖很乱,希望这个编辑更清楚!。

我希望从哈希中生成XML,嵌套在数组中。我试图使用Nokogiri建设者并且不能完全正确。感谢Tin Man的一些帮助,我现在离我更近了一点,但我的例子远远不够清楚,无法破译。我也错过了一些关键信息。

这是我需要生成的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>

到目前为止,我已经接近以下代码了,但是嵌套循环意味着我最终会对每个'问题'进行过多的迭代/重复,准确地说是三个。

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

如何在循环中循环并仅返回正确数量的“问题”?根据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|位之前我不访问该哈希值。作为一个临时的bodge,我只需为issue_id分配一个值,并在每个循环中递增它。有没有办法在迭代哈希之前得到:id值?或者我的逻辑是否存在缺陷?

编辑33: 似乎将ID值分配如下:

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

1 个答案:

答案 0 :(得分:2)

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

您想要的代码示例是:

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根目录和与end相关联的rule_array以便它们正确嵌套导致:

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,但是如果没有缺失但又非常重要的期望输出,那么它就足够了。

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