Traversal
*********

Flatland supplies a rich set of tools for working with structured
data.  For this section, we’ll use the following schema as an example.
It is simple yet has a bit of variety in its structure.

   from flatland import Form, Dict, List, String, Integer

   class Annotation(Form):
       """A spot on a 2D surface."""
       title = String
       flags = List.of(Integer)
       location = Dict.of(Integer.named('x'),
                          Integer.named('y'))

   sample_data = {
     'title': 'Interesting Spot',
     'flags': [1, 3, 5],
     'location': {'x': 10, 'y': 20},
   }

   ann1 = Annotation(sample_data, name=u'ann1')


Going Raw
=========

You may not even need to use any of these traversal strategies in your
application.  An element’s "value" is a full & recursive “export” of
its native Python value.  Many times this is sufficient.

   >>> ann1['title']     # ann1 is a flatland structure
   <String u'title'; value=u'Interesting Spot'>
   >>> type(ann1.value)  # but its .value is not
   <type 'dict'>
   >>> ann1.value == sample_data
   True


Python Syntax
=============

Containers elements such as "Form", "Dict", and "List" implement the
Python methods you’d expect for their type.  In most cases you may use
them as if they were "dict" and "list" instances- the difference being
that they always contain "Element" instances.

For example, "Form" and "Dict" can be indexed and used like "dict":

   >>> ann1['title'].value
   u'Interesting Spot'
   >>> ann1['location']['x'].value
   10
   >>> sorted(ann1['location'].items())
   [(u'x', <Integer u'x'; value=10>), (u'y', <Integer u'y'; value=20>)]
   >>> u'title' in ann1
   True

And "List" and similar types can be used like lists:

   >>> ann1['flags']
   [<Integer None; value=1>, <Integer None; value=3>, <Integer None; value=5>]
   >>> ann1['flags'][0].value
   1
   >>> ann1['flags'].value
   [1, 3, 5]
   >>> Integer(3) in ann1['flags']
   True
   >>> 3 in ann1['flags']
   True

The final example is of special note: the value in the expression is
not an "Element".  Most containers will accept native Python values in
these types of expressions and convert them into a temporary "Element"
for the operation.  The example below is equivalent to the example
above.

   >>> ann1['flags'].member_schema(3) in ann1['flags']
   True


Traversal Properties
====================

Elements of all types support a core set of properties that allow
navigation to related elements: "root", "parents", "children", and
"all_children".

   >>> list(ann1['flags'].children)
   [<Integer None; value=1>, <Integer None; value=3>, <Integer None; value=5>]
   >>> list(ann1['title'].children)  # title is a String and has no children
   []
   >>> sorted(el.name for el in ann1.all_children if el.name)
   [u'flags', u'location', u'title', u'x', u'y']
   >>> [el.name for el in ann1['location']['x'].parents]
   [u'location', u'ann1']

Each of these properties (excepting "root") returns an iterator of
elements.


Path Lookups
============

Another option for operating on elements is the "find()" method.
"find" selects elements by *path*, a string that represents one or
more related elements.  Looking up elements by path is a powerful
technique to use when authoring flexible & reusable validators.

   >>> ann1.find('title')   # find 'ann1's child named 'title'
   [<String u'title'; value=u'Interesting Spot'>]

Paths are evaluated relative to the element:

   >>> ann1['location'].find('x')
   [<Integer u'x'; value=10>]

Referencing parents is possible with "..":

   >>> ann1['location']['x'].find('../../title')
   [<String u'title'; value=u'Interesting Spot'>]

Absolute paths begin with a "/".

   >>> ann1['location']['x'].find('/title')
   [<String u'title'; value=u'Interesting Spot'>]

Members of sequences can be selected like any other child (their index
number is their name), or you can use Python-like slicing:

   >>> ann1.find('/flags/0')
   [<Integer None; value=1>]
   >>> ann1.find('/flags[0]')
   [<Integer None; value=1>]

Full Python slice notation is supported as well.  With slices, paths
can select more than one element.

   >>> ann1.find('/flags[:]')
   [<Integer None; value=1>, <Integer None; value=3>, <Integer None; value=5>]
   >>> ann1.find('/flags[1:]')
   [<Integer None; value=3>, <Integer None; value=5>]

Further path operations are permissible after slices.  A richer schema
is needed to illustrate this:

   >>> Points = List.of(List.of(Dict.of(Integer.named('x'),
   ...                                  Integer.named('y'))))
   >>> p = Points([[dict(x=1, y=1), dict(x=2, y=2)],
   ...             [dict(x=3, y=3)]])
   >>> p.find('[:][:]/x')
   [<Integer u'x'; value=1>, <Integer u'x'; value=2>, <Integer u'x'; value=3>]

The equivalent straight Python to select the same set of elements is
quite a bit more wordy.


Path Syntax
-----------

"/" (leading slash)
   Selects the root of the element tree

"element"
   The name of a child element

"element/child"
   "/" separates path segments

".."
   Traverse to the parent element

"element[0]"
   For a sequence container element, select the 0th child

"element[:]"
   Select all children of a container element (need not be a sequence)

"element[1:5]"
   Select a slice of a sequence container’s children
