#!/usr/bin/env python
# -*- coding: utf-8 -*-
version = "0.1.4"

import getopt
import sys
import re

# =============================================================================

def iteritems(obj):
    """iteritems() in python2 and items() in python3"""
    if sys.version[0] == '2':
        return obj.iteritems()
    else:
        return obj.items()

class Dialect:
    shortcuts = {}
    synonyms = {}
    required = {}
    short_tags = ()

class XmlDialect(Dialect):
    shortcuts = {}
    synonyms = {}
    short_tags = ()
    required = {}

class HtmlDialect(Dialect):
    # Constructor, accepts an indent which should come from an Options object
    # so it integrates well with their editing environment.
    def __init__(self, indent=4):
        self.indent = indent

        self.shortcuts = {
            'cc:ie': {
                'opening_tag': '<!--[if IE]>',
                'closing_tag': '<![endif]-->'},
            'cc:ie8': {
                'opening_tag': '<!--[if lte IE 8]>',
                'closing_tag': '<![endif]-->'},
            'cc:ie9': {
                'opening_tag': '<!--[if lte IE 9]>',
                'closing_tag': '<![endif]-->'},
            'cc:noie': {
                'opening_tag': '<!--[if !IE]><!-->',
                'closing_tag': '<!--<![endif]-->'},
            'php:t': {
                'expand': True,
                'opening_tag': '<?php',
                'closing_tag': '?>',
                },
            'erb:p': {
                'opening_tag': '<%= ',
                'closing_tag': ' %>',
                },
            'erb:c': {
                'opening_tag': '%<# ',
                'closing_tag': ' %>',
                },
            'erb:d': {
                'opening_tag': '<% ',
                'closing_tag': ' %>',
                },
            'erb:b': {
                'expand': True,
                'opening_tag' : '<% $2 %>',
                'closing_tag' : '<% end %>',
                },
            'erb:bp': {
                'expand': True,
                'opening_tag' : '<%= $2 %>',
                'closing_tag' : '<% end %>',
                },
            'html:4t': {
                'expand': True,
                'opening_tag':
                    '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n' +
                    '<html lang="en">\n' +
                    '<head>\n' +
                    (' ' * self.indent) + '<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />\n' +
                    (' ' * self.indent) + '<title></title>\n' +
                    '</head>\n' +
                    '<body>',
                'closing_tag':
                    '</body>\n' +
                    '</html>'},
            'html:4s': {
                'expand': True,
                'opening_tag':
                    '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">\n' +
                    '<html lang="en">\n' +
                    '<head>\n' +
                    (' ' * self.indent) + '<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />\n' +
                    (' ' * self.indent) + '<title></title>\n' +
                    '</head>\n' +
                    '<body>',
                'closing_tag':
                    '</body>\n' +
                    '</html>'},
            'html:xt': {
                'expand': True,
                'opening_tag':
                    '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n' +
                    '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">\n' +
                    '<head>\n' +
                    (' ' * self.indent) + '<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />\n' +
                    (' ' * self.indent) + '<title></title>\n' +
                    '</head>\n' +
                    '<body>',
                'closing_tag':
                    '</body>\n' +
                    '</html>'},
            'html:xs': {
                'expand': True,
                'opening_tag':
                    '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n' +
                    '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">\n' +
                    '<head>\n' +
                    (' ' * self.indent) + '<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />\n' +
                    (' ' * self.indent) + '<title></title>\n' +
                    '</head>\n' +
                    '<body>',
                'closing_tag':
                    '</body>\n' +
                    '</html>'},
            'html:xxs': {
                'expand': True,
                'opening_tag':
                    '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n' +
                    '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">\n' +
                    '<head>\n' +
                    (' ' * self.indent) + '<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />\n' +
                    (' ' * self.indent) + '<title></title>\n' +
                    '</head>\n' +
                    '<body>',
                'closing_tag':
                    '</body>\n' +
                    '</html>'},
            'html:5': {
                'expand': True,
                'opening_tag':
                    '<!DOCTYPE html>\n' +
                    '<html lang="en">\n' +
                    '<head>\n' +
                    (' ' * self.indent) + '<meta charset="UTF-8">\n' +
                    (' ' * self.indent) + '<title></title>\n' +
                    '</head>\n' +
                    '<body>',
                'closing_tag':
                    '</body>\n' +
                    '</html>'},
            'input:button': {
                'name': 'input',
                'attributes': { 'class': 'button', 'type': 'button', 'name': '', 'value': '' }
                },
            'input:password': {
                'name': 'input',
                'attributes': { 'class': 'text password', 'type': 'password', 'name': '', 'value': '' }
                },
            'input:radio': {
                'name': 'input',
                'attributes': { 'class': 'radio', 'type': 'radio', 'name': '', 'value': '' }
                },
            'input:checkbox': {
                'name': 'input',
                'attributes': { 'class': 'checkbox', 'type': 'checkbox', 'name': '', 'value': '' }
                },
            'input:file': {
                'name': 'input',
                'attributes': { 'class': 'file', 'type': 'file', 'name': '', 'value': '' }
                },
            'input:text': {
                'name': 'input',
                'attributes': { 'class': 'text', 'type': 'text', 'name': '', 'value': '' }
                },
            'input:submit': {
                'name': 'input',
                'attributes': { 'class': 'submit', 'type': 'submit', 'value': '' }
                },
            'input:hidden': {
                'name': 'input',
                'attributes': { 'type': 'hidden', 'name': '', 'value': '' }
                },
            'script:src': {
                'name': 'script',
                'attributes': { 'src': '' }
                },
            'script:jquery': {
                'name': 'script',
                'attributes': { 'src': 'http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js' }
                },
            'script:jquery2': {
                'name': 'script',
                'attributes': { 'src': 'http://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js' }
                },
            'script:jsapi': {
                'name': 'script',
                'attributes': { 'src': 'http://www.google.com/jsapi' }
                },
            'script:jsapix': {
                'name': 'script',
                'text': '\n    google.load("jquery", "1.3.2");\n    google.setOnLoadCallback(function() {\n        \n    });\n'
                },
            'link:css': {
                'name': 'link',
                'attributes': { 'rel': 'stylesheet', 'type': 'text/css', 'href': '', 'media': 'all' },
                },
            'link:print': {
                'name': 'link',
                'attributes': { 'rel': 'stylesheet', 'type': 'text/css', 'href': '', 'media': 'print' },
                },
            'link:favicon': {
                'name': 'link',
                'attributes': { 'rel': 'shortcut icon', 'type': 'image/x-icon', 'href': '' },
                },
            'link:touch': {
                'name': 'link',
                'attributes': { 'rel': 'apple-touch-icon', 'href': '' },
                },
            'link:rss': {
                'name': 'link',
                'attributes': { 'rel': 'alternate', 'type': 'application/rss+xml', 'title': 'RSS', 'href': '' },
                },
            'link:atom': {
                'name': 'link',
                'attributes': { 'rel': 'alternate', 'type': 'application/atom+xml', 'title': 'Atom', 'href': '' },
                },
            'meta:ieedge': {
                'name': 'meta',
                'attributes': { 'http-equiv': 'X-UA-Compatible', 'content': 'IE=edge' },
                },
            'form:get': {
                'name': 'form',
                'attributes': { 'method': 'get' },
                },
            'form:g': {
                'name': 'form',
                'attributes': { 'method': 'get' },
                },
            'form:post': {
                'name': 'form',
                'attributes': { 'method': 'post' },
                },
            'form:p': {
                'name': 'form',
                'attributes': { 'method': 'post' },
                },
            }
        self.synonyms = {
            'php': 'php:t',
            'checkbox': 'input:checkbox',
            'check': 'input:checkbox',
            'input:c': 'input:checkbox',
            'input:b': 'input:button',
            'input:h': 'input:hidden',
            'hidden': 'input:hidden',
            'submit': 'input:submit',
            'input:s': 'input:submit',
            'radio': 'input:radio',
            'input:r': 'input:radio',
            'text': 'input:text',
            'pass': 'input:password',
            'passwd': 'input:password',
            'password': 'input:password',
            'pw': 'input:password',
            'input': 'input:text',
            'input:t': 'input:text',
            'linkcss': 'link:css',
            'scriptsrc': 'script:src',
            'jquery': 'script:jquery',
            'jsapi': 'script:jsapi',
            'html5': 'html:5',
            'html4': 'html:4s',
            'html4s': 'html:4s',
            'html4t': 'html:4t',
            'xhtml': 'html:xxs',
            'xhtmlt': 'html:xt',
            'xhtmls': 'html:xs',
            'xhtml11': 'html:xxs',
            'opt': 'option',
            'st': 'strong',
            'css': 'style',
            'csss': 'link:css',
            'css:src': 'link:css',
            'csssrc': 'link:css',
            'js': 'script',
            'jss': 'script:src',
            'js:src': 'script:src',
            'jssrc': 'script:src',
            }
        self.short_tags = (
            'area', 'base', 'basefont', 'br', 'embed', 'hr',
            'input', 'img', 'link', 'param', 'meta')
        self.required = {
            'a':      {'href':''},
            'base':   {'href':''},
            'abbr':   {'title': ''},
            'acronym':{'title': ''},
            'bdo':    {'dir': ''},
            'link':   {'rel': 'stylesheet', 'href': ''},
            'style':  {'type': 'text/css'},
            'script': {'type': 'text/javascript'},
            'img':    {'src':'', 'alt':''},
            'iframe': {'src': '', 'frameborder': '0'},
            'embed':  {'src': '', 'type': ''},
            'object': {'data': '', 'type': ''},
            'param':  {'name': '', 'value': ''},
            'form':   {'action': '', 'method': 'post'},
            'input':  {'type': '', 'name': '', 'value': ''},
            'area':   {'shape': '', 'coords': '', 'href': '', 'alt': ''},
            'select': {'name': ''},
            'option': {'value': ''},
            'textarea':{'name': ''},
            'meta':   {'content': ''},
        }
    short_tags = (
        'area', 'base', 'basefont', 'br', 'embed', 'hr',
        'input', 'img', 'link', 'param', 'meta')
    required = {
        'a':      {'href':''},
        'base':   {'href':''},
        'abbr':   {'title': ''},
        'acronym':{'title': ''},
        'bdo':    {'dir': ''},
        'link':   {'rel': 'stylesheet', 'href': ''},
        'style':  {'type': 'text/css'},
        'script': {'type': 'text/javascript'},
        'img':    {'src':'', 'alt':''},
        'iframe': {'src': '', 'frameborder': '0'},
        'embed':  {'src': '', 'type': ''},
        'object': {'data': '', 'type': ''},
        'param':  {'name': '', 'value': ''},
        'form':   {'action': '', 'method': 'post'},
        'input':  {'type': '', 'name': '', 'value': ''},
        'area':   {'shape': '', 'coords': '', 'href': '', 'alt': ''},
        'select': {'name': ''},
        'option': {'value': ''},
        'textarea':{'name': ''},
        'meta':   {'content': ''},
    }

