Nokogiri builder #to_xml, возврат каретки после добавления фрагментов текста невозможен - PullRequest
1 голос
/ 28 апреля 2019

Я использую Nokogiri 1.10.3 и Ruby 2.4.5.

У меня есть несколько сложных текстовых XML-строк для добавления в документ со стандартным композитным заголовком.Я делаю это, используя Builder для создания документа с заголовком, а затем перебираю строки, чтобы добавить их.

При использовании to_xml возврат каретки и отступы начала строки теряются издокумент, за исключением того, где они появляются в добавленных XML-строках.

Похоже, что только в самих XML-строках содержится "\n".

Примеры:

Хорошо: Builder без добавления 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 не имеют возврата каретки.Результирующая строка 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" 

Bad: Builder с добавляемыми строками 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 допустимо возвращение каретки, поэтому использование gsub для удаления всех возвратов каретки из строк не будет для меня вариантом, я боюсь.

Есть ли другой способ включитьэти текстовые строки, которые могут не вызвать такую ​​проблему?


Как указывает @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>" 

Так что приемлемым решением будет нечто, удаляющее теТекстовые элементы?

1 Ответ

2 голосов
/ 28 апреля 2019

Сериализация XML обрабатывается базовым libxml2. "Если libxml2 обнаружит, что уже есть какие-то текстовые узлы как дочерние узлы, он отключит автоматическое выравнивание для всего поддерева." AFAIK, это поведение 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
...