# -*- coding: iso-8859-15 -*-


# Glasnost
# By: Odile Bénassy <obenassy@entrouvert.com>
#     Romain Chantereau <rchantereau@entrouvert.com>
#     Nicolas Clapiès <nclapies@easter-eggs.org>
#     Pierre-Antoine Dejace <padejace@entrouvert.be>
#     Thierry Dulieu <tdulieu@easter-eggs.com>
#     Florent Monnier <monnier@codelutin.com>
#     Cédric Musso <cmusso@easter-eggs.org>
#     Frédéric Péters <fpeters@entrouvert.be>
#     Benjamin Poussin <poussin@codelutin.com>
#     Emmanuel Raviart <eraviart@entrouvert.com>
#     Sébastien Régnier <regnier@codelutin.com>
#     Emmanuel Saracco <esaracco@easter-eggs.com>
#
# Copyright (C) 2000, 2001 Easter-eggs & Emmanuel Raviart
# Copyright (C) 2002 Odile Bénassy, Code Lutin, Thierry Dulieu, Easter-eggs,
#     Entr'ouvert, Frédéric Péters, Benjamin Poussin, Emmanuel Raviart,
#     Emmanuel Saracco & Théridion
# Copyright (C) 2003 Odile Bénassy, Romain Chantereau, Nicolas Clapiès,
#     Code Lutin, Pierre-Antoine Dejace, Thierry Dulieu, Easter-eggs,
#     Entr'ouvert, Florent Monnier, Cédric Musso, Ouvaton, Frédéric Péters,
#     Benjamin Poussin, Rodolphe Quiédeville, Emmanuel Raviart, Sébastien
#     Régnier, Emmanuel Saracco, Théridion & Vecam
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.


__doc__ = """XHTML Generator (not Glasnost specific)"""

__version__ = '$Revision$'[11:-2]


import types
import urllib

import context
import tools_new as commonTools


class asIs:
    children = None
    inline = 1

    def __init__(self, *children):
        if children:
            self.children = list(children)

    def __iadd__(self, child):
        self.append(child)
        return self

    def __len__(self):
        return len(str(self))

    def __nonzero__(self):
        if self.children:
            return 1
        else:
            return 0

    def __str__(self):
        return self.getAsIs()

    def append(self, child):
        if self.children is None:
            self.children = []
        self.children.append(child)

    def getAsIs(self, **keywords):
        if not self.children:
            return ''
        else:
            return ''.join([getAsIs(child, **keywords)
                            for child in self.children])

    def getAsXml(self, parent = None, indent = 0, **keywords):
        return self.getAsIs(**keywords)

    def isInline(self):
        return self.inline


class Character:
    name = None

    def __len__(self):
        return len(str(self))

    def __nonzero__(self):
        return 1

    def __str__(self):
        return self.getAsXml()

    def getAsIs(self, **keywords):
        return self.getAsXml(**keywords)

    def getAsXml(self, parent = None, indent = 0, **keywords):
        if parent is not None and parent.preserveSpacing:
            return '&%s;' % self.name
        else:
            return """\
%(spacing)s&%(name)s;
""" % {
                    'name': self.name,
                    'spacing': '  ' * indent,
                    }

    def isInline(self):
        return 1


class array:
    children = None

    def __init__(self, *children):
        if children:
            self.children = list(children)

    def __iadd__(self, child):
        self.append(child)
        return self

    def __len__(self):
        return len(str(self))

    def __nonzero__(self):
        if self.children:
            return 1
        else:
            return 0

    def __str__(self):
        return self.getAsXml()

    def append(self, child):
        if self.children is None:
            self.children = []
        self.children.append(child)

    def getAsIs(self, **keywords):
        if not self.children:
            return ''
        else:
            return ''.join([ getAsIs(child, **keywords)
                             for child in self.children])

    def getAsXml(self, parent = None, indent = 0, **keywords):
        if not self.children:
            return ''
        else:
            return ''.join([
                getAsXml(child, parent = parent, indent = indent, **keywords)
                for child in self.children])

    def isInline(self):
        if self.children:
            for child in self.children:
                if not isInline(child):
                    return 0
        return 1


