How to save unescaped & in nokogiri xml?
Asked Answered
S

2

0

How can I save & in the final xml file using nokogiri?

My code is like:

require 'rubygems' 
require 'nokogiri'

  file_name = "amp.xml"
    @doc = Nokogiri::XML('<project/>')

    arg = Nokogiri::XML::Node.new "arg", @doc
    arg['line'] = "how to save only &???"
    @doc.root.add_child(arg)

    File.open(file_name, 'w') {|f| f.write(@doc.to_xml) }

and the output is like

<?xml version="1.0"?>
<project>
  <arg line="how to save only &amp;???"/>
</project>

UPDATE

Looks like I can use CDATA but not sure how to use it with nokogiri. I read the xml file using @doc = Nokogiri::XML(File.open(file_name))

Steffin answered 2/8, 2011 at 2:34 Comment(1)
& is an illegal xml character, I don't think you can save it unescaped. This question is about illegal characters https://mcmap.net/q/18226/-what-are-invalid-characters-in-xmlAlvar
A
3

You can't put an unescaped & in XML as you wish. Here is from the W3 spec for XML:

The ampersand character (&) and the left angle bracket (<) MUST NOT appear in their literal form, except when used as markup delimiters, or within a comment, a processing instruction, or a CDATA section. If they are needed elsewhere, they MUST be escaped using either numeric character references or the strings " & " and " < " respectively.

As for using CDATA in Nokogiri, here is info from Nokogiri's site, if you use Nokogiri::XML::Builder to build your XML.

UPDATE: Here is the code from my example mentioned in comments.

module Questions
  @source = File.dirname(__FILE__) + '/questions.xml'
  def parse
    if File.exists?(@source)
      File.open(@source, 'r+') do |document|
        q = {}
        text = Nokogiri::XML::Document.parse(document)
        text.xpath('.//question').each do |c|
          parent = c.attribute_nodes[2].to_s
          q[:type] = c.attribute_nodes[1].to_s.to_sym   # => question type
          q[:q_id] = c.attribute_nodes[0].to_s   # => question type
          q[:question] = c.xpath('.//q').first.content   # => question
          q[:answers] = []
          c.xpath('.//a').each { |ans|
            p = ans.attribute_nodes.first.value   # => point value
            a = ans.content   # => answer
            q[:answers] << [a, p]
          }
          if parent == "NA"
            Question.create!(q)
          else
            Question.first(conditions: {q_id: parent}).children << Question.create!(q)
          end
        end
      end
    end
  end

  def write
    builder = Nokogiri::XML::Builder.new do |xml|
      xml.root {
        Question.each do |t|
          xml.question(id: t.id, type: t.type, parent: t.parent) {
            xml.q_ t.q
            t.answers.each { |c|
              xml.a(point: c.p) { xml.text c.a }
            }
          }
        end
      }
    end
    document = builder.to_xml
    File.open(@source, 'w+') do |f|
      f.puts document
    end
  end   # end write

  module_function :parse
  module_function :write
end

--- And an example of what I was working with. ---

  <question id="q0000" type="root" parent="NA">
    <q>How do you feel about sports?</q>
    <a point="0">I don't have any interest in sports.</a>
    <a point="q0001">I like to play sports.</a>
    <a point="q0002">I follow college or professional sports.</a>
  </question>
Alvar answered 2/8, 2011 at 3:29 Comment(7)
I don't use builder. Or I don't know how to use builder if I read the xml file as in my 'update'.Steffin
Builder is the only way I have done xml with Nokogiri, hopefully someone else can help with your method.Alvar
No, I believe it is only for "building" the xml. I used XML::Document::parse to do the parsing in block form. I can show some code if you want.Alvar
Can I read the file my way and then create new node using builder and then add it to the file?Steffin
Ya in the use case I had, I was reading data and creating objects of a custom class I had. Then I would edit, and write back out to the file. I had one method to read and then a separate method to write back to the file. I'm not sure if that helps your requirement.Alvar
I believe that it would help out :-)Steffin
@Steffin I added some code, I hope it helps. I am by no means an expert with Nokogiri, this was my first experience. The brackets that are in there are required, it wouldn't work using do end and I guess that is a known item (not bug I guess).Alvar
B
1

I recently had a similar problem. You need to use xml.<< instead of xml.text https://www.rubydoc.info/github/sparklemotion/nokogiri/Nokogiri/XML/Builder#%3C%3C-instance_method

Change:

            t.answers.each { |c|
              xml.a(point: c.p) { xml.text c.a }
            }

to

            t.answers.each { |c|
              xml.a(point: c.p) { xml << c.a }
            }
Boabdil answered 10/5, 2021 at 20:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.