class Parser:
    """The parser.
    """

    # Constructor
    # -------------------------------------------------------------------------

    def __init__(self, options=None, str=''):
        """Constructor.
        """

        self.tokens = []
        self.str = str
        self.options = options
        if self.options.has("xml"):
            self.dialect = XmlDialect()
        else:
            self.dialect = HtmlDialect(int(self.options.options['indent-spaces']))
        self.root = Element(parser=self)
        self.caret = []
        self.caret.append(self.root)
        self._last = []

    # Methods
    # -------------------------------------------------------------------------

    def load_string(self, str):
        """Loads a string to parse.
        """

        self.str = str
        self._tokenize()
        self._parse()

    def render(self):
        """Renders.
        Called by [[Router]].
        """

        # Get the initial render of the root node
        output = self.root.render()

        # Indent by whatever the input is indented with
        indent = re.findall("^[\r\n]*(\s*)", self.str)[0]
        output = indent + output.replace("\n", "\n" + indent)

        # Strip newline if not needed
        if self.options.has("no-last-newline") \
            or self.prefix or self.suffix:
            output = re.sub(r'\n\s*$', '', output)

        # TextMate mode
        if self.options.has("textmate"):
            output = self._textmatify(output)

        return output

    # Protected methods
    # -------------------------------------------------------------------------

    def _textmatify(self, output):
        """Returns a version of the output with TextMate placeholders in it.
        """

        matches = re.findall(r'(></)|("")|(\n\s+)\n|(.|\s)', output)
        output = ''
        n = 1
        for i in matches:
            if i[0]:
                output += '>$%i</' % n
                n += 1
            elif i[1]:
                output += '"$%i"' % n
                n += 1
            elif i[2]:
                output += i[2] + '$%i\n' % n
                n += 1
            elif i[3]:
                output += i[3]
        output += "$0"
        return output

    def _tokenize(self):
        """Tokenizes.
        Initializes [[self.tokens]].
        """

        str = self.str.strip()

        # Find prefix/suffix
        while True:
            match = re.match(r"^(\s*<[^>]+>\s*)", str)
            if match is None: break
            if self.prefix is None: self.prefix = ''
            self.prefix += match.group(0)
            str = str[len(match.group(0)):]

        while True:
            match = re.findall(r"(\s*<[^>]+>[\s\n\r]*)$", str)
            if not match: break
            if self.suffix is None: self.suffix = ''
            self.suffix = match[0] + self.suffix
            str = str[:-len(match[0])]

        # Split by the element separators
        for token in re.split('(<|>|\+(?!\\s*\+|$))', str):
            if token.strip() != '':
                self.tokens.append(Token(token, parser=self))

    def _parse(self):
        """Takes the tokens and does its thing.
        Populates [[self.root]].
        """

        # Carry it over to the root node.
        if self.prefix or self.suffix:
            self.root.prefix = self.prefix
            self.root.suffix = self.suffix
            self.root.depth += 1

        for token in self.tokens:
            if token.type == Token.ELEMENT:
                # Reset the "last elements added" list. We will
                # repopulate this with the new elements added now.
                self._last[:] = []

                # Create [[Element]]s from a [[Token]].
                # They will be created as many as the multiplier specifies,
                # multiplied by how many carets we have
                count = 0
                for caret in self.caret:
                    local_count = 0
                    for i in range(token.multiplier):
                        count += 1
                        local_count += 1
                        new = Element(token, caret,
                                count = count,
                                local_count = local_count,
                                parser = self)
                        self._last.append(new)
                        caret.append(new)

            # For >
            elif token.type == Token.CHILD:
                # The last children added.
                self.caret[:] = self._last

            # For <
            elif token.type == Token.PARENT:
                # If we're the root node, don't do anything
                parent = self.caret[0].parent
                if parent is not None:
                    self.caret[:] = [parent]
        return

    # Properties
    # -------------------------------------------------------------------------

    # Property: dialect
    # The dialect of XML
    dialect = None

    # Property: str
    # The string
    str = ''

    # Property: tokens
    # The list of tokens
    tokens = []

    # Property: options
    # Reference to the [[Options]] instance
    options = None

    # Property: root
    # The root [[Element]] node.
    root = None

    # Property: caret
    # The current insertion point.
    caret = None

    # Property: _last
    # List of the last appended stuff
    _last = None

    # Property: indent
    # Yeah
    indent = ''

    # Property: prefix
    # (String) The trailing tag in the beginning.
    #
    # Description:
    # For instance, in `<div>ul>li</div>`, the `prefix` is `<div>`.
    prefix = ''

    # Property: suffix
    # (string) The trailing tag at the end.
    suffix = ''
    pass

