如何使用 Nokogiri 将带有命名空间的节点树插入到现有 XML 文件中?
How can I insert a tree of nodes with namespaces into an existing XML file using Nokogiri?
我一直在使用 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>
文档的基本结构不会因文档而异,然后是每个文档特定的 <node>
和 <edge>
标签的集合。
我已经能够使用 Nokogiri::XML::Builder.
在单一方法中成功构建此 XML
但是,我现在也想根据不同类型的数据生成这个文件——文件的大部分内容将保持不变;只有我的循环数据并生成 <node>
和 <edge>
标签的代码会改变。所以我正在有效地尝试创建一个 XML 模板,我可以从多个 Ruby 方法调用它,然后插入它们自己的变体。
我的想法是,我可以保存一个 XML 文件,其中包含除 <node>
和 <edge>
标签之外的所有内容。然后我会让每个不同的方法使用 Nokogiri::XML::Builder 创建一个 <node>
和 <edge>
标签的 DocumentFragment,打开模板文件,并将 DocumentFragment 作为 [=21= 的子项插入] 标签:
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:ShapeNode
、y:Geometry
等)。如果我这样做,我会得到一个 ArgumentError (Namespace y has not been defined)
.
这对我来说很有意义,因为 DocumentFragment 不知道模板 XML 文件中的所有命名空间定义。但是我不知道如何实际为 DocumentFragment 提供名称空间,因为它没有真正的根标记来添加它们;真正的根在模板文件中。
有没有办法将命名空间定义传递到 DocumentFragment 的 Nokogiri::XML::Builder 中?或者,有没有更好的方法让我创建带有命名空间的嵌套标签的集合,并将它们插入现有的 XML 文档中?
如果你想创建一个命名空间范围内的构建器实例,一个漂亮的小勾是使用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
将切出节点集。
我一直在使用 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>
文档的基本结构不会因文档而异,然后是每个文档特定的 <node>
和 <edge>
标签的集合。
我已经能够使用 Nokogiri::XML::Builder.
在单一方法中成功构建此 XML但是,我现在也想根据不同类型的数据生成这个文件——文件的大部分内容将保持不变;只有我的循环数据并生成 <node>
和 <edge>
标签的代码会改变。所以我正在有效地尝试创建一个 XML 模板,我可以从多个 Ruby 方法调用它,然后插入它们自己的变体。
我的想法是,我可以保存一个 XML 文件,其中包含除 <node>
和 <edge>
标签之外的所有内容。然后我会让每个不同的方法使用 Nokogiri::XML::Builder 创建一个 <node>
和 <edge>
标签的 DocumentFragment,打开模板文件,并将 DocumentFragment 作为 [=21= 的子项插入] 标签:
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:ShapeNode
、y:Geometry
等)。如果我这样做,我会得到一个 ArgumentError (Namespace y has not been defined)
.
这对我来说很有意义,因为 DocumentFragment 不知道模板 XML 文件中的所有命名空间定义。但是我不知道如何实际为 DocumentFragment 提供名称空间,因为它没有真正的根标记来添加它们;真正的根在模板文件中。
有没有办法将命名空间定义传递到 DocumentFragment 的 Nokogiri::XML::Builder 中?或者,有没有更好的方法让我创建带有命名空间的嵌套标签的集合,并将它们插入现有的 XML 文档中?
如果你想创建一个命名空间范围内的构建器实例,一个漂亮的小勾是使用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
将切出节点集。