########################################################################
# $Header: /var/local/cvsroot/4Suite/Ft/Xml/MarkupWriter.py,v 1.6 2005-09-13 22:59:35 uogbuji Exp $
"""
MarkupWriter provides a very friendly interface for generating XML content

Copyright 2005 Fourthought, Inc. (USA).
Detailed license and copyright information: http://4suite.org/COPYRIGHT
Project home, documentation, distributions: http://4suite.org/
"""

import sys
from Ft.Xml import XML_NAMESPACE, EMPTY_NAMESPACE, EMPTY_PREFIX, XMLNS_NAMESPACE
#from Ft.Xml import MessageSource

class MarkupWriter(object):
    """
    General-purpose utility class for generating XML (may eventually be
    expanded to produce more output types)

    Sample usage:
    
    from Ft.Xml import MarkupWriter
    writer = MarkupWriter(indent=u"yes")
    writer.startDocument()
    writer.startElement(u'xsa')
    writer.startElement(u'vendor')
    #Element with simple text (#PCDATA) content
    writer.simpleElement(u'name', content=u'Centigrade systems')
    #Note writer.text(content) still works
    writer.simpleElement(u'email', content=u"info@centigrade.bogus")
    writer.endElement(u'vendor')
    #Element with an attribute
    writer.startElement(u'product', attributes={u'id': u"100\u00B0"})
    #Note writer.attribute(name, value, namespace=None) still works
    writer.simpleElement(u'name', content=u"100\u00B0 Server")
    #XML fragment
    writer.xmlFragment('<version>1.0</version><last-release>20030401</last-release>')
    #Empty element
    writer.simpleElement(u'changes')
    writer.endElement(u'product')
    writer.endElement(u'xsa')
    writer.endDocument()

    Note on the difference between 4Suite writers and printers
    Writer  - module that exposes a broad public API for building output
              bit by bit
    Printer - module that simply takes a DOM and creates output from it
              as a whole, within one API invokation
    """
    def __init__(self, stream=sys.stdout, **wargs):
        """
        Convenience factory function for Markup writers (based on
        xsl:output in XSLT)
        """
        from Ft.Xml.Xslt.XmlWriter import XmlWriter
        from Ft.Xml.Xslt.OutputParameters import OutputParameters
        oparams = OutputParameters()
        for arg in wargs:
            setattr(oparams, arg, wargs[arg])
        self.writer = XmlWriter(oparams, stream)
        #self.__doc__ += self.writer.__doc__
        return

    def __getattr__(self, value):
        #Delegate to writer
        #if hasattr(self, value):
        #    return object.self.__dict, value)
        return getattr(self.writer, value)
        
    def startElement(self, tagName, namespace=EMPTY_NAMESPACE, extraNss=None,
                     attributes=None):
        """
        Create a start tag with optional attributes.  Must eventually
        be matched with an endElement call
        
        Note: all "strings" in these parameters must be unicode objects
        tagName - qualified name of the element (must be unicode)
        namespace - optional namespace URI
        attributes - optional dictionary mapping name to unicode value
                    the name can either be a unicode QName or a tuple
                    of (QName, namespace URI)
        extraNss - optional dictionary (defaults to an empty one) that
                   creates additional namespace declarations that the
                   user wants to place on the specific element. Each key
                   is a ns prefix, and each value a ns name (URI).
                   You do not need to use extraNss if you will be using
                   a similar namespace parameter.  In fact, most people
                   will never need this parameter.
        """
        if tagName.startswith('xml:'):
            #We can use such a raw test because of the very special case
            #nature of the XML prefix
            namespace = XML_NAMESPACE
        if namespace == EMPTY_NAMESPACE and u':' in tagName:
            #If they supplied a prefix, but not a namespace, complain
            #raise MarkupWriterException(MarkupWriterException.ELEM_PREFIX_WITHOUT_NAMESPACE)
            raise TypeError("Prefixed name %s specified without namespace.  Namespace should be provided in the second parameter."%(tagName))
        self.writer.startElement(tagName, namespace, extraNss)
        if attributes is not None:
            for name in attributes:
                if isinstance(name, tuple):
                    qname, namespace = name
                    value = attributes[name]
                    self.writer.attribute(qname, value, namespace)
                else:
                    if u':' in tagName:
                        #If they supplied a prefix, but not a namespace, complain
                        raise TypeError("Prefixed name %s specified without namespace.  Namespace should be provided by using the attribute name form (<qualified-name>, <namespace>)."%(name))
                    value = attributes[name]
                    self.writer.attribute(name, value)
        return

    def simpleElement(self, tagName, namespace=EMPTY_NAMESPACE, extraNss=None,
                      attributes=None, content=u""):
        """
        Create a simple tag with optional attributes and content.  The
        complete element, start tag, optional text content, end tag, will
        all be generated by this one call.  Must *not* be matched with
        an endElement call.

        Note: all "strings" in these parameters must be unicode objects
        tagName - qualified name of the element
        namespace - optional namespace URI
        attributes - optional dictionary mapping name to unicode value
                    the name can either be a unicode QName or a tuple
                    of (QName, namespace URI)
        content   - optional unicode object with the text body of the
                    simple element
        extraNss - optional dictionary (defaults to an empty one) that
                   creates additional namespace declarations that the
                   user wants to place on the specific element. Each key
                   is a ns prefix, and each value a ns name (URI).
                   You do not need to use extraNss if you will be using
                   a similar namespace parameter.  In fact, most people
                   will never need this parameter.
        """
        if tagName.startswith('xml:'):
            #We can use such a raw test because of the very special case
            #nature of the XML prefix
            namespace = XML_NAMESPACE
        self.startElement(tagName, namespace, extraNss, attributes)
        if content:
            self.writer.text(content)
        self.writer.endElement(tagName, namespace)
        return

    def xmlFragment(self, fragment):
        """
        Incorporate a well-formed general entity into the output.
        fragment of
        fragment - string (must not be a Unicode object) to be incorporated
                   verbatim into the output, after testing for wellp-formedness
        """
        from Ft.Xml.Domlette import EntityReader
        #Essentially just a WF test.
        #We don't actually use the resulting docfrag
        docfrag = EntityReader.parseString(
            fragment, 'urn:bogus:Ft.Xml.Xslt.MarkupWriter.xmlFragment')
        self.writer._stream.write(fragment)
        return


## from Ft import FtException
## class MarkupWriterException(FtException):
##     """
##     Exception class for errors specific to MarkupWriter
##     """
##     ELEM_PREFIX_WITHOUT_NAMESPACE = 100
##     ATTR_PREFIX_WITHOUT_NAMESPACE = 100

##     def __init__(self, errorCode, *args):
##         FtException.__init__(self, errorCode, MessageSource.MARKUPWRITER, args)
##         return