# =============================================================================

class Element:
    """An element.
    """

    def __init__(self, token=None, parent=None, count=None, local_count=None,
                 parser=None, opening_tag=None, closing_tag=None,
                 attributes=None, name=None, text=None):
        """Constructor.

        This is called by ???.

        Description:
        All parameters are optional.

        token       - (Token) The token (required)
        parent      - (Element) Parent element; `None` if root
        count       - (Int) The number to substitute for `&` (e.g., in `li.item-$`)
        local_count - (Int) The number to substitute for `$` (e.g., in `li.item-&`)
        parser      - (Parser) The parser

        attributes  - ...
        name        - ...
        text        - ...
        """

        self.children = []
        self.attributes = {}
        self.parser = parser

        if token is not None:
            # Assumption is that token is of type [[Token]] and is
            # a [[Token.ELEMENT]].
            self.name        = token.name
            self.attributes  = token.attributes.copy()
            self.text        = token.text
            self.populate    = token.populate
            self.expand      = token.expand
            self.opening_tag = token.opening_tag
            self.closing_tag = token.closing_tag

        # `count` can be given. This will substitude & in classname and ID
        if count is not None:
            for key in self.attributes:
                attrib = self.attributes[key]
                attrib = attrib.replace('&', ("%i" % count))
                if local_count is not None:
                    attrib = attrib.replace('$', ("%i" % local_count))
                self.attributes[key] = attrib

        # Copy over from parameters
        if attributes: self.attributes = attributes
        if name:       self.name       = name
        if text:       self.text       = text

        self._fill_attributes()

        self.parent = parent
        if parent is not None:
            self.depth = parent.depth + 1

        if self.populate: self._populate()

    def render(self):
        """Renders the element, along with it's subelements, into HTML code.

        [Grouped under "Rendering methods"]
        """

        output = ""

        try:    tabs = bool(self.parser.options.options['indent-tabs'])
        except: tabs = False

        if tabs:
            spaces = '\t'
        else:
            try:    spaces_count = int(self.parser.options.options['indent-spaces'])
            except: spaces_count = 4
            spaces = ' ' * spaces_count

        indent = self.depth * spaces

        prefix, suffix = ('', '')
        if self.prefix: prefix = self.prefix + "\n"
        if self.suffix: suffix = self.suffix

        # Make the guide from the ID (/#header), or the class if there's no ID (/.item)
        # This is for the start-guide, end-guide and post-tag-guides
        guide_str = ''
        if 'id' in self.attributes:
            guide_str += "#%s" % self.attributes['id']
        elif 'class' in self.attributes:
            guide_str += ".%s" % self.attributes['class'].replace(' ', '.')

        # Build the post-tag guide (e.g., </div><!-- /#header -->),
        # the start guide, and the end guide.
        guide = ''
        start_guide = ''
        end_guide = ''
        if ((self.name == 'div') and
            (('id' in self.attributes) or ('class' in self.attributes))):

            if (self.parser.options.has('post-tag-guides')):
                guide = "<!-- /%s -->" % guide_str

            if (self.parser.options.has('start-guide-format')):
                format = self.parser.options.get('start-guide-format')
                try:    start_guide = format % guide_str
                except: start_guide = (format + " " + guide_str).strip()
                start_guide = "%s<!-- %s -->\n" % (indent, start_guide)

            if (self.parser.options.has('end-guide-format')):
                format = self.parser.options.get('end-guide-format')
                try:    end_guide = format % guide_str
                except: end_guide = (format + " " + guide_str).strip()
                end_guide = "\n%s<!-- %s -->" % (indent, end_guide)

        # Short, self-closing tags (<br />)
        short_tags = self.parser.dialect.short_tags

        # When it should be expanded..
        # (That is, <div>\n...\n</div> or similar -- wherein something must go
        # inside the opening/closing tags)
        if  len(self.children) > 0 \
            or self.expand \
            or prefix or suffix \
            or (self.parser.options.has('expand-divs') and self.name == 'div'):

            for child in self.children:
                output += child.render()

            # For expand divs: if there are no children (that is, `output`
            # is still blank despite above), fill it with a blank line.
            if (output == ''): output = indent + spaces + "\n"

            # If we're a root node and we have a prefix or suffix...
            # (Only the root node can have a prefix or suffix.)
            if prefix or suffix:
                output = "%s%s%s%s%s\n" % \
                    (indent, prefix, output, suffix, guide)

            # Uh..
            elif self.name != '' or \
                self.opening_tag is not None or \
                self.closing_tag is not None:
                output = start_guide + \
                    indent + self.get_opening_tag() + "\n" + \
                    output + \
                    indent + self.get_closing_tag() + \
                    guide + end_guide + "\n"


        # Short, self-closing tags (<br> or <br /> depending on configuration)
        elif self.name in short_tags:
            if self.parser.options.has('no-html5-self-closing'):
                output = "%s<%s />\n" % (indent, self.get_default_tag())
            else:
                output = "%s<%s>\n" % (indent, self.get_default_tag())

        # Tags with text, possibly
        elif self.name != '' or \
            self.opening_tag is not None or \
            self.closing_tag is not None:
            between_tags = self.text
            if self.parser.options.has('open-empty-tags'):
                between_tags += "\n\n" + indent
            output = "%s%s%s%s%s%s%s%s" % \
                (start_guide, indent, self.get_opening_tag(),
                between_tags,
                self.get_closing_tag(),
                guide, end_guide, "\n")

        # Else, it's an empty-named element (like the root). Pass.
        else:
            pass

        return output

    def get_default_tag(self):
        """Returns the opening tag (without brackets).

        Usage:
            element.get_default_tag()

        [Grouped under "Rendering methods"]
        """

        output = '%s' % (self.name)
        for key, value in iteritems(self.attributes):
            output += ' %s="%s"' % (key, value)
        return output

    def get_opening_tag(self):
        if self.opening_tag is None:
            return "<%s>" % self.get_default_tag()
        else:
            return self.opening_tag

    def get_closing_tag(self):
        if self.closing_tag is None:
            return "</%s>" % self.name
        else:
            return self.closing_tag

    def append(self, object):
        """Registers an element as a child of this element.

        Usage:
            element.append(child)

        Description:
        Adds a given element `child` to the children list of this element. It
        will be rendered when [[render()]] is called on the element.

        See also:
        - [[get_last_child()]]

        [Grouped under "Traversion methods"]
        """

        self.children.append(object)

    def get_last_child(self):
        """Returns the last child element which was [[append()]]ed to this element.

        Usage:
            element.get_last_child()

        Description:
        This is the same as using `element.children[-1]`.

        [Grouped under "Traversion methods"]
        """

        return self.children[-1]

    def _populate(self):
        """Expands with default items.

        This is called when the [[populate]] flag is turned on.
        """

        if self.name == 'ul':
            elements = [Element(name='li', parent=self, parser=self.parser)]

        elif self.name == 'dl':
            elements = [
                Element(name='dt', parent=self, parser=self.parser),
                Element(name='dd', parent=self, parser=self.parser)]

        elif self.name == 'table':
            tr = Element(name='tr', parent=self, parser=self.parser)
            td = Element(name='td', parent=tr, parser=self.parser)
            tr.children.append(td)
            elements = [tr]

        else:
            elements = []

        for el in elements:
            self.children.append(el)

    def _fill_attributes(self):
        """Fills default attributes for certain elements.

        Description:
        This is called by the constructor.

        [Protected, grouped under "Protected methods"]
        """

        # Make sure <a>'s have a href, <img>'s have an src, etc.
        required = self.parser.dialect.required

        for element, attribs in iteritems(required):
            if self.name == element:
                for attrib in attribs:
                    if attrib not in self.attributes:
                        self.attributes[attrib] = attribs[attrib]

    # -------------------------------------------------------------------------

    # Property: last_child
    # [Read-only]
    last_child = property(get_last_child)

    # -------------------------------------------------------------------------

    # Property: parent
    # (Element) The parent element.
    parent = None

    # Property: name
    # (String) The name of the element (e.g., `div`)
    name = ''

    # Property: attributes
    # (Dict) The dictionary of attributes (e.g., `{'src': 'image.jpg'}`)
    attributes = None

    # Property: children
    # (List of Elements) The children
    children = None

    # Property: opening_tag
    # (String or None) The opening tag. Optional; will use `name` and
    # `attributes` if this is not given.
    opening_tag = None

    # Property: closing_tag
    # (String or None) The closing tag
    closing_tag = None

    text = ''
    depth = -1
    expand = False
    populate = False
    parser = None

    # Property: prefix
    # Only the root note can have this.
    prefix = None
    suffix = None

