Построение дерева XML из массива "strings / that / are / paths" (в Ruby) - PullRequest
4 голосов
/ 01 октября 2009

Каков наилучший способ построить дерево XML в Ruby, если у вас есть массив строковых путей?


<code>paths = [
  "nodeA1",
  "nodeA1/nodeB1/nodeC1",
  "nodeA1/nodeB1/nodeC1/nodeD1/nodeE1",
  "nodeA1/nodeB1/nodeC2",
  "nodeA1/nodeB2/nodeC2",
  "nodeA3/nodeB2/nodeC3"
]</code>
xml = 
<code><nodeA1>
    <nodeB1>
        <nodeC1>
            <nodeD1>
                <nodeE1/>
            </nodeD1>
        </nodeC1>
        <nodeC2/>
    </nodeB1>
    <nodeB2>
        <nodeC2/>
        <nodeC3/>
    </nodeB2>
</nodeA1></code>

Моя первая мысль - разделить строку пути на массив и сравнить ее глубину и содержимое с предыдущим массивом, но потом, если я доберусь до пути "nodeA1 / nodeB1 / nodeC1 / nodeD1 / nodeE1", когда я пойду Вернемся к «nodeA1 / nodeB1 / nodeC2», узел [1] является общим предком, но отслеживать это беспорядочно, по крайней мере, так, как я это делал.

Я хотел бы также сделать его рекурсивным, чтобы я мог обрабатывать каждый уровень гнезда в своей собственной функции, но пока не нашел ни одного полууниверсального решения.

Какие идеи или вещи вы обычно делаете, когда сталкиваетесь с этой проблемой?

Спасибо! Lance

Ответы [ 3 ]

5 голосов
/ 01 октября 2009

REXML - твой друг! Вы получаете XPaths, так что используйте их!

require 'rexml/document'

paths = [
  "nodeA1",
  "nodeA1/nodeB1/nodeC1",
  "nodeA1/nodeB1/nodeC1/nodeD1/nodeE1",
  "nodeA1/nodeB1/nodeC2",
  "nodeA1/nodeB2/nodeC2",
  "nodeA3/nodeB2/nodeC3"
]

x = REXML::Document.new
x.elements << "xml"

paths.each do |p|
  steps = p.split(/\//)
  steps.each_index do |i|
    unless REXML::XPath.first(x,"/xml/" + steps[0..i]*"/")
      REXML::XPath.first(x,"/xml/" + steps[0...i]*"/").elements << steps[i]
    end
  end
end
puts x.to_s

Обратите внимание, что данные вашего примера имеют как nodeA1, так и nodeA3 на верхнем уровне, поэтому я начал с корня с именем "xml" здесь. Если «3» была опечаткой, а nodeA1 действительно был вашим корнем (как показывает пример вывода XML), вы можете удалить строку «x.elements <<« xml »и изменить все« / xml / »на просто "/".</p>

4 голосов
/ 01 октября 2009

Это очень похоже на этот вопрос . Вот модифицированная версия, основанная на ответе sris :

paths = [
  "nodeA1",
  "nodeA1/nodeB1/nodeC1",
  "nodeA1/nodeB1/nodeC1/nodeD1/nodeE1",
  "nodeA1/nodeB1/nodeC2",
  "nodeA1/nodeB2/nodeC2",
  "nodeA3/nodeB2/nodeC3"
]

tree = {}

paths.each do |path|
  current  = tree
  path.split("/").inject("") do |sub_path,dir|
    sub_path = File.join(sub_path, dir)
    current[sub_path] ||= {}
    current  = current[sub_path]
    sub_path
  end
end

def make_tree(prefix, node)
  tree = ""
  node.each_pair do |path, subtree| 
    tree += "#{prefix}<#{File.basename(path)}"
    if subtree.empty?
      tree += "/>\n"
    else
      tree += ">\n"
      tree += make_tree(prefix + "\t", subtree) unless subtree.empty?
      tree += "#{prefix}</#{File.basename(path)}>\n"
    end
  end
  tree
end

xml = make_tree "", tree
print xml

Edit:

Вот модифицированная версия, которая создает настоящий XML-документ с использованием Nokogiri. Я думаю, что на самом деле легче следовать, чем строковая версия. Я также исключил использование File, потому что оно вам на самом деле не нужно для удовлетворения ваших потребностей:

require 'nokogiri'

paths = [
  "nodeA1",
  "nodeA1/nodeB1/nodeC1",
  "nodeA1/nodeB1/nodeC1/nodeD1/nodeE1",
  "nodeA1/nodeB1/nodeC2",
  "nodeA1/nodeB2/nodeC2",
  "nodeA3/nodeB2/nodeC3"
]

tree = {}

paths.each do |path|
  current  = tree
  path.split("/").each do |name|
    current[name] ||= {}
    current  = current[name]
  end
end

def make_tree(node, curr = nil, doc = Nokogiri::XML::Document.new)
  #You need a root node for the XML.  Feel free to rename it.
  curr ||= doc.root = Nokogiri::XML::Node.new('root', doc)
  node.each_pair do |name, subtree|
      child = curr << Nokogiri::XML::Node.new(name, doc)
      make_tree(subtree, child, doc) unless subtree.empty?
  end
  doc
end

xml = make_tree tree
print xml

Редактировать 2:

Да, это правда, что в Ruby 1.8 хэши не гарантируют поддержание порядка вставки. Если это проблема, есть способы обойти это. Вот решение, которое сохраняет порядок, но не беспокоится о рекурсии и гораздо проще для него:

require 'nokogiri'

paths = [
  "nodeA1",
  "nodeA1/nodeB1/nodeC1",
  "nodeA1/nodeB1/nodeC1/nodeD1/nodeE1",
  "nodeA1/nodeB1/nodeC2",
  "nodeA1/nodeB2/nodeC2",
  "nodeA3/nodeB2/nodeC3"
]

doc = Nokogiri::XML::Document.new
doc.root = Nokogiri::XML::Node.new('root', doc)

paths.each do |path|
  curr = doc.root
  path.split("/").each do |name|
    curr = curr.xpath(name).first || curr << Nokogiri::XML::Node.new(name, doc)
  end
end

print doc
1 голос
/ 01 октября 2009

Похоже, другая версия этот вопрос .

Таким образом, вы можете просто определить древовидную структуру и создать узлы для каждой строки в списке. Затем напишите метод вывода, который выводит дерево в формате xml.

Если вы хотите обойтись без определения древовидной структуры, вы должны убедиться, что список отсортирован, как в вашем примере. Затем переберите список и сравните каждую строку с предыдущей:

  • Для всех узлов в предыдущей строке, которые не являются частью текущей, напишите закрывающий тег (в обратном порядке)
  • Для всех узлов в текущей строке, которые не являются частью предыдущей строки, напишите открывающий тег.

Это решение не может создавать самозакрывающиеся теги (""), поскольку для этого требуется сравнение с предыдущей и следующей строкой.

И это решение не является рекурсивным, но я думаю, что проблема также не является рекурсивной ... (или я просто не совсем понял, почему вы хотели рекурсивную функцию)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...