title: Replacing part of the text in a Nokogiri node while preserving markup in contents
body: <p>I'm trying to replace instances of a unique string across a bunch of files by scanning the content of the nodes with Nokogiri and then performing a <code>gsub</code>. I'm keeping part of the string in place, and transforming it into an anchor tag. However, the majority of the nodes have various forms of markup in the contents, and aren't just straightforward strings. For example, let's say I have a file like this:</p>
  
  <pre><code>&lt;?xml version="1.0" encoding="UTF-8" standalone="no"?&gt;
  &lt;html&gt;
      &lt;head&gt;
          &lt;title&gt;Title&lt;/title&gt;
          &lt;link href="style.css" rel="stylesheet" type="text/css" /&gt;
      &lt;/head&gt;
      &lt;body&gt;
          &lt;div&gt;
              &lt;p class="header"&gt;&amp;lt;&amp;lt;2&amp;gt;&amp;gt;Header&lt;/p&gt;
              &lt;p class="paragraph"&gt;
              &lt;p class="text_style"&gt;Lorem ipsum blah blah blah. &amp;lt;&amp;lt;3&amp;gt;&amp;gt; Here is more content. &lt;span class="style"&gt;Preserve this.&lt;/span&gt; Blah blah extra text.&lt;/p&gt;
          &lt;/div&gt;
      &lt;/body&gt;
  &lt;/html&gt;
  </code></pre>
  
  <p>There are numbers throughout the document, surrounded by <code>&amp;lt;&amp;lt;</code> and <code>&amp;gt;&amp;gt;</code>. I want to take the value of the number and transform it into a tag like this: <code>&lt;a id='[#]'/&gt;</code>, but I want to preserve the HTML markup of other elements within the same section, i.e. <code>&lt;span class="style"&gt;Preserve this.&lt;/span&gt;</code>.</p>
  
  <p>Here's everything I've tried:</p>
  
  <pre><code>file = File.open("file.xhtml") {|f| Nokogiri::XML(f)}
  
  file.xpath("//text()").each { |node|
      if node.text.match(/&lt;&lt;([^_]*)&gt;&gt;/)
          new_content = node.text.gsub(/&lt;&lt;([^_]*)&gt;&gt;/,"&lt;a id=\"\\1\"/&gt;")
          node.parent.inner_html = new_content
      end
  }
  </code></pre>
  
  <p>The <code>gsub</code> works correctly, but because it uses the <code>.text</code> method, any markup is ignored and effectively wiped out. In this case, the <code>&lt;span class="style"&gt;Preserve this.&lt;/span&gt;</code> part is completely removed. (FYI, I use the <code>.parent</code> method because if I just do <code>node.inner_html = new_content</code> I get this error: <code>add_child_node': cannot reparent Nokogiri::XML::Element there (ArgumentError)</code>.)</p>
  
  <p>If I do this instead:</p>
  
  <pre><code>    new_content = node.text.gsub(/&lt;&lt;([^_]*)&gt;&gt;/,"&lt;a id=\"\\1\"/&gt;")
      node.content = new_content
  </code></pre>
  
  <p>the characters aren't properly escaped: the file ends up with <code>&amp;lt;a id="3"/&amp;gt;</code> instead of <code>&lt;a id="3"/&gt;</code>.</p>
  
  <p>I tried using the CSS methods instead like so:</p>
  
  <pre><code>file.xpath("*").each { |node|
      if node.inner_html.match(/&amp;lt;&amp;lt;([^_]*)&amp;gt;&amp;gt;/)
          new_content = node.inner_html.gsub(/&amp;lt;&amp;lt;([^_]*)&amp;gt;&amp;gt;/,"&lt;a id=\"\\1\"/&gt;")
          node.inner_html = new_content
      end
  }
  </code></pre>
  
  <p>The <code>gsub</code> works, the markup is preserved, and the replaced tags are escaped properly. But the <code>&lt;head&gt;</code> and <code>&lt;body&gt;</code> tags are removed, which results in an invalid file:</p>
  
  <pre><code>&lt;?xml version="1.0" encoding="UTF-8" standalone="no"?&gt;
  &lt;html&gt;
  &lt;meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/&gt;
          &lt;title&gt;Title&lt;/title&gt;
          &lt;link href="style.css" rel="stylesheet" type="text/css"/&gt;
          &lt;div&gt;
              &lt;p class="header"&gt;&lt;a id="2"/&gt;Header&lt;/p&gt;
              &lt;p class="paragraph"&gt;
              &lt;/p&gt;&lt;p class="text_style"&gt;Lorem ipsum blah blah blah. &lt;a id="3"/&gt; Here is more content. &lt;span class="style"&gt;Preserve this.&lt;/span&gt; Blah blah extra text. &lt;/p&gt;    
      &lt;/div&gt;
  &lt;/html&gt;
  </code></pre>
  
  <p>I suspect it has something to do with the fact that I'm iterating over all the nodes (<code>file.css("*")</code>), which is also redundant, since a parent node is scanned in addition to its children. </p>
  
  <p>I've scoured the web but can't find any solutions for this. I just want to be able to swap out unique text while maintaining markup and having it be correctly encoded. Is there something very obvious that I'm missing here?</p>