# =============================================================================

class Token:
    def __init__(self, str, parser=None):
        """Token.

        Description:
        str   - The string to parse

        In the string `div > ul`, there are 3 tokens. (`div`, `>`, and `ul`)

        For `>`, it will be a `Token` with `type` set to `Token.CHILD`
        """

        self.str = str.strip()
        self.attributes = {}
        self.parser = parser

        # Set the type.
        if self.str == '<':
            self.type = Token.PARENT
        elif self.str == '>':
            self.type = Token.CHILD
        elif self.str == '+':
            self.type = Token.SIBLING
        else:
            self.type = Token.ELEMENT
            self._init_element()

    def _init_element(self):
        """Initializes. Only called if the token is an element token.
        [Private]
        """

        # Get the tag name. Default to DIV if none given.
        name = re.findall('^([\w\-:]*)', self.str)[0]
        if self.parser.options.options['namespaced-elements'] == True:
            name = name.replace('-', ':')

        # Find synonyms through this thesaurus
        synonyms = self.parser.dialect.synonyms
        if name in synonyms.keys():
            name = synonyms[name]

        if ':' in name:
            shortcuts = self.parser.dialect.shortcuts
            if name in shortcuts.keys():
                for key, value in iteritems(shortcuts[name]):
                    setattr(self, key, value)
                if 'html' in name:
                    return
            else:
                self.name = name

        elif (name == ''): self.name = 'div'
        else: self.name = name

        # Look for attributes
        attribs = []
        for attrib in re.findall('\[([^\]]*)\]', self.str):
            attribs.append(attrib)
            self.str = self.str.replace("[" + attrib + "]", "")
        if len(attribs) > 0:
            for attrib in attribs:
                try:    key, value = attrib.split('=', 1)
                except: key, value = attrib, ''
                self.attributes[key] = value

        # Try looking for text
        text = None
        for text in re.findall('\{(.*?)\}(?!\})', self.str):
            self.str = self.str.replace("{" + text + "}", "")
        if text is not None:
            self.text = text

        # Get the class names
        classes = []
        for classname in re.findall('\.([\$a-zA-Z0-9_\-\&]+)', self.str):
            classes.append(classname)
        if len(classes) > 0:
            try:    self.attributes['class']
            except: self.attributes['class'] = ''
            self.attributes['class'] += ' ' + ' '.join(classes)
            self.attributes['class'] = self.attributes['class'].strip()

        # Get the ID
        id = None
        for id in re.findall('#([\$a-zA-Z0-9_\-\&]+)', self.str): pass
        if id is not None:
            self.attributes['id'] = id

        # See if there's a multiplier (e.g., "li*3")
        multiplier = None
        for multiplier in re.findall('\*\s*([0-9]+)', self.str): pass
        if multiplier is not None:
            self.multiplier = int(multiplier)

        # Populate flag (e.g., ul+)
        flags = None
        for flags in re.findall('[\+\!]+$', self.str): pass
        if flags is not None:
            if '+' in flags: self.populate = True
            if '!' in flags: self.expand = True

    def __str__(self):
        return self.str

    str = ''
    parser = None

    # For elements
    # See the properties of `Element` for description on these.
    name = ''
    attributes = None
    multiplier = 1
    expand = False
    populate = False
    text = ''
    opening_tag = None
    closing_tag = None

    # Type
    type = 0
    ELEMENT = 2
    CHILD = 4
    PARENT = 8
    SIBLING = 16

