Как я могу вставить дерево узлов с пространствами имен в существующий файл XML, используя Nokogiri? - PullRequest
0 голосов
/ 06 февраля 2020

Я использовал Nokogiri для создания файла XML (в частности, документа GraphML, использующего некоторые пространства имен yEd). Пример типа файла, который я генерирую:

<?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>

Документ имеет базовую c структуру, которая не изменяется от документа к документу, а затем коллекцию <node> и <edge> тегов, указывающих c на каждый документ.

Мне удалось успешно построить XML в одном методе, используя Nokogiri :: XML :: Builder.

Однако теперь я хочу сгенерировать этот файл на основе другого типа данных - большая часть файла останется неизменной; только мой код, который перебирает данные и генерирует теги <node> и <edge>, изменится. Поэтому я пытаюсь создать шаблон XML, который можно вызывать из нескольких методов Ruby, которые затем будут вставлять свои собственные варианты.

Я думал, что смогу сохранить файл XML со всем, кроме тегов <node> и <edge>. Затем я бы использовал каждый отдельный метод с помощью Nokogiri :: XML :: Builder для создания DocumentFragment тегов <node> и <edge>, открытия файла шаблона и вставки DocumentFragment в качестве дочернего элемента тега <graph> :

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

Так что этот код в основном делает то, что я хочу. Он успешно загружает файл шаблона XML в YED_TEMPLATE, успешно создает DocumentFragment и успешно вставляет DocumentFragment в шаблон XML ...

..., пока я не включаю y теги пространства имен (y:ShapeNode, y:Geometry и др. c.). Если я это сделаю, я получу ArgumentError (Namespace y has not been defined).

Это имеет смысл для меня, поскольку DocumentFragment не знает всех определений пространства имен в файле шаблона XML. Но я понятия не имею, как на самом деле предоставить пространства имен для DocumentFragment, так как у него нет истинного тега root для добавления их; настоящее root находится в файле шаблона.

Есть ли способ передать определения пространств имен в Nokogiri :: XML :: Builder для DocumentFragment? В качестве альтернативы, есть ли лучший способ для меня создать коллекцию вложенных тегов с пространствами имен и вставить их в существующий документ XML?

1 Ответ

2 голосов
/ 06 февраля 2020

Изящный маленький тик, если вы хотите создать экземпляр построителя, ограниченный пространством имен, должен использовать 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), который не позволяет ему изменять аргументы.

Вы также можете просто создавать конструкторы с любым произвольным root с помощью:

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

builder.doc.xpath('/*').children будет вырезать набор узлов.

...