Skip to content

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

Description

@flavorjones

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

It's a good example of:

  • inline XML here-document
  • parse options
  • namespaces
  • multiple ways to find a node
  • multiple ways to add a node to a doc
  • introduction to Builder
  • how to save a doc
#! /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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions