Open Xml File with Nokogiri Update Node and Save

open xml file with nokogiri update node and save

First of all, your node_update is actually a NodeSet, not the Node that you probably think it is. You need a Node if you want to call inner_html= on it:

node_update[0].inner_html = 'true'

Then writing out the updated XML is just a bit of standard file manipulating combined with a to_xml call:

File.open('whatever.xml', 'w') { |f| f.print(doc.to_xml) }

As an aside, your input isn't valid XML. You have a </details> but no <details>.

How to replace XML node contents using Nokogiri

def amend_parent_xml(folder, target_file, new_file)
Dir["#{@target_folder}/#{folder}/*.xml"]
.sort.select{|f| !File.directory? f }
.each do |xml_file|
doc = Nokogiri.XML( File.read(xml_file) )
if file = doc.at("//Route//To//Node//Filename[.='#{target_file}']")
file.content = new_file # set the text of the node
File.open(xml_file,'w'){ |f| f<<doc }
break
end
end
end

Improvements:

  • Use File.read instead of File.open so that you don't leave a file handle open.
  • Uses an XPath expression to find the SINGLE matching node by looking for a node with the correct text value.
    • Alternatively you could find all the files and then if file=files.find{ |f| f.text==target_file }
  • Shows how to serialize a Nokogiri::XML::Document back to disk.
  • Breaks out of processing the files as soon as it finds a matching XML file.

How to save my changes in XML file with Nokogiri

Read the file into an in-memory XML document, modify the document as needed, then serialize the document back into the original file:

filename = 'exam.xml'
xml = File.read(filename)
doc = Nokogiri::XML(xml)
# ... make changes to doc ...
File.write(filename, doc.to_xml)

Read and write xml file using Nokogiri

You can get the XML text of your document as a String using xml.to_xml and then write this to a file in the usual way.

How can I use Nokogiri with Ruby to replace values in existing xml?

You want the content= method:

Set the Node’s content to a Text node containing string. The string gets XML escaped, not interpreted as markup.

Note that xpath returns a NodeSet not a single Node, so you need to use at_xpath or get the single node some other way:

doc = Nokogiri::XML(File.open ("C:\\myfile.xml"))  
node = doc.xpath("//Ranch//Street")[0] # use [0] to select the first result
node.content = "New value for this node"

puts doc # produces XML document with new value for the node

How can I insert a tree of nodes with namespaces into an existing XML file using Nokogiri?

A nifty little tick if you want to create a builder instance scoped to a namespace is to use Nokogiri::XML::Builder.with(doc.root):

