【问题标题】:How can I insert a tree of nodes with namespaces into an existing XML file using Nokogiri?如何使用 Nokogiri 将具有名称空间的节点树插入现有 XML 文件?
【发布时间】:2020-05-22 02:00:20
【问题描述】:

我一直在使用 Nokogiri 生成一个 XML 文件(特别是使用一些 yEd 命名空间的 GraphML 文档)。我正在生成的文件类型的示例:

<?xml version="1.0" encoding="UTF-8"?>
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd">
  <key attr.name="Description" attr.type="string" for="graph" id="d0"/>
  <key attr.name="description" attr.type="string" for="node" id="d5"/>
  <key for="node" id="d6" yfiles.type="nodegraphics"/>
  <key for="graphml" id="d7" yfiles.type="resources"/>
  <key attr.name="description" attr.type="string" for="edge" id="d9"/>
  <key for="edge" id="d10" yfiles.type="edgegraphics"/>
  <graph edgedefault="directed" id="G">
    <data key="d0"/>
    <node id="n10">
      <data key="d5"/>
      <data key="d6">
        <y:ShapeNode>
          <y:Geometry width="42.42640687119285" height="42.42640687119285" x="30.0" y="0.0"/>
          <y:Fill color="#DBDCDF" transparent="false"/>
          <y:BorderStyle color="#303236" raised="false" type="line" width="1.0"/>
          <y:NodeLabel alignment="center" fontFamily="Source Sans Pro Semibold" fontSize="17" fontStyle="plain" verticalTextPosition="bottom" horizontalTextPosition="center">DAY</y:NodeLabel>
          <y:Shape type="ellipse"/>
        </y:ShapeNode>
      </data>
    </node>
    <node id="n11">
      <data key="d5"/>
      <data key="d6">
        <y:ShapeNode>
          <y:Geometry width="30.0" height="30.0" x="-14.999999999999993" y="25.980762113533164"/>
          <y:Fill color="#DBDCDF" transparent="false"/>
          <y:BorderStyle color="#303236" raised="false" type="line" width="1.0"/>
          <y:NodeLabel alignment="center" fontFamily="Source Sans Pro Semibold" fontSize="12" fontStyle="plain" verticalTextPosition="bottom" horizontalTextPosition="center">STL</y:NodeLabel>
          <y:Shape type="ellipse"/>
        </y:ShapeNode>
      </data>
    </node>
    <node id="n12">
      <data key="d5"/>
      <data key="d6">
        <y:ShapeNode>
          <y:Geometry width="42.42640687119285" height="42.42640687119285" x="-15.000000000000014" y="-25.980762113533153"/>
          <y:Fill color="#DBDCDF" transparent="false"/>
          <y:BorderStyle color="#303236" raised="false" type="line" width="1.0"/>
          <y:NodeLabel alignment="center" fontFamily="Source Sans Pro Semibold" fontSize="17" fontStyle="plain" verticalTextPosition="bottom" horizontalTextPosition="center">DFW</y:NodeLabel>
          <y:Shape type="ellipse"/>
        </y:ShapeNode>
      </data>
    </node>
    <edge id="e0" source="n10" target="n11">
      <data key="d9"/>
      <data key="d10">
        <y:PolyLineEdge>
          <y:LineStyle width="2.0" color="#ff99cc"/>
          <y:Arrows source="none" target="standard"/>
          <y:EdgeLabel visible="false">American Airlines</y:EdgeLabel>
        </y:PolyLineEdge>
      </data>
    </edge>
    <edge id="e1" source="n11" target="n12">
      <data key="d9"/>
      <data key="d10">
        <y:PolyLineEdge>
          <y:LineStyle width="2.0" color="#ff99cc"/>
          <y:Arrows source="none" target="standard"/>
          <y:EdgeLabel visible="false">American Airlines</y:EdgeLabel>
        </y:PolyLineEdge>
      </data>
    </edge>
    <edge id="e2" source="n12" target="n10">
      <data key="d9"/>
      <data key="d10">
        <y:PolyLineEdge>
          <y:LineStyle width="2.0" color="#ff99cc"/>
          <y:Arrows source="none" target="standard"/>
          <y:EdgeLabel visible="false">American Airlines</y:EdgeLabel>
        </y:PolyLineEdge>
      </data>
    </edge>
  </graph>
  <data key="d7">
    <y:Resources/>
  </data>
</graphml>

文档具有不会因文档而异的基本结构,然后是每个文档特定的&lt;node&gt;&lt;edge&gt; 标记的集合。

我已经能够使用 Nokogiri::XML::Builder 以单一方法成功构建此 XML。

但是,我现在也想根据不同类型的数据生成这个文件——大部分文件都不会改变;只有循环数据并生成&lt;node&gt;&lt;edge&gt; 标记的代码会改变。因此,我正在有效地尝试创建一个 XML 模板,我可以从多个 Ruby 方法调用该模板,然后插入它们自己的变体。

我的想法是我可以保存一个 XML 文件,其中包含除 &lt;node&gt;&lt;edge&gt; 标记之外的所有内容。然后,我将让每种不同的方法使用 Nokogiri::XML::Builder 创建 &lt;node&gt;&lt;edge&gt; 标签的 DocumentFragment,打开模板文件,并将 DocumentFragment 作为 &lt;graph&gt; 标签的子项插入:

