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 ofFile.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 }
- Alternatively you could find all the files and then
- 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
Rails: Chartkick Cummulative User Graph
How to Create an Association Between Two Rails Models
Uploading a File to a S3 Presigned Url
How to Pass Multi Value Query Params in Swagger
Dangerousattributeerror in Omniauth Railscast Tutorial: Create Is Defined by Activerecord
Bundler: Not Executable: Script/Delayed_Job
Calling Ruby Method Without Instantiating Class
Intelligently Generating Combinations of Combinations
Validating Date Format Using Regular Expression
Why Do Numeric String Comparisons Give Unexpected Results
Ruby 2.1 with Erubis Template Engine
Rails: Merit Gem Badge Not Registering or Displaying
Openssl::Cipher::Ciphererror When Running Staging Db on Local
Ruby on Rails - Add Condition on ':Include =>' to Load Limited Number of Objects
Why Rails Can Use 'If' as Hash Key But Not in Ruby
Error When Starting Sinatra: "Tried to Create Proc Object Without a Block"