Nokogiri 生成器#to_xml,添加文本片段后没有回车return

Nokogiri builder #to_xml, no carriage return after adding text fragments

我正在使用 Nokogiri 1.10.3 和 Ruby 2.4.5。

我有许多复杂的 XML 文本字符串要添加到具有标准 header 复合的文档中。我正在使用 Builder 使用 header 创建文档,然后遍历字符串以添加它们。

当使用 to_xml 回车时 returns 和 beginning-of-line 缩进将从文档中丢失,除了它们出现在已添加的 XML 字符串中的位置。

似乎只有当 XML 字符串本身包含“\n”时才会出现。

示例:

好:未添加 XML 字符串的生成器。生成的 XML 字符串有回车符 returns 和缩进:

xml = Nokogiri::XML::Builder.new(encoding: "utf-8")
xml.Message do
  xml.Header do
    xml.NumberOne "1"
    xml.NumberTwo "2"
  end
end ; 0

xml.to_xml

=> "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Message>\n  <Header>\n    <NumberOne>1</NumberOne>\n    <NumberTwo>2</NumberTwo>\n  </Header>\n</Message>\n" 

注意 </NumberOne><NumberTwo> 之间的“\n”和 space,例如。

好:生成器添加了 XML 个字符串,并且 XML 个字符串没有回车 returns。生成的 XML 字符串有回车符 returns 和缩进:

xml_text1 = "<text>text1</text>"

xml = Nokogiri::XML::Builder.new(encoding: "utf-8")
xml.Message do
  xml.Header do
    xml.NumberOne "1"
    xml.NumberTwo "2"
  end
  xml << xml_text1
end ; 0

xml.to_xml

=> "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Message>\n  <Header>\n    <NumberOne>1</NumberOne>\n    <NumberTwo>2</NumberTwo>\n  </Header>\n  <text>text1</text>\n</Message>\n" 

不好:生成器添加了 XML 个字符串,XML 个字符串 do 有回车符 returns。生成的 XML 字符串删除了回车符 returns 和缩进,除非插入的 XML 字符串具有它们:

xml_text1 = "<text1>text1</text1>\n<text2>text2</text2>"

xml = Nokogiri::XML::Builder.new(encoding: "utf-8")
xml.Message do
  xml.Header do
    xml.NumberOne "1"
    xml.NumberTwo "2"
  end
  xml << xml_text1
end ; 0

xml.to_xml

=> "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Message><Header><NumberOne>1</NumberOne><NumberTwo>2</NumberTwo></Header><text1>text1</text1>\n<text2>text2</text2></Message>\n"

请注意,“\n”和 space 已被删除。

XML 内容中包含回车符 returns 是合法的,因此使用 gsub 删除字符串中的所有回车符 returns 将不会恐怕是我的一个选择。

是否有另一种方法来包含这些可能不会触发此类问题的文本字符串?


正如@igneus 指出的那样,XML 元素之间存在任何文本会导致此行为。

举个例子:

xml_text1 = "<text1>tex<b> <b>t1</text1> <text2>text2</text2>"

xml = Nokogiri::XML::Builder.new(encoding: "utf-8")
xml.Message do
  xml.Header do
    xml.NumberOne "1"
    xml.NumberTwo "2"
  end
  xml << xml_text1.gsub(/>\n {0,}</, "><")
end ; 0

xml.to_xml

=> "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Message><Header><NumberOne>1</NumberOne><NumberTwo>2</NumberTwo></Header><text1>tex<b> <b>t1</b></b></text1> <text2>text2</text2></Message>\n" 

事实上,当文本字符串转换为片段时,我们会看到额外的 Nokogiri::XML::Text objects 包含 space,或者在前面的示例中带有“\n” , "\n", 等等

xml_text1 = "<text1>tex<b> <b>t1</text1> <text2>text2</text2>"

xml = Nokogiri::XML::Builder.new(encoding: "utf-8")

=> #<Nokogiri::XML::DocumentFragment:0x3fff1805bcb4 name="#document-fragment" children=[#<Nokogiri::XML::Element:0x3fff1805b700 name="text1" children=[#<Nokogiri::XML::Text:0x3fff1805a4f4 "tex">, #<Nokogiri::XML::Element:0x3fff1805a3b4 name="b" children=[#<Nokogiri::XML::Text:0x3fff19a93fc8 " ">, #<Nokogiri::XML::Element:0x3fff19a93dac name="b" children=[#<Nokogiri::XML::Text:0x3fff19a93a3c "t1">]>, #<Nokogiri::XML::Text:0x3fff19a93730 " ">, #<Nokogiri::XML::Element:0x3fff19a9358c name="text2" children=[#<Nokogiri::XML::Text:0x3fff19a93258 "text2">]>]>]>]>

这些元素未被 to_xml 忽略。

xml.doc.fragment(xml_text1).to_xml(indent: 0)
 => "<text1>tex<b> <b>t1</b> <text2>text2</text2></b></text1>" 

那么可以通过的解决方案是删除那些文本元素吗?

XML 序列化由底层 libxml2 处理。 "If libxml2 detects that there is already some text nodes as children of a node it will disable automatic indenting for the whole subtree." 据我所知,无法更改此 libxml2 行为。

在您的示例中,这样的文本节点是由元素之间的换行符生成的,但对于任何元素间文本也是如此。由于文本节点被添加到根元素,整个文档呈现时没有缩进。如果它被添加到文档结构的某个地方,只有包含它的子树会缺少缩进:

xml_text1 = "<text1>text1</text1>a<text2>text2</text2>"
xml = Nokogiri::XML::Builder.new(encoding: "utf-8")
xml.Message do
  xml.Header do
    xml.NumberOne "1"
    xml.NumberTwo "2"
  end
  # wrapper element added
  xml.Wrapper do
    xml << xml_text1
  end
end

puts xml.to_xml

只有<Wrapper>的内容没有缩进:

<?xml version="1.0" encoding="utf-8"?>
<Message>
  <Header>
    <NumberOne>1</NumberOne>
    <NumberTwo>2</NumberTwo>
  </Header>
  <Wrapper><text1>text1</text1>a<text2>text2</text2></Wrapper>
</Message>

一个可能有用的 hack 是自己解析 XML 字符串并删除不需要的文本元素:

xml_text1 = "<text1>text1</text1>\n<text2>text2</text2>"

xml = Nokogiri::XML::Builder.new(encoding: "utf-8")
xml.Message do
  xml.Header do
    xml.NumberOne "1"
    xml.NumberTwo "2"
  end

  doc.fragment(xml_text1).children.each do |node|
    # drop all whitespace-only text nodes
    next if node.text? && node.content =~ /\A\s+\Z/
    insert node
  end
end