# =============================================================================

class Router:
    """The router.
    """

    # Constructor
    # -------------------------------------------------------------------------

    def __init__(self):
        pass

    # Methods
    # -------------------------------------------------------------------------

    def start(self, options=None, str=None, ret=None):
        if (options):
            self.options = Options(router=self, options=options, argv=None)
        else:
            self.options = Options(router=self, argv=sys.argv[1:], options=None)

        if (self.options.has('help')):
            return self.help()

        elif (self.options.has('version')):
            return self.version()

        else:
            return self.parse(str=str, ret=ret)

    def help(self):
        print("Usage: %s [OPTIONS]" % sys.argv[0])
        print("Expands input into HTML.")
        print("")
        for short, long, info in self.options.cmdline_keys:
            if "Deprecated" in info: continue
            if not short == '': short = '-%s,' % short
            if not long  == '': long  = '--%s' % long.replace("=", "=XXX")

            print("%6s %-25s %s" % (short, long, info))
        print("")
        print("\n".join(self.help_content))

    def version(self):
        print("Uhm, yeah.")

    def parse(self, str=None, ret=None):
        self.parser = Parser(self.options)

        try:
            # Read the files
            if str is not None:
                lines = str
            else:
                lines = sys.stdin.read()

        except KeyboardInterrupt:
            pass

        except:
            sys.stderr.write("Reading failed.\n")
            return

        try:
            self.parser.load_string(lines)
            output = self.parser.render()
            if ret: return output
            sys.stdout.write(output)

        except:
            sys.stderr.write("Parse error. Check your input.\n")
            print(sys.exc_info()[0])
            print(sys.exc_info()[1])

    def exit(self):
        sys.exit()

    help_content = [
        "Please refer to the manual for more information.",
    ]