doc = Nokogiri::XML('<?xml version="1.0"?>
<root xmlns:y="foo"></root>')
builder = Nokogiri::XML::Builder.with(doc.root) do |xml|
xml['y'].Shapenode do |sn|
sn.Foo
sn.Bar
end
end

builder.to_xml outputs:

<?xml version="1.0"?>
<root xmlns:y="foo">
<y:Shapenode>
<y:Foo/>
<y:Bar/>
</y:Shapenode>
</root>

Worth noting though is that it mutates doc. If I where to do this I would use Nokogiri::XML::Builder.with(doc.root.dup) which prevents it from mutating the arguments.

You can also just create builders with any arbitrary root with:

builder = Nokogiri::XML::Builder.new do |xml|
xml.root('xmlns:y' => 'bar') do
xml['y'].Shapenode
end
end

builder.doc.xpath('/*').children will slice out the node set.

How do I use Nokogiri to parse an XML file?

Here I will try to explain you all the questions/confusions you are having:

require 'nokogiri'

doc = Nokogiri::XML.parse <<-XML
<Collection version="2.0" id="74j5hc4je3b9">
<Name>A Funfair in Bangkok</Name>
<PermaLink>Funfair in Bangkok</PermaLink>
<PermaLinkIsName>True</PermaLinkIsName>
<Description>A small funfair near On Nut in Bangkok.</Description>
<Date>2009-08-03T00:00:00</Date>
<IsHidden>False</IsHidden>
<Items>
<Item filename="AGC_1998.jpg">
<Title>Funfair in Bangkok</Title>
<Caption>A small funfair near On Nut in Bangkok.</Caption>
<Authors>Anthony Bouch</Authors>
<Copyright>Copyright © Anthony Bouch</Copyright>
<CreatedDate>2009-08-07T19:22:08</CreatedDate>
<Keywords>
<Keyword>Funfair</Keyword>
<Keyword>Bangkok</Keyword>
<Keyword>Thailand</Keyword>
</Keywords>
<ThumbnailSize width="133" height="200" />
<PreviewSize width="532" height="800" />
<OriginalSize width="2279" height="3425" />
</Item>
<Item filename="AGC_1164.jpg" iscover="True">
<Title>Bumper Cars at a Funfair in Bangkok</Title>
<Caption>Bumper cars at a small funfair near On Nut in Bangkok.</Caption>
<Authors>Anthony Bouch</Authors>
<Copyright>Copyright © Anthony Bouch</Copyright>
<CreatedDate>2009-08-03T22:08:24</CreatedDate>
<Keywords>
<Keyword>Bumper Cars</Keyword>
<Keyword>Funfair</Keyword>
<Keyword>Bangkok</Keyword>
<Keyword>Thailand</Keyword>
</Keywords>
<ThumbnailSize width="200" height="133" />
<PreviewSize width="800" height="532" />
<OriginalSize width="3725" height="2479" />
</Item>
</Items>
</Collection>
XML

So from my understanding of Nokogiri, each 'Items' is a node, and under that there are children nodes of 'Item'?

No, each Items are Nokogiri::XML::NodeSet. And under that there are 2 children nodes of Items,which are of Nokogiri::XML::Element class object. You can say them also Nokogiri::XML::Node

doc.class # => Nokogiri::XML::Document
@block = doc.xpath("//Items/Item")
@block.class # => Nokogiri::XML::NodeSet
@block.count # => 2
@block.map { |node| node.name }
# => ["Item", "Item"]
@block.map { |node| node.class }
# => [Nokogiri::XML::Element, Nokogiri::XML::Element]
@block.map { |node| node.children.count }
# => [19, 19]
@block.map { |node| node.class.superclass }
# => [Nokogiri::XML::Node, Nokogiri::XML::Node]

We create a map of this, which returns a hash I believe, and the code in {} goes through each node and places the children text into @block. Then I can display all of this child node's text to the screen.

I don't understand this. Although I tried to explain below to show what is Node,and what is Nodeset in Nokogiri. Remember Nodeset is a collection of Nodes.

@chld_class = @block.map do |node|
node.children.class
end
@chld_class
# => [Nokogiri::XML::NodeSet, Nokogiri::XML::NodeSet]
@chld_name = @block.map do |node|
node.children.map { |n| [n.name,n.class] }
end
@chld_name
# => [[["text", Nokogiri::XML::Text],
# ["Title", Nokogiri::XML::Element],
# ["text", Nokogiri::XML::Text],
# ["Caption", Nokogiri::XML::Element],
# ["text", Nokogiri::XML::Text],
# ["Authors", Nokogiri::XML::Element],
# ["text", Nokogiri::XML::Text],
# ["Copyright", Nokogiri::XML::Element],
# ["text", Nokogiri::XML::Text],
# ["CreatedDate", Nokogiri::XML::Element],
# ["text", Nokogiri::XML::Text],
# ["Keywords", Nokogiri::XML::Element],
# ["text", Nokogiri::XML::Text],
# ["ThumbnailSize", Nokogiri::XML::Element],
# ["text", Nokogiri::XML::Text],
# ["PreviewSize", Nokogiri::XML::Element],
# ["text", Nokogiri::XML::Text],
# ["OriginalSize", Nokogiri::XML::Element],
# ["text", Nokogiri::XML::Text]],
# [["text", Nokogiri::XML::Text],
# ["Title", Nokogiri::XML::Element],
# ["text", Nokogiri::XML::Text],
# ["Caption", Nokogiri::XML::Element],
# ["text", Nokogiri::XML::Text],
# ["Authors", Nokogiri::XML::Element],
# ["text", Nokogiri::XML::Text],
# ["Copyright", Nokogiri::XML::Element],
# ["text", Nokogiri::XML::Text],
# ["CreatedDate", Nokogiri::XML::Element],
# ["text", Nokogiri::XML::Text],
# ["Keywords", Nokogiri::XML::Element],
# ["text", Nokogiri::XML::Text],
# ["ThumbnailSize", Nokogiri::XML::Element],
# ["text", Nokogiri::XML::Text],
# ["PreviewSize", Nokogiri::XML::Element],
# ["text", Nokogiri::XML::Text],
# ["OriginalSize", Nokogiri::XML::Element],
# ["text", Nokogiri::XML::Text]]]

@chld_name = @block.map do |node|
node.children.map{|n| [n.name,n.text.strip] if n.elem? }.compact
end.compact
@chld_name
# => [[["Title", "Funfair in Bangkok"],
# ["Caption", "A small funfair near On Nut in Bangkok."],
# ["Authors", "Anthony Bouch"],
# ["Copyright", "Copyright © Anthony Bouch"],
# ["CreatedDate", "2009-08-07T19:22:08"],
# ["Keywords", "Funfair\n Bangkok\n Thailand"],
# ["ThumbnailSize", ""],
# ["PreviewSize", ""],
# ["OriginalSize", ""]],
# [["Title", "Bumper Cars at a Funfair in Bangkok"],
# ["Caption", "Bumper cars at a small funfair near On Nut in Bangkok."],
# ["Authors", "Anthony Bouch"],
# ["Copyright", "Copyright © Anthony Bouch"],
# ["CreatedDate", "2009-08-03T22:08:24"],
# ["Keywords",
# "Bumper Cars\n Funfair\n Bangkok\n Thailand"],
# ["ThumbnailSize", ""],
# ["PreviewSize", ""],
# ["OriginalSize", ""]]]

Insert/update row /Import from XML via Nokogiri

First, use a loop like this to walk through your XML:

doc.search('SHOPITEM').each do |shop_item|
item_id = shop_item.at('ITEM_ID').text
product = shop_item.at('PRODUCT').text
description = shop_item.at('DESCRIPTION').text
# ...
end

Each iteration that finds a <SHOPITEM> tag will gather the information to create/modify a row.

Instead of using css, which is equivalent to search, you should use at or at_css. Both css and search return a NodeSet, which is like an array. If you use inner_text on a NodeSet that contains more than one node, you'll get all the text nodes concatenated together as a string, which is most likely not what you want and will usually be a bug you have to fix:

require 'nokogiri'

doc = Nokogiri::XML(<<EOT)
<foo>
<bar>1</bar>
<bar>2</bar>
</foo>
EOT

doc.search('bar').inner_text # => "12"
doc.css('bar').inner_text # => "12"

For more information, read the documentation for css, search, at and at_css in the Nokogiri::XML::Node page, along with the inner_text documentation in the Nokogiri::XML::NodeSet page.



Related Topics



Leave a reply



Submit