You are here: Home Dive Into Python 3

Difficulty level: ♦♦♦♢♢

XML

FIXME
— FIXME

 

Diving In

Most of the chapters in this book have centered around a piece of sample code. But XML isn’t about code; it’s about data. One common use of XML is “syndication feeds” that list the latest articles on a blog, forum, or other frequently-updated website. Most popular blogging software can produce a feed and update it whenever new articles, discussion threads, or blog posts are published. You can follow a blog by “subscribing” to its feed, and you can follow multiple blogs with a dedicated “feed aggregator” like Google Reader.

Here, then, is the XML data we’ll be working with in this chapter. It’s a feed — specifically, an Atom syndication feed.

[download feed.xml]

<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  <title type="text">dive into mark</title>
  <subtitle type="text">currently between addictions</subtitle>
  <id>tag:diveintomark.org,2001-07-29:/</id>
  <updated>2009-03-27T21:56:07Z</updated>
  <link rel="alternate" type="text/html" href="http://diveintomark.org/"/>
  <link rel="self" href="http://diveintomark.org/feed/" type="application/atom+xml"/>
  <entry>
    <author>
      <name>Mark</name>
      <uri>http://diveintomark.org/</uri>
    </author>
    <title type="html"><![CDATA[Dive into history, 2009 edition]]></title>
    <link rel="alternate" type="text/html"
      href="http://diveintomark.org/archives/2009/03/27/dive-into-history-2009-edition"/>
    <id>tag:diveintomark.org,2009-03-27:/archives/20090327172042</id>
    <updated>2009-03-27T21:56:07Z</updated>
    <published>2009-03-27T17:20:42Z</published>
    <category scheme="http://diveintomark.org" term="diveintopython"/>
    <category scheme="http://diveintomark.org" term="docbook"/>
    <category scheme="http://diveintomark.org" term="html"/>
    <summary type="html">Putting an entire chapter on one page sounds bloated, but
      consider this: my longest chapter so far would be 75 printed pages, and it
      loads in under 5 seconds. On dialup.</summary>
  </entry>
  <entry>
    <author>
      <name>Mark</name>
      <uri>http://diveintomark.org/</uri>
    </author>
    <title type="html"><![CDATA[Accessibility is a harsh mistress]]></title>
    <link rel="alternate" type="text/html"
      href="http://diveintomark.org/archives/2009/03/21/accessibility-is-a-harsh-mistress"/>
    <id>tag:diveintomark.org,2009-03-21:/archives/20090321200928</id>
    <updated>2009-03-22T01:05:37Z</updated>
    <published>2009-03-21T20:09:28Z</published>
    <category scheme="http://diveintomark.org" term="accessibility"/>
    <summary type="html">The accessibility orthodoxy does not permit people to
      question the value of features that are rarely useful and rarely used.</summary>
  </entry>
  <entry>
    <author>
      <name>Mark</name>
      <uri>http://diveintomark.org/</uri>
    </author>
    <title type="html"><![CDATA[A gentle introduction to video encoding,
      part 1: container formats]]></title>
    <link rel="alternate" type="text/html"
      href="http://diveintomark.org/archives/2008/12/18/give-part-1-container-formats"/>
    <id>tag:diveintomark.org,2008-12-18:/archives/20081218155422</id>
    <updated>2009-01-11T19:39:22Z</updated>
    <published>2008-12-18T15:54:22Z</published>
    <category scheme="http://diveintomark.org" term="asf"/>
    <category scheme="http://diveintomark.org" term="avi"/>
    <category scheme="http://diveintomark.org" term="encoding"/>
    <category scheme="http://diveintomark.org" term="flv"/>
    <category scheme="http://diveintomark.org" term="GIVE"/>
    <category scheme="http://diveintomark.org" term="mp4"/>
    <category scheme="http://diveintomark.org" term="ogg"/>
    <category scheme="http://diveintomark.org" term="video"/>
    <summary type="html">These notes will eventually become part of a
      tech talk on video encoding.</summary>
  </entry>
</feed>

A 5-Minute Crash Course in XML

If you already know about XML, you can skip this section.

XML is a generalized way of describing hierarchical structured data. An XML document contains one or more elements, which are delimited by start and end tags. This is a complete (albeit boring) XML document:

<foo>   
</foo>  
  1. This is the start tag of the foo element.
  2. This is the matching end tag of the foo element. Like balancing parentheses in writing or mathematics or code, every start tag much be closed (matched) by a corresponding end tag.

Elements can be nested. An element bar inside an element foo is said to be a subelement or child of foo.

<foo>
  <bar></bar>
</foo>

