Замена части текста в узле Nokogiri с сохранением разметки в содержимом - PullRequest
0 голосов
/ 30 января 2020

Я пытаюсь заменить экземпляры уникальной строки в группе файлов, сканируя содержимое узлов с помощью Nokogiri, а затем выполняя gsub. Я сохраняю часть строки на месте и превращаю ее в тег привязки. Тем не менее, большинство узлов имеют разную форму разметки в содержимом, а не просто строки. Например, допустим, у меня есть файл, подобный этому:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<html>
    <head>
        <title>Title</title>
        <link href="style.css" rel="stylesheet" type="text/css" />
    </head>
    <body>
        <div>
            <p class="header">&lt;&lt;2&gt;&gt;Header</p>
            <p class="paragraph">
            <p class="text_style">Lorem ipsum blah blah blah. &lt;&lt;3&gt;&gt; Here is more content. <span class="style">Preserve this.</span> Blah blah extra text.</p>
        </div>
    </body>
</html>

В документе есть числа, окруженные &lt;&lt; и &gt;&gt;. Я хочу взять значение числа и преобразовать его в тег вроде этого: <a id='[#]'/>, но я хочу сохранить HTML разметку других элементов в том же разделе, то есть <span class="style">Preserve this.</span>.

Вот все, что я пробовал:

file = File.open("file.xhtml") {|f| Nokogiri::XML(f)}

file.xpath("//text()").each { |node|
    if node.text.match(/<<([^_]*)>>/)
        new_content = node.text.gsub(/<<([^_]*)>>/,"<a id=\"\\1\"/>")
        node.parent.inner_html = new_content
    end
}

gsub работает правильно, но поскольку он использует метод .text, любая разметка игнорируется и эффективно стирается. В этом случае деталь <span class="style">Preserve this.</span> полностью удаляется. (К вашему сведению, я использую метод .parent, потому что, если я просто делаю node.inner_html = new_content, я получаю эту ошибку: add_child_node': cannot reparent Nokogiri::XML::Element there (ArgumentError).)

Если я делаю это вместо этого:

    new_content = node.text.gsub(/<<([^_]*)>>/,"<a id=\"\\1\"/>")
    node.content = new_content

символы не экранированы должным образом: файл заканчивается &lt;a id="3"/&gt; вместо <a id="3"/>.

Я попытался использовать методы CSS вместо этого, например:

file.xpath("*").each { |node|
    if node.inner_html.match(/&lt;&lt;([^_]*)&gt;&gt;/)
        new_content = node.inner_html.gsub(/&lt;&lt;([^_]*)&gt;&gt;/,"<a id=\"\\1\"/>")
        node.inner_html = new_content
    end
}

The gsub работает, разметка сохраняется, а замененные теги экранируются правильно. Но теги <head> и <body> удаляются, что приводит к неверному файлу:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
        <title>Title</title>
        <link href="style.css" rel="stylesheet" type="text/css"/>
        <div>
            <p class="header"><a id="2"/>Header</p>
            <p class="paragraph">
            </p><p class="text_style">Lorem ipsum blah blah blah. <a id="3"/> Here is more content. <span class="style">Preserve this.</span> Blah blah extra text. </p>    
    </div>
</html>

Я подозреваю, что это как-то связано с тем, что я перебираю все узлы (file.css("*")), что также является избыточным, поскольку родительский узел сканируется в дополнение к его дочерним узлам.

Я просмотрел сеть, но не могу найти никаких решений для этого. Я просто хочу иметь возможность менять уникальный текст, сохраняя разметку и правильно ее кодируя. Есть ли что-то очень очевидное, что я здесь скучаю?

1 Ответ

2 голосов
/ 03 февраля 2020

Похоже, что это работает довольно хорошо:

require 'nokogiri'

doc = Nokogiri::XML(<<EOT)
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<html>
    <head>
        <title>Title</title>
        <link href="style.css" rel="stylesheet" type="text/css" />
    </head>
    <body>
        <div>
            <p class="header">&lt;&lt;2&gt;&gt;Header</p>
            <p class="paragraph">
            <p class="text_style">Lorem ipsum. &lt;&lt;3&gt;&gt; more content. <span class="style">Preserve this.</span> extra text.</p>
        </div>
    </body>
</html>
EOT

doc.search("//text()[contains(.,'<<')]").each do |node|
  node.replace(node.content.gsub(/<<(\d+)>>/, '<a id="[\1]" />'))
end

Что приводит к:

puts doc.to_html

# >> <html>
# >>     <head>
# >> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
# >>         <title>Title</title>
# >>         <link href="style.css" rel="stylesheet" type="text/css">
# >>     </head>
# >>     <body>
# >>         <div>
# >>             <p class="header"><a id="[2]"></a>Header</p>
# >>             <p class="paragraph">
# >>             <p class="text_style">Lorem ipsum. <a id="[3]"></a> more content. <span class="style">Preserve this.</span> extra text.</p>
# >>         </p>
# >>     </div>
# >> </body>
# >> </html>

Nokogiri добавляет строку

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

, вероятно, потому что разметка определяется как XML.

Селектор "//text()[contains(.,'<<')]" ищет только текстовые узлы, содержащие '<<'. Возможно, вы захотите изменить это, чтобы сделать его более конкретным c, если это может привести к ложным срабатываниям. См. « XPath: использование регулярного выражения в функции содержит » для синтаксиса.

replace выполняет трюк; Вы пытались изменить узел Nokogiri :: XML :: Text, чтобы он содержал <a.../>, но это невозможно, необходимо кодировать < и >. Изменение узла на Nokogiri :: XML :: Element, то есть то, к чему Nokogiri по умолчанию относится <a id="[2]">, позволяет хранить его, как вы хотите.

...