class Tag:
    children = None
    emptyElement = 0 # Can the tag use empty tag shorthand?
    inline = 1 # Should the tag be enclosed in span (when inline is true) or
               # div?
    name = None
    preserveSpacing = 0
    inlineElements = 0 # Should the tag accept only inline elements (no block
                       # level ones).
    typoEnhancements = 0

    def __init__(self, *children, **attributes):
        if children:
            assert not attributes
            self.children = list(children)
        for name, value in attributes.items():
            if name[0] == '_':
                name = name[1:]
            setattr(self, 'attribute_' + name, value)

    def __call__(self, *children):
        assert self.children is None
        self.children = list(children)
        return self

    def __iadd__(self, child):
        self.append(child)
        return self

    def __len__(self):
        return len(str(self))

    def __nonzero__(self):
        return 1

    def __str__(self):
        return self.getAsXml()

    def append(self, child):
        if self.children is None:
            self.children = []
        self.children.append(child)

    def getAsIs(self, **keywords):
        return self.getAsXml(**keywords)

    def getAsXml(self, parent = None, indent = 0, **keywords):
        attributes = ' '.join([
            '%s="%s"' % (name, getAsXmlAttributeValue(value, tag = self,
                                                      **keywords))
            for name, value in self.getAttributes()])
        if attributes:
            attributes = ' ' + attributes
        if not self.children:
            children = ''
        else:
            children = ''.join([
                self.getChildAsXml(child, parent = self, indent = indent + 1,
                                   **keywords)
                for child in self.children])
        if not children and self.emptyElement:
            if parent is not None and parent.preserveSpacing:
                return '<%(tag)s%(attributes)s />' % {
                    'attributes': attributes,
                    'tag': self.name,
                    }
            else:
                return """\
%(spacing)s<%(tag)s%(attributes)s />
""" % {
                    'attributes': attributes,
                    'spacing': '  ' * indent,
                    'tag': self.name,
                    }
        else:
            if parent is not None and parent.preserveSpacing:
                if self.preserveSpacing:
                    return '<%(tag)s%(attributes)s>%(children)s</%(tag)s>' % {
                        'attributes': attributes,
                        'children': children,
                        'tag': self.name,
                        }
                else:
                    return """\
<%(tag)s%(attributes)s>
%(children)s\
%(spacing)s</%(tag)s>\
""" % {
                        'attributes': attributes,
                        'children': children,
                        'spacing': '  ' * indent,
                        'tag': self.name,
                    }
            elif self.preserveSpacing:
                return """\
%(spacing)s<%(tag)s%(attributes)s>%(children)s</%(tag)s>
""" % {
                    'attributes': attributes,
                    'children': children,
                    'spacing': '  ' * indent,
                    'tag': self.name,
                    }
            else:
                return """\
%(spacing)s<%(tag)s%(attributes)s>
%(children)s\
%(spacing)s</%(tag)s>
""" % {
                    'attributes': attributes,
                    'children': children,
                    'spacing': '  ' * indent,
                    'tag': self.name,
                    }

    def getAttribute(self, name):
        if name[0] == '_':
            name = name[1:]
        try:
            return getattr(self, 'attribute_' + name)
        except KeyError:
            return ''

    def getAttributeNames(self):
        baseClasses = self.getC3ClassLinearization()
        names = [ name
                 for name in self.__dict__.keys()
                 if name.startswith('attribute_')]
        for baseClass in baseClasses:
            for name in baseClass.__dict__.keys():
                if name.startswith('attribute_') and name not in names:
                    names.append(name)
        return [ name[10:]
                 for name in names
                 if getattr(self, name) is not None]

    def getAttributes(self):
        names = self.getAttributeNames()
        names.sort()
        return [ (name, getattr(self, 'attribute_' + name))
                 for name in names]

    def getC3ClassLinearization(self):
        return commonTools.getC3ClassLinearization(self.__class__)

    def getChildAsXml(self, child, **keywords):
        return getAsXml(child, **keywords)

    def getPython22ClassLinearization(self):
        baseClasses = []
        commonTools.buildPython22ClassLinearization(
            self.__class__, baseClasses)
        return baseClasses

    def isInline(self):
        if not self.inline:
            return 0
        if self.children:
            for child in self.children:
                if not isInline(child):
                    return 0
        return 1

    def setAttribute(self, name, value):
        if name[0] == '_':
            name = name[1:]
        setattr(self, 'attribute_' + name, value)
        if value is None:
            delattr(self, 'attribute_' + name)


