"""Read and format XHTML source from TinyMCE.

This module reads the XHTML and uses a formatter.Formatter to do the work of
making appropriate document(s): HTML and PDF, currently.

"""

import os, re, urllib, cStringIO

import pyRXPU
from rlextra.radxml.xmlutils import TupleTreeWalker, validateXhtml


class XhtmlTransformer(TupleTreeWalker):

    NAKED_CONTENT_WRAPPER = 'p'
    ADD_PARENT = {'b': 'p'}
    FLOWING_ELEMENTS = set(['p', 'ol', 'ul', 'li', 'address', 'h1', 'h2', 'h3'])

    def __init__(self, xmlSource, formatter):
        self._formatter = formatter
        self._inBody = False  # stuff in the header is always our own boilerplate junk
        self._flowingElementStack = []  # LIFO [(elementName, autoClose), ...]

        tree = validateXhtml(xmlSource)
        TupleTreeWalker.__init__(self, tree)

    def ensureParentPresent(self, name):
        parentToAdd = self.ADD_PARENT.get(name)
        if parentToAdd and (not self._flowingElementStack or
                            self._flowingElementStack[-1][0] not in self.FLOWING_ELEMENTS):
            self.startElement(parentToAdd, {}, autoClose=True)

    def startElement(self, name, attrs, autoClose=False):
        self.ensureParentPresent(name)

        if name in self.FLOWING_ELEMENTS:
            self._flowingElementStack.append((name, autoClose))

        if name == 'br':
            self._formatter.doBr()
        elif name == 'p':
            self._formatter.startPara()
        elif name == 'a':
            self._formatter.startLink(attrs.get("href", ""))
        elif name == 'ul':
            self._formatter.startUl()
        elif name == 'ol':
            self._formatter.startOl()
        elif name == 'li':
            self._formatter.startLi()
        elif name == 'span':
            self._formatter.startSpan()
        elif name == 'b':
            self._formatter.startBold()
        elif name == 'i':
            self._formatter.startItalic()
        elif name == 'address':
            self._formatter.startAddress()
        elif re.match('h\d', name):
            self._formatter.startHeading(int(name[1]))
        elif name == 'body':
            self._inBody = True

    def endElement(self, name):
        if self._flowingElementStack and name in self.FLOWING_ELEMENTS:
            opening, autoClose = self._flowingElementStack.pop()
            assert name == opening and not autoClose

        if name == 'p':
            self._formatter.endPara()
        elif name == 'a':
            self._formatter.endLink()
        elif name == 'ul':
            self._formatter.endUl()
        elif name == 'ol':
            self._formatter.endOl()
        elif name == 'li':
            self._formatter.endLi()
        elif name == 'span':
            self._formatter.endSpan()
        elif name == 'b':
            self._formatter.endBold()
        elif name == 'i':
            self._formatter.endItalic()
        elif name == 'address':
            self._formatter.endAddress()
        elif re.match('h\d', name):
            self._inH = False
            self._formatter.endHeading(int(name[1]))

        if self._flowingElementStack:
            autoCloserName, autoClose = self._flowingElementStack[-1]
            if autoClose:
                self._flowingElementStack.pop()
                self.endElement(autoCloserName)

    def characters(self, text):
        naked = not self._flowingElementStack
        nonEmpty = text.strip()
        wrap = naked and nonEmpty
        if wrap:
            self.startElement(self.NAKED_CONTENT_WRAPPER, {})

        if self._inBody:
            if self._flowingElementStack:
                self._formatter.flowingContent(text)
            else:
                self._formatter.content(text)

        if wrap:
            self.endElement(self.NAKED_CONTENT_WRAPPER)

    def getOutput(self):
        self.go()
        return self._formatter.getOutput()

    def resolveEntity(self, publicId, systemId):
        filename = os.path.join(self._dtdDir, systemId)
        url = urllib.pathname2url(filename)
        return url


def normalizeWhitespace(text):
    """Remove redundant whitespace from a string"""
    return ' '.join(text.split())

def transformXhtml(xhtmlFile, formatter):
    """
    xhtmlFile: filename or file-like object

    """
    source = xhtmlFile.read()
    tr = XhtmlTransformer(source, formatter)
    output = tr.getOutput()
    output = output.encode('utf-8')
    return output

def rmlFromMceTextField(text, paraStyle=None):
    from rldjango.common import rmlformatter
    if isinstance(text, unicode): text = text.encode('utf8')
    xhtmlFile = cStringIO.StringIO(xhtmlDocFromTinymceXhtmlFragment(text, addMissingPTag=True))
    formatter = rmlformatter.RmlFormatter(paraStyle=paraStyle)
    return transformXhtml(xhtmlFile, formatter)

def xhtmlDocFromTinymceXhtmlFragment(xhtmlFrag, publicDtd=False, title='', addMissingPTag=False):
    r = []
    assert addMissingPTag or xhtmlFrag[:255].lstrip().startswith('<p>'), (
        "expected XHTML fragment from TinyMCE starting with a <p> tag, got %r..." % xhtmlFrag[:255])

    r.append('<?xml version="1.0" encoding="UTF-8"?>')
    if publicDtd:
        r.append('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">')
    else:
        r.append('<!DOCTYPE html SYSTEM "xhtml1-strict.dtd">')
    r.append("""\
<html>
<head>
<title>%s</title>
</head>
<body>
""" % title)
    needsPTag = addMissingPTag and not xhtmlFrag[:255].lstrip().startswith('<')
    if needsPTag:
        r.append('<p>')
    r.append(xhtmlFrag)
    if needsPTag:
        r.append('</p>')
    r.append('</body></html>')
    r = '\n'.join(r)
    return r
