如何在 Ruby 中使用 Nokogiri 迭代 XML 嵌套元素



我正在尝试使用 Nokogiri 迭代 XML 中的文件夹结构,但我被困在了这个:

<test>
   <folder name="Folder A">
      <folder name="Folder A1">
         <file name="a.txt">Cool file</file>
      </folder>
      <folder name="Folder A2"></folder>
   </folder>
   <folder name="Folder B">
      <folder name="Folder B1"></folder>
      <folder name="Folder B2">
         <folder name="Folder B21">
            <file name="b.txt"></file>
         </folder>
   </folder>
</test>

因此,我想迭代一下,以便能够创建文件夹和文件树(文件夹 A1 和 A2 位于文件夹 A 内,文件夹 B1 和 B2 位于文件夹 B 内,文件夹 B21 位于文件夹 B2 内)。

所以我正在这样做:

nodes = allnodes.xpath('//folder')
nodes.each do |node|
  puts "name => #{node.attributes['name']}"
end

但这列出了我所有的文件夹(A、A1、A2、B、B1、B2、B21)。我怎样才能做到不检查以前的文件夹中是否有更多文件夹,然后将其发送到相同的递归函数?

非常感谢您的帮助:)

当您将 XPath 与 //foo 一起使用时,您会找到任何级别的foo元素。如果您改用./foo或仅使用foo则只会找到子元素。因此:

# Given an XML node, yields the node and all <file> children
# Then recursively does the same with every <folder> child
def process_files_and_folders(node,&blk)   
  yield node, node.xpath('file')
  node.xpath('folder').each{ |folder| process_files_and_folders(folder,&blk) }
end

这样做的关键是 (a) 递归(让方法调用所有子文件夹本身)和 (b) 捕获用户使用&blk表示法传递的块,然后将该块传递给以后的调用。

在实际操作中看到:

require 'nokogiri'
doc = Nokogiri.XML(my_xml)
process_files_and_folders( doc.root ) do |folder,files|
  depth  = folder.ancestors.length-1  # Just for pretty text output indenting
  indent = "  "*depth                 # Just for pretty text output indenting
  if folder['name']
    puts "#{indent}Processing the folder named #{folder['name']}"
  else
    puts "#{indent}No folder name; probably the root element."
  end
  unless files.empty?
    puts "#{indent}There are #{files.length} files in '#{folder['name']}':"
    files.each{ |file| print indent, file['name'], "n" }
  end
end

结果:

No folder name; probably the root element.
  Processing the folder named Folder A
    Processing the folder named Folder A1
    There are 1 files in 'Folder A1':
    a.txt
    Processing the folder named Folder A2
  Processing the folder named Folder B
    Processing the folder named Folder B1
    Processing the folder named Folder B2
      Processing the folder named Folder B21
      There are 1 files in 'Folder B21':
      b.txt

我会执行以下操作:

require 'nokogiri'
doc = Nokogiri::XML(<<-xml)
<test>
   <folder name="Folder A">
      <folder name="Folder A1">
         <file name="a.txt">Cool file</file>
      </folder>
      <folder name="Folder A2"></folder>
   </folder>
   <folder name="Folder B">
      <folder name="Folder B1"></folder>
      <folder name="Folder B2">
         <folder name="Folder B21">
            <file name="b.txt"></file>
         </folder>
   </folder>
</test>
xml
# Here I am collecting all folders, which has at-least one child.
parent_folders = doc.xpath("//folder").select do|folder_node|
  folder_node.xpath("./folder").size > 0
end
# Here I will iterate each parent directory, and would collect the corresponding
# sub-directories names.
parent_directory = parent_folders.each_with_object({}) do |parent_dir,dir_hash|
  dir_hash[parent_dir['name']] = parent_dir.xpath("./folder").collect do |sub_dir|
    sub_dir['name']
  end
end
parent_directory
# => {"Folder A"=>["Folder A1", "Folder A2"],
#     "Folder B"=>["Folder B1", "Folder B2", "Folder B21"],
#     "Folder B2"=>["Folder B21"]}

现在,您有一个哈希parent_directory,它维护所有目录(键)/子目录(值)关系。现在使用Hash#[]方法,您可以轻松提取给定目录的子目录。举个例子——

parent_directory['Folder A'] # => ["Folder A1", "Folder A2"]
有点

不清楚你想做什么,但假设你正在 Linux 系统上的磁盘上创建一个新的目录结构。

doc.xpath("//folder[not(folder)]").each do |f|
   path = f.xpath("ancestor-or-self::folder").map{|f| f['name']}.join("/")
   system("mkdir -p #{path}")
end

这是这样做的:

  • 第一行查找所有最低级别的文件夹(XML 中的叶节点)
  • 下一行查找所有包含文件夹的名称,并用斜杠连接它们以获取完整的"路径"。
  • 最后,系统命令"mkdir -p"创建最低级别的文件夹和介于两者之间的每个文件夹。

所以,我后来发现了如何解决它。

澄清一下,我打算有一个这样的功能:

def create_structure(nodeset, current_folder)
    new_folder = "#{current_folder }/#{nodeset.attributes['name']"
    Dir.makedir(new_folder)
    create_files_in_current_folder(nodeset, new_folder)
    subnodeset = nodeset.xpath('/folder')
    subnodeset.each do |node|
        create_structure(node, new_folder)
    end
end

这样我就可以将我在 xml 中的结构复制到文件系统中。

所以,至于解决方案,它就在我眼前。我不能使用"//folder"而是使用"/folder",因为第一个将返回所有文件夹,无论它们在 xml 结构中的位置如何,第二个将只返回当前节点根目录中的文件夹。

我希望这有所帮助,并感谢大家的回答。我会尽快尝试它们。

相关内容

  • 没有找到相关文章

最新更新