class urlCommon:
    hostName = None
    port = None # Must be a string, not a integer.
    protocol = None

    def __len__(self):
        return len(str(self))

    def __nonzero__(self):
        return 1

    def __str__(self):
        return self.getAsUrl()

    def getAsIs(self, **keywords):
        return self.getAsUrl(**keywords)

    def getAsXmlAttributeValue(self, tag = None, **keywords):
        return convertStringToXmlAttributeValue(self.getAsUrl(**keywords))


class httpUrl(urlCommon):
    fragment = None
    parameters = None
    path = None
    # protocol = http or https
    query = None

    def __init__(self, protocol = None, hostNameAndPort = None,
                 hostName = None, port = None, path = None, parameters = None,
                 query = None, fragment = None):
        if protocol is not None:
            self.protocol = protocol
        else:
            httpProtocol = context.getVar('httpProtocol')
            if httpProtocol is not None:
                self.protocol = httpProtocol
        if hostNameAndPort is not None:
            infos = hostNameAndPort.split(':', 1)
            hostName = infos[0]
            if len(infos) < 2:
                port = None
            else:
                port = infos[1]
        if hostName is not None:
            self.hostName = hostName
        else:
            httpHostName = context.getVar('httpHostName')
            if httpHostName is not None:
                self.hostName = httpHostName
        if port is not None:
            self.port = port
        else:
            httpPort = context.getVar('httpPort')
            if httpPort is not None:
                self.port = httpPort
        if path is not None:
            self.path = path
            assert self.path
            if self.path[0] != '/':
                self.path = '/' + self.path
        else:
            httpPath = context.getVar('httpPath')
            if httpPath is not None:
                self.path = httpPath
                assert self.path
                if self.path[0] != '/':
                    self.path = '/' + self.path
        if parameters is not None:
            self.parameters = parameters
        if query is not None:
            self.query = query
        if fragment is not None:
            self.fragment = fragment

    def add(self, name, value):
        setattr(self, 'argument_' + name, value)
        return self

    def getArgumentNames(self):
        baseClasses = self.getC3ClassLinearization()
        names = [ name
                 for name in self.__dict__.keys()
                 if name.startswith('argument_')]
        for baseClass in baseClasses:
            for name in baseClass.__dict__.keys():
                if name.startswith('argument_') and name not in names:
                    names.append(name)
        return [ name[9:]
                 for name in names
                 if getattr(self, name) is not None]

    def getArguments(self):
        names = self.getArgumentNames()
        names.sort()
        return [ (name, getattr(self, 'argument_' + name))
                 for name in names]

    def getAsAbsoluteUrl(self, **keywords):
        if self.protocol is not None:
            protocol = self.protocol
        else:
            httpProtocol = context.getVar('httpProtocol')
            if httpProtocol is not None:
                protocol = httpProtocol
            else:
                protocol = 'http'
        if self.hostName is not None:
            hostName = self.hostName
        else:
            httpHostName = context.getVar('httpHostName')
            if httpHostName is not None:
                hostName = httpHostName
            else:
                hostName = 'localhost'
        if self.port is not None:
            port = self.port
        else:
            httpPort = context.getVar('httpPort')
            if httpPort is not None:
                port = httpPort
            else:
                port = None # Default http or https port.
        pathAndQuery = self.getAsRelativeUrl()
        if port:
            return '%s://%s:%s%s' % (protocol, hostName, port, pathAndQuery)
        else:
            return '%s://%s%s' % (protocol, hostName, pathAndQuery)

    def getAsRelativeUrl(self, **keywords):
        relativeUrl = urllib.quote(self.getPath(**keywords))
        if self.parameters:
            relativeUrl = '%s;%s' % (relativeUrl, self.parameters)
        expandedArguments = []
        for name, value in self.getArguments():
            if type(value) in [types.ListType, types.TupleType]:
                for item in value:
                    expandedArguments.append(
                            '%s=%s' % (name, urllib.quote(str(item))))
            else:
                expandedArguments.append(
                        '%s=%s' % (name, urllib.quote(str(value))))
        query = '&'.join(expandedArguments)
        if self.query and query:
            query = '%s&%s' % (self.query, query)
        elif self.query:
            query = self.query
        if query:
            relativeUrl = '%s?%s' % (relativeUrl, query)
        if self.fragment:
            relativeUrl = '%s#%s' % (relativeUrl, self.fragment)
        return relativeUrl

    def getAsIs(self, **keywords):
        return self.getAsUrl(**keywords)

    def getAsUrl(self, **keywords):
        httpProtocol = context.getVar('httpProtocol')
        httpHostName = context.getVar('httpHostName')
        httpPort = context.getVar('httpPort')
        if (self.protocol is None or self.protocol == httpProtocol) \
                and (self.hostName is None or self.hostName == httpHostName) \
                and (self.port is None or self.port == httpPort):
            return self.getAsRelativeUrl(**keywords)
        else:
            return self.getAsAbsoluteUrl(**keywords)

    def getC3ClassLinearization(self):
        return commonTools.getC3ClassLinearization(self.__class__)

    def getPath(self, **keywords):
        httpPath = context.getVar('httpPath')
        if self.path is not None:
            path = self.path
        elif httpPath is not None:
            path = httpPath
        else:
            path = '/'
        return path

    def getPython22ClassLinearization(self):
        baseClasses = []
        commonTools.buildPython22ClassLinearization(
            self.__class__, baseClasses)
        return baseClasses


