Ссылка на конкретный блок при построении xml

Я использую Ruby для создания XML-структуры в формате gexf, которая будет представлять сетевой граф. Граф состоит из нескольких уровней вложенных узлов. Идея состоит в том, чтобы проанализировать файл, который выглядит примерно так:

| top node | middle node | bottom node |
|    a     |      1      |    "name1"  |
|    b     |      1      |    "name6"  |
|    a     |      2      |    "name3"  |
|    b     |      2      |    "name8"  |
|    b     |      1      |    "name5"  |
|    a     |      1      |    "name2"  |
|    b     |      2      |    "name7"  |
|    a     |      2      |    "name4"  |

и преобразовать его в это:

<node id = a label = "top node">  
  <node id = 1 label = "middle node">
    <node id = name1 label = "bottom node"/>
    <node id = name2 label = "bottom node"/>
  </node>    
  <node id = 2 label = "middle node">      
    <node id = name3 label = "bottom node"/>
    <node id = name4 label = "bottom node"/>
  </node> 
</node>
<node id = b label = "top node">  
  <node id = 1 label = "middle node">
    <node id = name5 label = "bottom node"/>
    <node id = name6 label = "bottom node"/>
  </node>    
  <node id = 2 label = "middle node">      
    <node id = name7 label = "bottom node"/>
    <node id = name8 label = "bottom node"/>
  </node> 
</node>

Как видите, поскольку строки в файле не расположены в определенном порядке, мне нужно иметь возможность ссылаться на каждый узел и подузел при построении XML-файла.

Если мой вопрос все еще не ясен, когда я читаю строку:

|    b     |      1      |    "name6"  |

Мне нужно, чтобы строитель вставил этот узел "name6" в "верхний узел b" и "средний узел 1". Возможно ли это вообще с помощью Builder или Nokogiri's builder или чего-то еще?


person hriundel    schedule 17.05.2012    source источник
comment
Это возможно с Нокогири. Что вы пробовали?   -  person Mark Thomas    schedule 17.05.2012


Ответы (1)


Вместо того, чтобы пытаться контролировать узлы по мере их создания, используйте возможности запроса CSS (или XPath) Nokogiri для поиска узлов, уже добавленных в документ, когда они вам понадобятся:

require 'nokogiri'

# Create an array of the top/middle/bottom node ids
rows = File.readlines('my.data')[1..-1].map{ |row| row.scan(/[^|\s"]+/) }

# Look underneath a parent node for another node with a specific id
# If you can't find one, create one (with the label) and return it.
def find_or_create_on(parent,id,label)
  parent.at("node[id='#{id}']") or
  parent.add_child("<node id='#{id}' label='#{label}' />")[0]
end

# Since an XML document can only ever have one root node,
# and your data can have many, let's wrap them all in a new document
root = Nokogiri.XML('<root></root>').root

# For each triplet, find or create the nodes you need, in order
# (When iterating an array of arrays, you can automagically convert
#  each item in the sub-array to a named variable.)
rows.each do |top_id, mid_id, bot_id|
  top = find_or_create_on( root, top_id, 'top node'    )
  mid = find_or_create_on( top,  mid_id, 'middle node' )
  bot = find_or_create_on( mid,  bot_id, 'bottom node' )
end

puts root
#=> <root>
#=>   <node id="a" label="top node">
#=>     <node id="1" label="middle node">
#=>       <node id="name1" label="bottom node"/>
#=>       <node id="name2" label="bottom node"/>
#=>     </node>
#=>     <node id="2" label="middle node">
#=>       <node id="name3" label="bottom node"/>
#=>       <node id="name4" label="bottom node"/>
#=>     </node>
#=>   </node>
#=>   <node id="b" label="top node">
#=>     <node id="1" label="middle node">
#=>       <node id="name6" label="bottom node"/>
#=>       <node id="name5" label="bottom node"/>
#=>     </node>
#=>     <node id="2" label="middle node">
#=>       <node id="name8" label="bottom node"/>
#=>       <node id="name7" label="bottom node"/>
#=>     </node>
#=>   </node>
#=> </root>

Обратите внимание, что вы можете пересмотреть свое использование атрибута id, так как указанные здесь значения не являются ни а) глобально уникальными во всем документе, ни б) допустимыми идентификаторами (число не может быть значением ID в XML).

Кроме того, в ваших выходных данных есть некоторые дочерние узлы, отсортированные в другом порядке, чем в исходных данных. Например, b/2/name8 появляется перед b/2/name7, поэтому мое решение создает их в таком порядке. Если вам нужно их отсортировать, сначала отсортируйте rows, например:

rows.sort.each do |top_id,mid_id,bot_id|
person Phrogz    schedule 18.05.2012
comment
Ах, спасибо! Это имеет смысл. Причина, по которой я использую id, заключается в том, что именно так работает формат gexf. Каждый узел имеет идентификатор атрибута, который должен быть уникальным идентификатором. Идентификаторы в моем вопросе были примерными. На самом деле я удостоверяюсь, что они уникальны. И причина, по которой мои исходные данные отформатированы таким образом, заключалась в том, чтобы показать, что они не идут ни в каком порядке. - person hriundel; 19.05.2012
comment
Не за что; Я надеюсь, что это помогает. - person Phrogz; 19.05.2012