Elements can have attributes, which are name-value pairs. Attributes are listed within the start tag of an element. Attribute names can not be repeated within an element. Attribute values must be quoted.

<foo lang="en">          
  <bar lang="fr"></bar>  
</foo>
  1. The foo element has one attribute, named lang. The value of its lang attribute is en.
  2. The bar element has one attribute, named lang. The value of its lang attribute is fr. This doesn’t conflict with the foo element in any way. Each element has its own set of attributes.

If an element has more than one attribute, the ordering of the attributes is not significant. An element’s attributes form an unordered set of keys and values, like a Python dictionary.

Elements can have text content.

<foo lang="en">
  <bar lang="fr">PapayaWhip</bar>
</foo>

Elements that contain no text and no children are empty.

<foo></foo>

There is a shorthand for writing empty elements. By putting a / character in the start tag, you can skip the end tag altogther. The XML document in the previous example could be written like this instead:

<foo/>

Like Python functions can be declared in different modules, XML elements can be declared in different namespaces. Namespaces usually look like URLs. You use an xmlns declaration to define a default namespace. A namespace declaration looks similar to an attribute, but it has a different purpose.

<feed xmlns="http://www.w3.org/2005/Atom">  
  <title>dive into mark</title>             
</feed>
  1. The feed element is in the http://www.w3.org/2005/Atom namespace.
  2. The title element is also in the http://www.w3.org/2005/Atom namespace. The namespace declaration affects the element where it’s declared, plus all child elements.

You can also use an xmlns:prefix declaration to define a namespace and associate it with a prefix. Then each element in that namespace must be explicitly declared with the prefix.

<atom:feed xmlns:atom="http://www.w3.org/2005/Atom">  
  <atom:title>dive into mark</atom:title>             
</atom:feed>
  1. The feed element is in the http://www.w3.org/2005/Atom namespace.
  2. The title element is also in the http://www.w3.org/2005/Atom namespace.

As far as an XML parser is concerned, the previous two XML documents are identical. Namespace + element name = XML identity. Prefixes only exist to refer to namespaces, so the actual prefix name (atom:) is irrelevant. The namespaces match, the element names match, the attributes (or lack of attributes) match, and each element’s text content matches, therefore the XML documents are the same.

The Structure Of An Atom Feed

Think of a weblog, or in fact any website with frequently updated content, like CNN.com. The site itself has a title (“CNN.com”), a subtitle (“Breaking News, U.S., World, Weather, Entertainment & Video News”), a last-updated date (“updated 12:43 p.m. EDT, Sat May 16, 2009”), and a list of articles posted at different times. Each article also has a title, a first-published date (and maybe also a last-updated date, if they published a correction or fixed a typo), and a unique URL.

The Atom syndication format is designed to capture all of this information in a standard format. My weblog and CNN.com are wildly different in design, scope, and audience, but they both have the same basic structure. CNN.com has a title; my blog has a title. CNN.com publishes articles; I publish articles.

