Nokogiri生成器#to_xml,添加文本片段后无回车符

时间:2019-04-28 14:47:38

标签: ruby xml nokogiri

Nokogiri 1.10.3

Ruby 2.4.5

我有许多复杂的XML文本字符串可添加到具有标准标头组合的文档中,我通过使用Builder来创建带有标头的文档,然后遍历这些字符串以达到此目的。将它们添加进来。

#to_xml发送到Nokogiri::XML::Builder时,回车和行首缩进将从文档中丢失,除非它们出现在已添加的xml字符串中。

似乎只有XML字符串本身包含\n

示例

好:没有添加XML字符串的生成器。结果XML字符串具有回车符和缩进:

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" 

例如,注意\n</NumberOne><NumberTwo>之间的空格。

好:添加了XML字符串且XML字符串没有回车符的Builder。结果XML字符串具有回车符和缩进:

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 的生成器具有回车符。所得的XML字符串具有回车符和缩进,但插入的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和空格已删除。

让XML内容包含回车符是合法的,因此恐怕我无法将所有回车符从字符串中移出。

还有另一种方法来包含这些可能不会引起此类问题的文本字符串吗?

编辑

正如@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对象,其中包含一个空格(或在前面的示例中带有\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>" 

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

1 个答案:

答案 0 :(得分:1)

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>

一个可能有用的技巧是自己解析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