# Character entity references.


class Nbsp(Character):
    name = 'nbsp'
nbsp = Nbsp()


# Tags.

class a(Tag):
    name = 'a'
    preserveSpacing = 1

class body(Tag):
    name = 'body'

class br(Tag):
    emptyElement = 1
    name = 'br'

class button(Tag):
    name = 'button'
    preserveSpacing = 1

class code(Tag):
    name = 'code'
    preserveSpacing = 1

class col(Tag):
    emptyElement = 1
    name = 'col'

class colgroup(Tag):
    name = 'colgroup'

class div(Tag):
    inline = 0
    name = 'div'
    preserveSpacing = 0

class em(Tag):
    name = 'em'
    preserveSpacing = 1

class fieldset(Tag):
    inline = 0
    name = 'fieldset'

class form(Tag):
    name = 'form'

class h1(Tag):
    inlineElements = 1
    name = 'h1'
    preserveSpacing = 1

class h2(Tag):
    inlineElements = 1
    name = 'h2'
    preserveSpacing = 1

class h3(Tag):
    inlineElements = 1
    name = 'h3'
    preserveSpacing = 1

class h4(Tag):
    inlineElements = 1
    name = 'h4'
    preserveSpacing = 1

class h5(Tag):
    inlineElements = 1
    name = 'h5'
    preserveSpacing = 1

class h6(Tag):
    inlineElements = 1
    name = 'h6'
    preserveSpacing = 1

class head(Tag):
    name = 'head'

class html(Tag):
    name = 'html'

class hr(Tag):
    emptyElement = 1
    name = 'hr'

class img(Tag):
    emptyElement = 1
    name = 'img'

class input(Tag):
    emptyElement = 1
    name = 'input'

class label(Tag):
    name = 'label'
    preserveSpacing = 1

class li(Tag):
    name = 'li'

class optgroup(Tag):
    name = 'optgroup'
    preserveSpacing = 1

class option(Tag):
    name = 'option'
    preserveSpacing = 1
    typoEnhancements = 1

class p(Tag):
    inline = 0
    name = 'p'
    preserveSpacing = 0
    typoEnhancements = 1

