#dynamic rml experiments

import sys
from types import StringType, UnicodeType, TupleType, ListType

from rlextra.radxml import xmlutils
from pprint import pprint as pp

import xacquire

#we allow multiple names for each tag.
# Sometimes DTD's/schemas are cleaner if you can define
# separate block-level or line-level tags, whose content can
# then be defined as something more helpful than 'anything goes'.

#These could all be redefined for a new app.
TAG_LOOP = ['loop', 'loop_b','loop_i']
TAG_LOOP_INNER = 'var'
TAG_LOOP_OUTER = 'in'

TAG_EXPR = ['expr','expr_i','expr_b']
TAG_IF = ['if', 'if_i','if_b']

# this is the general 'if' statement.  The switch contains 1 or
# more cases, and an optional default.
# You may use one of two patterns:
#..a named variable and allowed values....
#<switch expr="colour">
#  <case cond="green">Go</case>
#  <case cond="amber">Slow</case>
#  <case cond="red">Stop</case>
#</switch>
#...or a full python expression in each condition...
#<switch>
#  <case cond="colour==green">Go</case>
#  <case cond="colour==amber">Slow</case>
#  <case cond="colour==red">Stop</case>
#</switch>
#It knows which to do based on the presence/absence of 'expr' in the switch.
#

TAG_SWITCH = ['switch','switch_i','switch_b']
TAG_CASE = ['case','case_b','case_i']
TAG_DEFAULT = ['default','default_b','default_i']

TAG_ASSIGN = ['assign','assign_b','assign_i']
TAG_ASSIGN_NAME = 'name'
TAG_ASSIGN_VALUE = 'value'

TAG_SCRIPT = ['script','script_b','script_i']

TAG_ACQUIRE = ['acquire','acquire_b','acquire_i']


sample1 = """<?xml version="1.0" encoding="UTF-8"?>
<document>
<script>import string</script>
<assign name="author" value="Andy Robinson"/>
<author><expr>author + ' ' + string.digits</expr></author>
<para>Here comes a table with <expr>2+2</expr> rows</para>
<table><tr><td>Product</td><td>Units</td><td>Taste</td></tr>
<loop var="(product, units)" in="dataSet">  <tr>
    <td><expr>product</expr></td>
    <td><expr>units</expr></td>
    <td>
        <switch expr="product">
            <case condition="ham">nice</case>
            <case condition="spam">nasty</case>
            <case condition="eggs">OK</case>
            <default>never tried that...</default>
        </switch>
    </td>
  </tr></loop>
</table>
</document>
"""

def preProcess(tree, nameSpace):
    """Expands the parsed tree in the namespace and return new one.
    Returns a single tag-tuple in most cases, but a list of them
    if processing a loop node.
    """
    #expand this into a class with methods for each tag it handles.
    #then I can put logic tags in one and data access in another.
    tagName, attrs, children, extraStuff = tree

    if tagName in TAG_LOOP:
        innerTxt = attrs[TAG_LOOP_INNER]
        outer = eval(attrs[TAG_LOOP_OUTER], nameSpace)
        dataSet = []
        for row in outer:
            nameSpace['__loop_inner__'] = row
            exec (innerTxt + " = __loop_inner__") in nameSpace
            newChildren = processChildren(children, nameSpace)
            if newChildren is not None:
                dataSet.extend(newChildren)
        return dataSet

    elif tagName in TAG_ASSIGN:
        name = attrs[TAG_ASSIGN_NAME]
        valueStr = attrs[TAG_ASSIGN_VALUE]
        try:
            value = eval(valueStr)
        except SyntaxError:  #must be a string
            value = valueStr
        nameSpace[name] = value
        return None

    elif tagName in TAG_SCRIPT:
        code = children[0]
        exec code in nameSpace
        return None

    elif tagName in TAG_EXPR:
        exprText = children[0]
        print 'found expression',exprText
        assert type(exprText) in (StringType, UnicodeType), "expr can only contain strings"

        #attributes may affect escaping
        if attrs is not None:
            escape = attrs.get('escape', None)
        else:
            escape = None

        exprValue = eval(exprText, nameSpace)
        if escape == 'CDATA':
            exprValue = '<![CDATA[%s]]>' % exprValue
        return [str(exprValue)]

    elif tagName in TAG_SWITCH:
        #two modes, with and without top level variable
        if attrs is None:
            exprText = None
        elif attrs.has_key('expr'):
            exprText = attrs.get('expr','')
        else:
            exprText = None
            
        if exprText:
            expr = eval(exprText, nameSpace)

        selected = None
        for child in children:
            if type(child) is TupleType:
                (childTagName, childAttrs, grandChildren, stuff) = child
                if childTagName in TAG_CASE:
                    condition = childAttrs['condition']
                    if exprText:
                        #check if it equals the value
                        try:
                            value = eval(condition, nameSpace)
                        except NameError:
                            value = condition # assume a string
                        if (expr == value):
                            selected = processChildren(grandChildren, nameSpace)
                            break
                    else:
                        #they gave us a full condition, evaluate it
                        yes = eval(condition, nameSpace)
                        if yes:
                            selected = processChildren(grandChildren, nameSpace)
                            break
                elif childTagName in TAG_DEFAULT:
                    selected = processChildren(grandChildren, nameSpace)
                    break
                else:
                    raise ValueError('%s tag may only contain these tags: ' % (TAG_SWITCH, ', '.join(TAG_CASE+TAG_DEFAULT)))

                    
        return selected

    elif tagName in TAG_ACQUIRE:
        #all children will be data fetchers
        xacquire.acquireData(children, nameSpace)
        return None
    else:
        newChildren = processChildren(children, nameSpace)
        return (tagName, attrs, newChildren, extraStuff)

def processChildren(children, nameSpace):
    "Handles iteration over child tags, one of which may be a loop"
    if children is None:
        newChildren = None
    else:
        newChildren = []
        for child in children:
            if type(child) in (StringType, UnicodeType):
                newChildren.append(child)
            else:
                newStuff = preProcess(child, nameSpace)
                if type(newStuff) is TupleType:
                    #returned a single tag-tuple
                    newChildren.append(newStuff)
                elif type(newStuff) is ListType:
                    #must have been a loop tag, returned a list
                    newChildren.extend(newStuff)
                #if None, omit it...
    return newChildren

def processDataAcquitision(children, nameSpace):
    """The data acquisition tags give easy access to data sources.
    They do not return values but put them in the namespace"""
    if children is None:
        newChildren = None
    else:
        for child in children:
            if type(child) in (StringType, UnicodeType):
                newChildren.append(child)
            else:
                newStuff = preProcess(child, nameSpace)
                if type(newStuff) is TupleType:
                    #returned a single tag-tuple
                    newChildren.append(newStuff)
                elif type(newStuff) is ListType:
                    #must have been a loop tag, returned a list
                    newChildren.extend(newStuff)
                #if None, omit it...
    return newChildren

def run(sample):
    from reportlab.lib.rparsexml import parsexml
    tree = parsexml(sample)
    xmlutils.stripWhitespace(tree)


    nameSpace = {'dataSet':[('ham',3),('spam',15),('eggs',12)]}
    processed = preProcess(tree, nameSpace)
    print
    print 'raw results:'
    pp(processed)
    print
    print 'formatted as XML again:'
    tw = xmlutils.TagWrapper(processed)
    xml = xmlutils.reconstructXML(tw)
    print xml

if __name__=='__main__':
    run(sample1)