# =============================================================================

class Options:
    def __init__(self, router, argv, options=None):
        # Init self
        self.router = router

        # `options` can be given as a dict of stuff to preload
        if options:
            for k, v in iteritems(options):
                self.options[k] = v
            return

        # Prepare for getopt()
        short_keys, long_keys = "", []
        for short, long, info in self.cmdline_keys: # 'v', 'version'
            short_keys += short
            long_keys.append(long)

        try:
            getoptions, arguments = getopt.getopt(argv, short_keys, long_keys)

        except getopt.GetoptError:
            err = sys.exc_info()[1]
            sys.stderr.write("Options error: %s\n" % err)
            sys.stderr.write("Try --help for a list of arguments.\n")
            return router.exit()

        # Sort them out into options
        options = {}
        for option in getoptions:
            key, value = option # '--version', ''
            if (value == ''): value = True

            # If the key is long, write it
            if key[0:2] == '--':
                clean_key = key[2:]
                options[clean_key] = value

            # If the key is short, look for the long version of it
            elif key[0:1] == '-':
                for short, long, info in self.cmdline_keys:
                    if short == key[1:]:
                        print(long)
                        options[long] = True

        # Done
        for k, v in iteritems(options):
            self.options[k] = v

    def __getattr__(self, attr):
        return self.get(attr)

    def get(self, attr):
        try:    return self.options[attr]
        except: return None

    def has(self, attr):
        try:    return self.options.has_key(attr)
        except: return False

    options = {
        'indent-spaces': 4,
        'namespaced-elements': False,
    }
    cmdline_keys = [
        ('h', 'help', 'Shows help'),
        ('v', 'version', 'Shows the version'),
        ('', 'no-guides', 'Deprecated'),
        ('', 'post-tag-guides', 'Adds comments at the end of DIV tags'),
        ('', 'textmate', 'Adds snippet info (textmate mode)'),
        ('', 'indent-spaces=', 'Indent spaces'),
        ('', 'indent-tabs', 'Indent with tabs'),
        ('', 'expand-divs', 'Automatically expand divs'),
        ('', 'no-last-newline', 'Skip the trailing newline'),
        ('', 'start-guide-format=', 'To be documented'), # << TODO
        ('', 'end-guide-format=', 'To be documented'),   # << TODO
        ('', 'xml', 'Skip html attribute fillings'),
        ('', 'no-html5-self-closing', 'Use HTML4 <br /> instead of HTML5 <br>'),
        ('', 'open-empty-tags', 'leaves an empty space between empty tags instead of leaving them on one line'),
    ]

    # Property: router
    # Router
    router = 1

# =============================================================================

if __name__ == "__main__":
    z = Router()
    z.start()