class pre(Tag):
    inline = 0
    name = 'pre'
    preserveSpacing = 1

class script(Tag):
    name = 'script'
    preserveSpacing = 1

class select(Tag):
    name = 'select'

class span(Tag):
    name = 'span'
    inlineElements = 1
    preserveSpacing = 1

class strong(Tag):
    name = 'strong'
    preserveSpacing = 1

class caption(Tag):
    name = 'caption'
    preserveSpacing = 1

class ol(Tag):
    inline = 0
    name = 'ol'

class table(Tag):
    inline = 0
    name = 'table'

class td(Tag):
    name = 'td'
    preserveSpacing = 1

class th(Tag):
    name = 'th'
    preserveSpacing = 1

class thead(Tag):
    name = 'thead'
    preserveSpacing = 1

class tbody(Tag):
    name = 'tbody'
    preserveSpacing = 1

class tfoot(Tag):
    name = 'tfoot'
    preserveSpacing = 1

class textarea(Tag):
    name = 'textarea'
    preserveSpacing = 1

class title(Tag):
    name = 'title'
    preserveSpacing = 1

class tr(Tag):
    name = 'tr'

class tt(Tag):
    name = 'tt'
    preserveSpacing = 1

class ul(Tag):
    inline = 0
    name = 'ul'


def convertStringToXml(s):
    s = s.replace('&', '&amp;')
    s = s.replace('<', '&lt;')
    return s


def convertStringToXmlAttributeValue(s):
    return s.replace(
            '&', '&amp;').replace(
            '"', '&quot;').replace(
            '\'', '&apos;').replace(
            '<', '&lt;').replace(
            '>', '&gt;')


def enclose(object, **enclosingAttributes):
    if not object:
        return None
    if isInline(object):
        return span(**enclosingAttributes)(object)
    else:
        return div(**enclosingAttributes)(object)


def isInline(object):
    if not object:
        return 1
    elif type(object) == types.InstanceType and hasattr(object, 'isInline'):
        return object.isInline()
    elif type(object) in [types.ListType, types.TupleType]:
        for child in object:
            if not isInline(object):
                return 0
        return 1
    else:
        return 1
    

def enhanceTypo(s):
    # typographic junkie
    #   idea from: http://daringfireball.net/projects/smartypants/
    s = s.replace('...', '&#8230;')   # ellipsis (...)
    s = s.replace(' -- ', ' &#8212; ')  # em-dash
    s = s.replace('(c)', '&copy; ')  # copyright symbol
    return s


def getAsIs(object, **keywords):
    if object is None:
        return ''
    elif type(object) in [types.ListType, types.TupleType]:
        return ''.join([
            getAsIs(item, **keywords)
            for item in object])
    else:
        return str(object)
    

def getAsXml(object, parent = None, indent = 0, **keywords):
    if object is None:
        return ''
    elif type(object) == types.InstanceType and hasattr(object, 'getAsXml'):
        return object.getAsXml(parent = parent, indent = indent, **keywords)
    elif type(object) in [types.ListType, types.TupleType]:
        return ''.join([
            getAsXml(item, parent = parent, indent = indent, **keywords)
            for item in object])
    else:
        strObject = convertStringToXml(str(object))
        if parent is not None and parent.typoEnhancements:
            strObject = enhanceTypo(strObject)
        if parent is not None and parent.preserveSpacing:
            return strObject
        else:
            return """\
%(spacing)s%(object)s
""" % {
                'object': strObject,
                'spacing': '  ' * indent,
                }


def getAsXmlAttributeValue(object, tag = None, **keywords):
    if object is None:
        return ''
    elif isinstance(object, urlCommon):
        return object.getAsXmlAttributeValue(tag = tag, **keywords)
    else:
        return convertStringToXmlAttributeValue(str(object))


def main():
    print html(
        head(title('Test page')),
        body(a(href = 'http://www.entrouvert.org')('entr\'ouvert'),
             a(href = httpUrl(path = '/index.html'))('Index'),
             a(href = httpUrl(port = '8080', path = '/index.html'))('Index'),
             )
        )


if __name__ == "__main__":
    main()