At the top level is the “root” element, which every Atom feed shares: the <feed> element in the Atom namespace (http://www.w3.org/2005/Atom). ... FIXME

Parsing XML

FIXME

>>> import xml.etree.ElementTree as etree
>>> tree = etree.parse("examples/feed.xml")
>>> root = tree.getroot()
>>> root
<Element {http://www.w3.org/2005/Atom}feed at cd1eb0>

Elements Are Lists

FIXME

>>> root.tag
'{http://www.w3.org/2005/Atom}feed'
>>> len(root)
9
>>> for child in root:
...   print(child)
...
<Element {http://www.w3.org/2005/Atom}title at e2b5d0>
<Element {http://www.w3.org/2005/Atom}subtitle at e2b4e0>
<Element {http://www.w3.org/2005/Atom}id at e2b6c0>
<Element {http://www.w3.org/2005/Atom}updated at e2b6f0>
<Element {http://www.w3.org/2005/Atom}link at e181b0>
<Element {http://www.w3.org/2005/Atom}link at e2b4b0>
<Element {http://www.w3.org/2005/Atom}entry at e2b720>
<Element {http://www.w3.org/2005/Atom}entry at e2b510>
<Element {http://www.w3.org/2005/Atom}entry at e2b750>

Attributes Are Dictonaries

FIXME

>>> root.attrib
{'{http://www.w3.org/XML/1998/namespace}lang': 'en'}
>>> root[4]
<Element {http://www.w3.org/2005/Atom}link at e181b0>
>>> root[4].attrib
{'href': 'http://diveintomark.org/', 'type': 'text/html', 'rel': 'alternate'}
>>> root[3]
<Element {http://www.w3.org/2005/Atom}updated at e2b4e0>
>>> root[3].attrib
{}

Searching For Nodes Within An XML Document

FIXME

>>> tree.findall("{http://www.w3.org/2005/Atom}entry")
[<Element {http://www.w3.org/2005/Atom}entry at e2b4e0>, <Element {http://www.w3.org/2005/Atom}entry at e2b510>, <Element {http://www.w3.org/2005/Atom}entry at e2b540>]

>>> feed_links = tree.findall("{http://www.w3.org/2005/Atom}link")
>>> feed_links
[<Element {http://www.w3.org/2005/Atom}link at e181b0>, <Element {http://www.w3.org/2005/Atom}link at e2b4b0>]
>>> feed_links[0].attrib
{'href': 'http://diveintomark.org/', 'type': 'text/html', 'rel': 'alternate'}
>>> feed_links[1].attrib
{'href': 'http://diveintomark.org/feed/', 'type': 'application/atom+xml', 'rel': 'self'}

>>> all_links = tree.findall("//{http://www.w3.org/2005/Atom}link")
>>> all_links
[<Element {http://www.w3.org/2005/Atom}link at e181b0>, <Element {http://www.w3.org/2005/Atom}link at e2b4b0>, <Element {http://www.w3.org/2005/Atom}link at e2b570>, <Element {http://www.w3.org/2005/Atom}link at e2b480>, <Element {http://www.w3.org/2005/Atom}link at e2b5a0>]
>>> all_links[0].attrib
{'href': 'http://diveintomark.org/', 'type': 'text/html', 'rel': 'alternate'}
>>> all_links[1].attrib
{'href': 'http://diveintomark.org/feed/', 'type': 'application/atom+xml', 'rel': 'self'}
>>> all_links[2].attrib
{'href': 'http://diveintomark.org/archives/2009/03/27/dive-into-history-2009-edition', 'type': 'text/html', 'rel': 'alternate'}
>>> all_links[3].attrib
{'href': 'http://diveintomark.org/archives/2009/03/21/accessibility-is-a-harsh-mistress', 'type': 'text/html', 'rel': 'alternate'}
>>> all_links[4].attrib
{'href': 'http://diveintomark.org/archives/2008/12/18/give-part-1-container-formats', 'type': 'text/html', 'rel': 'alternate'}

Going Further With lxml

FIXME

>>> from lxml import etree
.
.  FIXME (show how it's a drop-in replacement for everything we've done so far)
.

from here on out, use lxml.etree explicitly because these functions are specific to lxml
>>> import lxml.etree
>>> nsmap = {"atom": "http://www.w3.org/2005/Atom"}
>>> tree = lxml.etree.parse("examples/feed.xml")
>>> entries = tree.xpath("//atom:category[@term='accessibility']/..", namespaces=nsmap)
>>> entries
[<Element {http://www.w3.org/2005/Atom}entry at e2b630>]
>>> entry = entries[0]
>>> entry.xpath("./atom:title/text()", namespaces=nsmap)
['Accessibility is a harsh mistress']

Customizing Your XML Parser

FIXME

>>> import lxml.etree
>>> parser = lxml.etree.XMLParser(no_network=True, ns_clean=True, recover=True, remove_blank_text=True, remove_comments=True)
>>> tree = lxml.etree.parse("examples/feed.xml", parser)

Incremental Parsing

FIXME

Generating XML

FIXME

>>> import lxml.etree
>>> new_feed = lxml.etree.Element("{http://www.w3.org/2005/Atom}feed", attrib={"{http://www.w3.org/XML/1998/namespace}lang": "en"})
>>> print(lxml.etree.tounicode(new_feed))
<ns0:feed xmlns:ns0="http://www.w3.org/2005/Atom" xml:lang="en"/>

FIXME

>>> import lxml.etree
>>> new_feed = lxml.etree.Element("feed", nsmap=NSMAP)
>>> print(lxml.etree.tounicode(new_feed))
<feed xmlns="http://www.w3.org/2005/Atom"/>
>>> new_feed.set("{http://www.w3.org/XML/1998/namespace}lang", "en")
>>> print(lxml.etree.tounicode(new_feed))
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"/>

FIXME

>>> title = lxml.etree.SubElement(new_feed, "title", attrib={"type":"html"})
>>> print(lxml.etree.tounicode(new_feed))
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><title type="html"/></feed>
>>> title.text = "dive into mark"
>>> print(lxml.etree.tounicode(new_feed))
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><title type="html">dive into mark</title></feed>
>>> print(lxml.etree.tounicode(new_feed, pretty_print=True))
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  <title type="html">dive into mark</title>
</feed>

Further Reading

© 2001–9 Mark Pilgrim