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
我正在使用 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