sparklemotion / nokogiri.org

Documentation site for Nokogiri (a ruby library)
https://nokogiri.org/
MIT License
46 stars 24 forks source link

add example to tutorials: find an xml node, manipulate it, and save it #29

Open flavorjones opened 4 years ago

flavorjones commented 4 years ago

Answer to nokogiri-talk thread Simple example to read+edit KML files

It's a good example of:

#! /usr/bin/env ruby

require "nokogiri"

xml = <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
  <Document>
    <name>Document.kml</name>
    <Placemark>
      <name>Waypoint</name>
      <Point>
        <coordinates>-122.371,37.816,0</coordinates>
      </Point>
    </Placemark>
    <Placemark>
      <name>Track</name>
      <LineString>
        <coordinates>-0.376291,43.296237,199.75 -0.376299,43.296237,199.75</coordinates>
      </LineString>
    </Placemark>
  </Document>
</kml>
EOF

doc = Nokogiri::XML(xml)

# 1. insert/replace the document's name. 
#
# First, let's find the name node.
# For explanation of why the "xmlns" is needed, check out:
# > https://nokogiri.org/tutorials/searching_a_xml_html_document.html#namespaces
# 
name_node = doc.at_xpath("/xmlns:kml/xmlns:Document/xmlns:name")

#
# You could also use a CSS query which will (mostly) ignore namespaces. This is exactly the same search.
#
name_node = doc.at_css("kml > Document > name")

#
# Modify the contents of the <name/> node
#
name_node.content = "New Document Name"

# 2. a. find all Placemark blocks that contain a LineString
#
# I can think of two ways to do this. The first way is to search for
# LineString nodes within a Placemark node, and then get those nodes' parents:
placemarks_with_linestring = doc.xpath("//xmlns:Placemark/xmlns:LineString").map(&:parent)

# This first approach would work with CSS as well:
placemarks_with_linestring = doc.css("Placemark > LineString").map(&:parent)

# The second approach is to just use an XPath query to express that
# you want Placemarks that contain a LineString:
placemarks_with_linestring = doc.xpath("//xmlns:Placemark[xmlns:LineString]")

# Then you can add a new child node to that Placemark:
placemarks_with_linestring.each do |placemark|
  # the string passed into add_child is parsed just like any other XML fragment
  placemark.add_child "<some>blah</some>"
end

# The end result:
puts doc.to_xml
# >> <?xml version="1.0" encoding="UTF-8"?>
# >> <kml xmlns="http://www.opengis.net/kml/2.2">
# >>   <Document>
# >>     <name>New Document Name</name>
# >>     <Placemark>
# >>       <name>Waypoint</name>
# >>       <Point>
# >>         <coordinates>-122.371,37.816,0</coordinates>
# >>       </Point>
# >>     </Placemark>
# >>     <Placemark>
# >>       <name>Track</name>
# >>       <LineString>
# >>         <coordinates>-0.376291,43.296237,199.75 -0.376299,43.296237,199.75</coordinates>
# >>       </LineString>
# >>     <some>blah</some></Placemark>
# >>   </Document>
# >> </kml>

# 3. Write this to a file
#
# Use normal Ruby idioms for opening a file and writing to it, and use #to_xml to serialize the doc:
File.open("output.kml", "w") do |file|
  file.write doc.to_xml
end

and the variation in response to the question "why is the new node not lined up?"

doc = Nokogiri::XML(xml) do |config|
  config.noblanks
end

and another variation on how to add the new node, using Builder.with functionality:

placemarks_with_linestring.each do |placemark|
  Nokogiri::XML::Builder.with(placemark) do |placemark|
    placemark.some "blah"
  end
end