YED_TEMPLATE = "#{Rails.root}/app/views/xml_templates/flights.yed.graphml"

def self.yed_from_string(flight_string)
  airports = flight_string.split(/[,-]/).tally

  output = File.open(YED_TEMPLATE) {|f| Nokogiri::XML(f)}

  nodes = Nokogiri::XML::DocumentFragment.parse("")
  Nokogiri::XML::Builder.with(nodes) do |xml|
    airports.map{|airport, visits| yed_airport_node(xml, airport, airport, visits)}
  end

  # Write similar code for edges

  output.at("graph").add_child(nodes)
  return output.to_xml
end

private

def self.yed_airport_node(xml, id, text, visits)
  xml.node(id: "n#{id}") do
    xml.data(key: "d5")
    xml.data(key: "d6") do
      xml[:y].ShapeNode do
        xml[:y].Geometry(circle_size(visits))
        xml[:y].Fill(color: BASE_STYLES[:node_color_fill], transparent: false)
        xml[:y].BorderStyle(color: BASE_STYLES[:node_color_border], raised: false, type: "line", width: BASE_STYLES[:node_width_border])
        xml[:y].NodeLabel(text, **font(visits))
        xml[:y].Shape(type: "ellipse")
      end
    end
  end
  return nil
end

# Write similar method for edges

所以这段代码在很大程度上做了我想要的。它成功地在 YED_TEMPLATE 加载了模板 XML 文件,它成功地创建了一个 DocumentFragment,并且它成功地将 DocumentFragment 插入到了模板 XML 中......

...只要我不包含 y 命名空间标签(y:ShapeNodey:Geometry 等)。如果我这样做了,我会收到一个ArgumentError (Namespace y has not been defined)

这对我来说很有意义,因为 DocumentFragment 并不知道模板 XML 文件中的所有命名空间定义。但是我不知道如何将命名空间实际提供给 DocumentFragment,因为它没有真正的根标签来添加它们;真正的根在模板文件中。

我有没有办法将命名空间定义传递给 DocumentFragment 的 Nokogiri::XML::Builder?或者,我有没有更好的方法来创建带有命名空间的嵌套标签集合,并将它们插入到现有的 XML 文档中?

【问题讨论】:

  • 我们需要更多信息;你想要的输出是什么?添加最小的 XML,该 XML 可以准确地代表您尝试生成的问题。不要添加“更新”或“编辑”标签,只需将其添加到您最初包含的位置即可。添加的标签会在该文档中的什么位置出现?请参阅“How to Ask”、“Stack Overflow question checklist”和“MCVE”及其所有链接页面。将标签添加到文档很容易,但您想要在哪里添加标签并不明显。
  • 人们似乎对 Nokogiri 没有理解的一件事是,文档中任何提到参数的地方都可以是 node_or_tags,例如,“Add node_or_tags as a child of this Node. node_or_tags can be a Nokogiri::XML::Node, a ::DocumentFragment, a ::NodeSet, or a string containing markup.”。所以,"&lt;foo&gt;&lt;bar&gt;hello world!&lt;/bar&gt;&lt;/foo&gt;" 可以被传入,Nokogiri 将解析它并创建一个片段并执行该方法所做的任何事情。这可以让生活更轻松。
  • @theTinMan 很高兴知道这一点,如果一切都失败了,我当然可以使用我想要轻松插入的 XML 生成一个字符串。但如果我要生成任何复杂的 XML 字符串,我觉得我应该使用 XML 构建器来完成它;我的问题是我似乎无法自行构建文档的子集,因为该子集不知道它尚未附加到的主文档中定义的命名空间。

标签: ruby-on-rails ruby xml nokogiri


【解决方案1】:

如果你想创建一个作用域为命名空间的构建器实例,一个漂亮的小勾是使用Nokogiri::XML::Builder.with(doc.root)

doc = Nokogiri::XML('<?xml version="1.0"?>
<root xmlns:y="foo"></root>')
builder = Nokogiri::XML::Builder.with(doc.root) do |xml|
  xml['y'].Shapenode do |sn|
    sn.Foo
    sn.Bar
  end
end

builder.to_xml 输出:

<?xml version="1.0"?>
<root xmlns:y="foo">
  <y:Shapenode>
     <y:Foo/>
     <y:Bar/>
  </y:Shapenode>
</root>

值得注意的是,它会变异doc。如果我在哪里执行此操作,我会使用 Nokogiri::XML::Builder.with(doc.root.dup) 来防止它改变参数。

您也可以使用任意根创建构建器:

builder = Nokogiri::XML::Builder.new do |xml|
  xml.root('xmlns:y' => 'bar') do
    xml['y'].Shapenode
  end
end

builder.doc.xpath('/*').children 将切出节点集。

【讨论】:

  • 我最终使用了第二种策略(具有任意根的构建器),然后使用 template.at("graph").add_child(builder.at("root").children) 将其附加到我的模板中。这正是我让它工作所需要的,谢谢!
猜你喜欢
  • 1970-01-01
  • 2018-06-24
  • 1970-01-01
  • 2015-12-26
  • 2015-09-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-09-27
相关资源
最近更新 更多