text: #
  # Licensed to the Apache Software Foundation (ASF) under one
  # or more contributor license agreements.  See the NOTICE file
  # distributed with this work for additional information
  # regarding copyright ownership.  The ASF licenses this file
  # to you under the Apache License, Version 2.0 (the
  # "License"); you may not use this file except in compliance
  # with the License.  You may obtain a copy of the License at
  # 
  #   http://www.apache.org/licenses/LICENSE-2.0
  # 
  # Unless required by applicable law or agreed to in writing,
  # software distributed under the License is distributed on an
  # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  # KIND, either express or implied.  See the License for the
  # specific language governing permissions and limitations
  # under the License.
  #
  
  """
  Simple DOM for both SGML and XML documents.
  """
  
  from __future__ import division
  from __future__ import generators
  from __future__ import nested_scopes
  from __future__ import absolute_import
  
  import sys
  if sys.version_info[0] == 2:
      STRING_TYPES = (basestring,)
  else:
      STRING_TYPES = (str,)
  
  class Container:
  
    def __init__(self):
      self.children = []
  
    def add(self, child):
      child.parent = self
      self.children.append(child)
  
    def extend(self, children):
      for child in children:
        child.parent = self
        self.children.append(child)
  
  class Component:
  
    def __init__(self):
      self.parent = None
  
    def index(self):
      if self.parent:
        return self.parent.children.index(self)
      else:
        return 0
  
    def _line(self, file, line, column):
      self.file = file
      self.line = line
      self.column = column
  
  class DispatchError(Exception):
  
    def __init__(self, scope, f):
      msg = "no such attribute"
  
  class Dispatcher:
  
    def is_type(self, type):
      cls = self
      while cls != None:
        if cls.type == type:
          return True
        cls = cls.base
      return False
  
    def dispatch(self, f, attrs = ""):
      cls = self
      while cls != None:
        if hasattr(f, cls.type):
          return getattr(f, cls.type)(self)
        else:
          cls = cls.base
  
      cls = self
      while cls != None:
        if attrs:
          sep = ", "
          if cls.base == None:
            sep += "or "
        else:
          sep = ""
        attrs += "%s'%s'" % (sep, cls.type)
        cls = cls.base
  
      raise AttributeError("'%s' object has no attribute %s" %
                           (f.__class__.__name__, attrs))
  
  class Node(Container, Component, Dispatcher):
  
    type = "node"
    base = None
  
    def __init__(self):
      Container.__init__(self)
      Component.__init__(self)
      self.query = Query([self])
  
    def __getitem__(self, name):
      for nd in self.query[name]:
        return nd
  
    def text(self):
      from . import transforms
      return self.dispatch(transforms.Text())
  
    def tag(self, name, *attrs, **kwargs):
      t = Tag(name, *attrs, **kwargs)
      self.add(t)
      return t
  
    def data(self, s):
      d = Data(s)
      self.add(d)
      return d
  
    def entity(self, s):
      e = Entity(s)
      self.add(e)
      return e
  
  class Tree(Node):
  
    type = "tree"
    base = Node
  
  class Tag(Node):
  
    type = "tag"
    base = Node
  
    def __init__(self, _name, *attrs, **kwargs):
      Node.__init__(self)
      self.name = _name
      self.attrs = list(attrs)
      self.attrs.extend(kwargs.items())
      self.singleton = False
  
    def get_attr(self, name):
      for k, v in self.attrs:
        if name == k:
          return v
  
    def _idx(self, attr):
      idx = 0
      for k, v in self.attrs:
        if k == attr:
          return idx
        idx += 1
      return None
  
    def set_attr(self, name, value):
      idx = self._idx(name)
      if idx is None:
        self.attrs.append((name, value))
      else:
        self.attrs[idx] = (name, value)
  
    def dispatch(self, f):
      try:
        attr = "do_" + self.name
        method = getattr(f, attr)
      except AttributeError:
        return Dispatcher.dispatch(self, f, "'%s'" % attr)
      return method(self)
  
  class Leaf(Component, Dispatcher):
  
    type = "leaf"
    base = None
  
    def __init__(self, data):
      assert isinstance(data, STRING_TYPES)
      self.data = data
  
  class Data(Leaf):
    type = "data"
    base = Leaf
  
  class Entity(Leaf):
    type = "entity"
    base = Leaf
  
  class Character(Leaf):
    type = "character"
    base = Leaf
  
  class Comment(Leaf):
    type = "comment"
    base = Leaf
  
  ###################
  ## Query Classes ##
  ###########################################################################
  
  class Adder:
  
    def __add__(self, other):
      return Sum(self, other)
  
  class Sum(Adder):
  
    def __init__(self, left, right):
      self.left = left
      self.right = right
  
    def __iter__(self):
      for x in self.left:
        yield x
      for x in self.right:
        yield x
  
  class View(Adder):
  
    def __init__(self, source):
      self.source = source
  
  class Filter(View):
  
    def __init__(self, predicate, source):
      View.__init__(self, source)
      self.predicate = predicate
  
    def __iter__(self):
      for nd in self.source:
        if self.predicate(nd): yield nd
  
  class Flatten(View):
  
    def __iter__(self):
      sources = [iter(self.source)]
      while sources:
        try:
          nd = next(sources[-1])
          if isinstance(nd, Tree):
            sources.append(iter(nd.children))
          else:
            yield nd
        except StopIteration:
          sources.pop()
  
  class Children(View):
  
    def __iter__(self):
      for nd in self.source:
        for child in nd.children:
          yield child
  
  class Attributes(View):
  
    def __iter__(self):
      for nd in self.source:
        for a in nd.attrs:
          yield a
  
  class Values(View):
  
    def __iter__(self):
      for name, value in self.source:
        yield value
  
  def flatten_path(path):
    if isinstance(path, STRING_TYPES):
      for part in path.split("/"):
        yield part
    elif callable(path):
      yield path
    else:
      for p in path:
        for fp in flatten_path(p):
          yield fp
  
  class Query(View):
  
    def __iter__(self):
      for nd in self.source:
        yield nd
  
    def __getitem__(self, path):
      query = self.source
      for p in flatten_path(path):
        if callable(p):
          select = Query
          pred = p
          source = query
        elif isinstance(p, STRING_TYPES):
          if p[0] == "@":
            select = Values
            pred = lambda x, n=p[1:]: x[0] == n
            source = Attributes(query)
          elif p[0] == "#":
            select = Query
            pred = lambda x, t=p[1:]: x.is_type(t)
            source = Children(query)
          else:
            select = Query
            pred = lambda x, n=p: isinstance(x, Tag) and x.name == n
            source = Flatten(Children(query))
        else:
          raise ValueError(p)
        query = select(Filter(pred, source))
  
      return query
  