# Developing with YAXL

## Introduction

Here are the beginnings of a tutorial for YAXL.

	* [Reading XML](#reading_xml)
	* [Creating XML](#creating_xml)
	* [Manipulating `Element`s](#manipulating_xml)
	* [Writing XML](#writing_xml)

<a name="reading_xml" />
## Reading XML

Use the `parse` function to obtain a hierarchy of `Element` instances.

	>>> import yaxl
	>>> x = yaxl.parse("""
	... 	<person id="1234">
	... 		<name>Bob Smith</name>
	... 		<age>200</age>
	... 	</person>
	... """)
	>>> x
	<person id="1234"><name>Bob Smith</name><age>200</age></person>
	>>> x['id']
	'1234'
	>>> x[0].text
	u'Bob Smith'
	>>> x.children[0].text
	u'Bob Smith'
	>>> x[0]
	<name>Bob Smith</name>
	>>> x[1]
	<age>200</age>

#### Valid sources

The `parse` method is very versatile: it accepts a file path, a file-like object, a URL or a string as the source from which to load `Element`s.

This source can contain either a full document or a well-formed XML fragment.

<a name="creating_xml" />
## Creating new elements

You can create new elements by using the `Element` constructor or the `append` method of an existing `Element`. Both accept a name for the element (which may contain a properly bound [namespace prefix](#xml_namespaces)), a `dict` of attributes (all non-string values are converted to strings) and a `text` parameter that sets the text node contents of the element being created. Both return the newly created element so you can chain calls to them.

	>>> from yaxl import *
	>>> person = Element('person', {'id': 1234})
	>>> person.append('name', text='Bob Smith')
	<name>Bob Smith</name>
	>>> person
	<person id="1234"><name>Bob Smith</name></person>
	>>> person.append('age').text = 200
	>>> person
	<person id="1234"><name>Bob Smith</name><age>200</age></person>
	>>> person.append('address').append('street', text='Coconut Grove')
	<street>Coconut Grove</street>
	>>> person[0]
	<name>Bob Smith</name>
	>>> person[1]
	<age>200</age>
	>>> person[2]
	<address><street>Coconut Grove</street></address>
	>>> person[2][0]
	<street>Coconut Grove</street>
	>>> person[2][0].text
	u'Coconut Grove'

You can also `append` an element to another:

	>>> from yaxl import *
	>>> person = Element('person', {'id': '1234'})
	>>> age = Element('age', text=200)
	>>> person.append(age)
	<age>200</age>
	>>> person
	<person id="1234"><age>200</age></person>

Note that trying to create a new element whose name uses an unbound [namespace prefix](#xml_namespaces) will raise an `Exception`.

<a name="manipulating_xml" />
## Manipulating `Element`s

	* [String contents](#string_contents)
	* [Locating elements](#locating_elements)
	* [XML Attributes](#xml_attributes)
	* [Element names](#element_qname)
	* [XML Namespaces](#xml_namespaces)

<a name="string_contents" />
### String contents

The contents of an element are accessible via the `text` property. As a shortcut, the string value of an element is considered to be the same as the `text` property or equal to the empty string if no `text` property exists for that element. Note that element contents are automatically converted to unicode.

	>>> from yaxl import *
	>>> x = Element('x', text='something')
	>>> x.text
	u'something'
	>>> '%s' % x
	'something'
	>>> str(x)
	'something'
	>>> x
	<x>something</x>

You can also append strings to an element's contents:

	>>> x = Element('x', text='Hello')
	>>> x += ', World!'
	>>> x.text
	u'Hello, World!'

<a name="locating_elements" />
### Locating elements

You can access the elements in a document (or any sub-tree) in three ways.

	* accessing the `children` property of the element
	* indexing the element itself
	* calling the element or calling `select` on the element to evaluate an XPath expression

#### The `children`

Each element has a `children` property that is a list of all its children.

	>>> from yaxl import *
	>>> x = Element('x')
	>>> y = x.append('y')
	>>> z = x.append('z')
	>>> x.children
	[<y />, <z />]

#### Indexing the element

Indexing the element like a list is a shortcut for indexing the list of an element's children.

	>>> from yaxl import *
	>>> x = Element('x')
	>>> y = x.append('y')
	>>> z = x.append('z')
	>>> x[0]
	<y />
	>>> x[1]
	<z />

#### Using `select` and XPath

You can also use XPath to retrieve elements' children and attributes. The `select` method on an `Element` allows you to execute arbitrary XPath expressions using that `Element` as the execution context. As a shortcut, you can "call" the element!

	>>> from yaxl import *
	>>> x = Element('x')
	>>> y = x.append('y')
	>>> z = y.append('z')
	>>> x.select('z')
	>>> x.select('y')
	<y><z /></y>
	>>> x.select('/')
	<x><y><z /></y></x>
	>>> x('//z')
	<z />

Note that there is no difference between using the `select` method and "calling" an element

	>>> from yaxl import *
	>>> x = parse("""
	... 	<table>
	... 		<tr>
	... 			<td>Something</td>
	... 			<td>Something Else</td>
	... 		</tr>
	... 		<tr>
	... 			<td>Another something</td>
	... 			<td>Another something else</td>
	... 		</tr>
	... 	</table>
	... """)
	>>> x
	<table><tr><td>Something</td><td>Something Else</td></tr><tr><td>Another something</td><td>Another something else</td></tr></table>
	>>> x[0]
	<tr><td>Something</td><td>Something Else</td></tr>
	>>> x[0].qname
	u'tr'
	>>> x[1]
	<tr><td>Another something</td><td>Another something else</td></tr>
	>>> x[0][0].text
	u'Something'
	>>> x[0][0]
	<td>Something</td>
	>>> x('tr/td')
	(<td>Something</td>, <td>Something Else</td>, <td>Another something</td>, <td>Another something else</td>)
	>>> x('tr')
	(<tr><td>Something</td><td>Something Else</td></tr>, <tr><td>Another something</td><td>Another something else</td></tr>)
	>>> x('/')
	<table><tr><td>Something</td><td>Something Else</td></tr><tr><td>Another something</td><td>Another something else</td></tr></table>
	>>> x('/tr')
	(<tr><td>Something</td><td>Something Else</td></tr>, <tr><td>Another something</td><td>Another something else</td></tr>)
	>>> x.children
	[<tr><td>Something</td><td>Something Else</td></tr>, <tr><td>Another something</td><td>Another something else</td></tr>]
	>>> x.children[0]
	<tr><td>Something</td><td>Something Else</td></tr>
	>>> x.children[1].children
	[<td>Another something</td>, <td>Another something else</td>]

<a name="xml_attributes" />
### XML Attributes

You can access all of an element's attributes via the `attributes` property of the element.

	>>> from yaxl import *
	>>> x = Element('t', {'id': 1234, 'name': 'John Doe'})
	>>> x['id']
	'1234'
	>>> x['id'] = 5
	>>> x
	<t id="5" name="John Doe" />
	>>> x['name'] = 17
	>>> x
	<t id="5" name="17" />
	
Note that trying to modify an attribute whose qname uses an unbound namespace prefix will raise an `Exception`.

	>>> from yaxl import *
	>>> x = Element('t', {'id': 1234, 'name': 'John Doe'})
	>>> x['test:x'] = 5
	Traceback (most recent call last):
		...
	UndeclaredNamespaceException: test was not declared prior to use with localname x was not bound to a URI before use

<a name="element_qname" />
### Element names

XML elements are named by a QName property. YAXL `Element`s have an attribute called `qname` that simulates the QName property. It does so by `join`ing its `ns` property (the namespace prefix the element is defined in) and its `localname` property (i.e. the local name of the element in that namespace).

	>>> from yaxl import *
	>>> x = Element('x')
	>>> x
	<x />
	>>> x.qname
	'x'
	>>> x.ns
	>>> x.localname
	'x'
	>>> x['xmlns:ex'] = 'http://example.org'
	>>> x.ns = 'ex'
	>>> x.localname = 'mytag'
	>>> x
	<ex:mytag xmlns:ex="http://example.org" />
	>>> x['xmlns:ex2'] = 'http://example2.org'
	>>> x.qname = 'ex2:anothertag'
	>>> x
	<ex2:anothertag xmlns:ex2="http://example2.org" />
	
Although setting an element's `qname` property to a value with an un-mapped namespace prefix normally raises an `UndeclaredNamespaceException`, you can use namespaces mapped in the `namespaces` keyword parameter of calls to the `Element` constructor and the `append` method.

<a name="xml_namespaces" />
### XML Namespaces

In order to use a namespace in an element you need to first map the namespace's prefix to its URL. Elements "inherit" the namespaces declared in their parents. To map a new namespace in an element, set an `xmlns:*` attribute to the URI to which the prefix should be bound.

	>>> from yaxl import *
	>>> x = Element('t', {'id': 1234, 'name': 'John Doe'})
	>>> x['xmlns:test'] = 'http://example.org'
	>>> x['test:x'] = 5
	>>> x
	<t xmlns:test="http://example.org" test:x="5" id="1234" name="John Doe" />

The default namespace of each element is specified by its `xmlns` attribute which is normally unset. Setting this attribute will change the default namespace of the element.

	>>> from yaxl import *
	>>> x = Element('x')
	>>> x['xmlns']
	>>> x
	<x />
	>>> x['xmlns'] = 'http://example.org'
	>>> x
	<x xmlns="http://example.org" />

The `xml` prefix is mapped by default to [http://www.w3.org/XML/1998/namespace](http://www.w3.org/XML/1998/namespace).

	>>> from yaxl import *
	>>> x = Element('x')
	>>> x['xmlns:xml']
	'http://www.w3.org/XML/1998/namespace'

<a name="writing_xml" />
## Writing XML

Use the `repr` function, and the `asdoc` and `write` methods to output XML from a tree of `Element`s.

### Output methods
#### The `repr` function

Using `repr` on an `Element` returns an XML representation of the element. It is the method used by default in the Python interpreter so evaluating an `Element` at the prompt will print out it's XML representation.

	>>> from yaxl import *
	>>> x = Element('x')
	>>> x += 'This is a text'
	>>> x
	<x>This is a text</x>

The only downside to this output method is that it returns an XML fragment and not a well-formed document.

#### The `asdoc` method

You can retrieve a well-formed XML document using an `Element` as the root with the `asdoc` method.

	>>> from yaxl import *
	>>> x = Element('x')
	>>> x.asdoc()
	'<?xml version="1.0" encoding="UTF8"?><x />'
	
As you can see, the default encoding for XML documents produced by the `asdoc` method is `UTF8`.

#### The `write` method

The `write` method can be used for output to files or other file-like objects. It will use the `write` method on any object supplied to output it's `Element`.

	>>> from StringIO import StringIO
	>>> from yaxl import *
	>>> io = StringIO()
	>>> x = Element('x')
	>>> x.write(io)
	>>> io.getvalue()
	'<x />'

The `write` method, like the `repr` function, outputs an XML fragment, not a valid document. It can be used, however, when building XML files programmatically (e.g. when customizing processing instructions).

<a name="xml_doc_encoding" />
### Encoding

To set a document's encoding, pass an `encoding` keyword parameter to any of the `Element` output methods (`__repr__`, `asdoc`, `write`).

	>>> from yaxl import *
	>>> x = Element('x')
	>>> x.text = 'text of mine'
	>>> x.asdoc(encoding='iso-8859-2')
	'<?xml version="1.0" encoding="iso-8859-2"?><x>text of mine</x>'
	
Remember, by default, all documents are encoded in UTF8.