#!/usr/local/bin/python2.0
# Copyright (c) 2001 Samuel Brauer. All Rights Reserved. NO WARRANTY.
# $Revision: 1.28 $
import sys
import time
import traceback
import types
import spb.XmlUtil
import spb.makiExceptions
import xml.sax.saxlib
import xml.sax.saxutils
MAKI_NS = 'http://maki.sourceforge.net/maki'
DEFAULT_KEYSTYLE = "attribute"
def debug(func, msg):
func("makiLogic: "+msg)
def my_eval(sourcecode, compiledfragment, evalDict):
try:
return eval(compiledfragment, evalDict)
except:
exc_info = sys.exc_info()
if(isinstance(exc_info[0], types.ClassType) and issubclass(exc_info[0], spb.makiExceptions.MakiException)): raise
tb = exc_info[2].tb_next
lineno = tb.tb_lineno
raise spb.makiExceptions.MakiLogicError, ["%s: %s" % (str(exc_info[0]), str(exc_info[1])), "at line#%d: %s" % (lineno, sourcecode.split("\n")[lineno-1])], tb
def processAttributeString(s, evalDict):
"""s is a string that may contain "{expressions}" which must be evaluated"""
if(s == None): return ""
if(s.find('{') < 0): return s
out = []
sourcecode = None
for c in s:
if(c == '{'):
if(sourcecode == None):
sourcecode = []
else:
sourcecode.append(c)
elif(c == '}'):
if(sourcecode == None):
out.append(c)
else:
val = "".join(sourcecode)
if(not val or val[0] == '{'):
out.append(val)
else:
out.append(codeToText(val, evalDict))
sourcecode = None
else:
if(sourcecode != None):
sourcecode.append(c)
else:
out.append(c)
return "".join(out)
def evaluateCode(sourcecode, evalDict):
"""evaluate a piece of python code, returning an object"""
logFunc = evalDict.get("MAKILOGIC_log", None)
if(logFunc):
debug(logFunc, "evaluateCode input: "+sourcecode)
timein = time.time()
compiledfragment = compile(sourcecode, "<string>", "eval")
if(logFunc):
debug(logFunc, "compilation time: %8.4f" % (time.time() - timein))
timein = time.time()
output = my_eval(sourcecode, compiledfragment, evalDict)
if(logFunc):
debug(logFunc, "evaluation time: %8.4f" % (time.time() - timein))
debug(logFunc, "evaluateCode output: "+repr(output))
return output
def codeToText(sourcecode, evalDict):
"""evaluate a piece of python code, returning a string coerced from the object"""
return spb.XmlUtil.objectToText(evaluateCode(sourcecode, evalDict))
def codeToSaxEvents(handler, sourcecode, evalDict):
"""evaluate a piece of python code and write SAX2 events representing the result"""
return spb.XmlUtil.objectToSaxEvents(handler, evaluateCode(sourcecode, evalDict))
def doLogic(sourcecode, evalDict):
sourcecode = sourcecode.strip() + "\n"
logFunc = evalDict.get("MAKILOGIC_log", None)
if(logFunc):
debug(logFunc, "doLogic input:")
lineno = 0
for line in sourcecode.split("\n"):
lineno += 1
logFunc("%4d: %s" % (lineno, line))
timein = time.time()
compiledfragment = compile(sourcecode, "<string>", "exec")
if(logFunc):
debug(logFunc, "compilation time: %8.4f" % (time.time() - timein))
timein = time.time()
my_eval(sourcecode, compiledfragment, evalDict)
if(logFunc):
debug(logFunc, "evaluation time: %8.4f" % (time.time() - timein))
def stripPrefix(name):
idx = name.find(':')
if(idx > 0): return name[idx+1:]
return name
def getUriAndName(attrs, evalDict):
"""returns a tuple (uri, name)"""
uri = attrs.get((None, 'uri'), None)
name = attrs.get((None, 'name'), None)
if(uri): uri = processAttributeString(uri, evalDict)
if(name):
name = processAttributeString(name, evalDict)
name = stripPrefix(name)
return (uri, name)
#------------------------------------------------------
class QuitParsingEarly(Exception):
"""Used as a signal that the handler did not parse the entire source."""
def __init__(self, args=None):
self.args = args
class ElementInfo:
def __init__(self, name, attrs, needsFlushFlag=1):
# most normal elements will need to be flushed (written to output stream)
# maki elements are the exception
self.name = name
self.attrs = {}
for key in attrs.keys():
self.attrs[key] = attrs[key]
self.bufferCharsFlag = 1
self.chars = ''
self.needsFlushFlag = needsFlushFlag
class makiLogicHandler(xml.sax.saxlib.ContentHandler):
def __init__(self, out, evalDict, maxMakiTags=None):
self.out = out
self.prefixMappings = {}
self.uriMappings = {}
self.undeclaredPrefixes = []
self.evalDict = evalDict
self.elements = [] # one ElementInfo instance per open XML element we parse
self.makiElements = [] # one ElementInfo instance per open element that was dynamically generated by maki:element
self.locator = None
self.maxMakiTags = maxMakiTags
self.makiTags = 0
self.quitLine = None
self.quitColumn = None
def getQuitLocation(self):
return (self.quitLine, self.quitColumn)
def setDocumentLocator(self, locator):
self.locator = locator
def writeCharacters(self, chars, escape=0):
if(not chars): return
if(isinstance(chars, types.UnicodeType)): chars = chars.encode('utf-8')
if(escape == 1): self.out.write(xml.sax.saxutils.escape(chars))
elif(escape == 2): self.out.write(xml.sax.saxutils.escape(chars).replace('"', '"'))
else: self.out.write(chars)
def writeStartDocument(self):
header = '<?xml version="1.0" encoding="utf-8"?>'
self.out.write(header)
def writeStartElementNS(self, name, attrs):
uri = name[0]
lname = name[1]
self.out.write('<')
if(uri):
self.writeCharacters(self.uriMappings[uri][-1])
self.out.write(':')
self.writeCharacters(lname)
for prefix in self.undeclaredPrefixes:
self.writeCharacters(' xmlns:%s="%s"' % (prefix, self.prefixMappings[prefix][-1]))
self.undeclaredPrefixes = []
for pair in attrs.items():
attname = pair[0]
attvalue = pair[1]
self.out.write(' ')
atturi = attname[0]
attlname = attname[1]
if(atturi):
prefix = self.uriMappings[atturi][-1]
self.writeCharacters(prefix)
self.out.write(':')
#self.out.write('%s="%s"' % (attlname, xml.sax.saxutils.escape(attvalue)))
self.writeCharacters('%s="' % attlname)
self.writeCharacters(attvalue, 2)
self.out.write('"')
self.out.write('>')
def writeEndElementNS(self, name):
uri = name[0]
lname = name[1]
self.out.write('</')
if(uri):
self.writeCharacters(self.uriMappings[uri][-1])
self.out.write(':')
self.writeCharacters(lname)
self.out.write('>')
def writePI(self, target, data):
#self.out.write('<?%s %s?>' % (target, data))
self.out.write('<?')
self.writeCharacters('%s %s' % (target, data))
self.out.write('?>')
def writeComment(self, data):
#self.out.write('<!--%s-->' % data)
self.out.write('<!--')
self.writeCharacters(data)
self.out.write('-->')
def getHelperHandler(self):
printhandler = spb.XmlUtil.PrintHandler(self.out, self.prefixMappings, self.uriMappings, self.undeclaredPrefixes)
return spb.XmlUtil.FilterHandler(printhandler, 0, 0, 0, 1)
def flush(self):
idx = 0
for element in self.elements:
if(element.needsFlushFlag):
self.writeStartElementNS(element.name, element.attrs)
element.needsFlushFlag = 0
self.writeCharacters(element.chars, 1)
element.bufferCharsFlag = 0
element.chars = '' # free some memory
element.attrs = None # free some memory
def startDocument(self):
self.writeStartDocument()
def startPrefixMapping(self, prefix, uri):
mappings = self.prefixMappings.get(prefix, [])
mappings.append(uri)
self.prefixMappings[prefix] = mappings
mappings = self.uriMappings.get(uri, [])
mappings.append(prefix)
self.uriMappings[uri] = mappings
if(prefix not in self.undeclaredPrefixes):
self.undeclaredPrefixes.append(prefix)
def endPrefixMapping(self, prefix):
mappings = self.prefixMappings.get(prefix)
uri = mappings.pop()
mappings = self.uriMappings.get(uri)
mappings.pop()
#
# Notes on <maki:element>
# Any directly contained character data is passed straight through.
# If you want character data to be evaluated, it must be explicitly inside
# a maki:expr.
#
# Notes on <maki:attribute>
# These must be the first children of their enclosing element.
# If they follow any character data or other elements then the behavior
# is undefined (quite possibly a runtime exception).
#
def startElementNS(self, name, qname, attrs):
if(name[0] == MAKI_NS):
if(name[1] != 'attribute'): self.flush()
if(name[1] != 'element'): self.elements.append(ElementInfo(name, attrs, 0))
if(name[1] == 'element'):
(eluri, elname) = getUriAndName(attrs, self.evalDict)
if(not elname): raise RuntimeError, "maki:element without @name"
element = ElementInfo((eluri, elname), {}, 1)
self.elements.append(element)
self.makiElements.append(element)
elif(name[1] not in ('page', 'logic', 'global-logic', 'expr', 'attribute', 'processing-instruction', 'comment', 'elements')):
raise RuntimeError, "Unrecognized maki tag '%s'" % name[1]
else:
self.flush()
self.elements.append(ElementInfo(name, attrs, 1))
def endElementNS(self, name, qname):
if(name[0] == MAKI_NS):
self.makiTags += 1
if(name[1] == 'element'): self.flush()
element = self.elements.pop()
if(name[1] == 'page'):
pass
elif(name[1] == 'logic'):
doLogic(element.chars, self.evalDict)
elif(name[1] == 'expr'):
result = evaluateCode(element.chars.strip(), self.evalDict)
if(result != None):
(eluri, elname) = getUriAndName(element.attrs, self.evalDict)
# if a @name exists, wrap the expression with an element
if(elname): self.writeStartElementNS((eluri, elname), {})
keyStyle = element.attrs.get((None, 'keyStyle'), None)
if(keyStyle):
if(keyStyle not in ('element', 'attribute')): raise RuntimeError, "@keyStyle must be 'element' or 'attribute'"
else:
keyStyle = DEFAULT_KEYSTYLE
spb.XmlUtil.objectToSaxEvents(self, result, keyStyle)
if(elname): self.writeEndElementNS((eluri, elname))
elif(name[1] == 'elements'):
(eluri, elname) = getUriAndName(element.attrs, self.evalDict)
if(not elname): raise RuntimeError, "maki:elements without @name"
objects = evaluateCode(element.chars.strip(), self.evalDict)
if(objects):
keyStyle = element.attrs.get((None, 'keyStyle'), None)
if(keyStyle):
if(keyStyle not in ('element', 'attribute')): raise RuntimeError, "@keyStyle must be 'element' or 'attribute'"
else:
keyStyle = DEFAULT_KEYSTYLE
for object in objects:
self.writeStartElementNS((eluri, elname), {})
spb.XmlUtil.objectToSaxEvents(self, object, keyStyle)
self.writeEndElementNS((eluri, elname))
elif(name[1] == 'element'):
self.makiElements.pop()
self.writeEndElementNS(element.name)
elif(name[1] == 'attribute'):
(atturi, attname) = getUriAndName(element.attrs, self.evalDict)
if(not attname): raise RuntimeError, "maki:attribute without @name"
attvalue = codeToText(element.chars, self.evalDict)
targetElement = self.elements[-1]
targetElement.attrs[(atturi, attname)] = attvalue
elif(name[1] == 'processing-instruction'):
piname = element.attrs.get((None, 'name'), None)
target = processAttributeString(piname, self.evalDict)
if(not target): raise RuntimeError, "<processing-instruction> without @name"
data = codeToText(element.chars, self.evalDict)
self.writePI(target, data)
elif(name[1] == 'comment'):
comment = codeToText(element.chars, self.evalDict)
self.writeComment(comment)
else:
raise RuntimeError, "Unrecognized maki tag '%s'" % name[1]
if(self.maxMakiTags != None and self.makiTags >= self.maxMakiTags):
self.quitLine = self.locator.getLineNumber()
self.quitColumn = self.locator.getColumnNumber()
raise QuitParsingEarly
else:
self.flush()
self.elements.pop()
self.writeEndElementNS(name)
def startElement(self, name, attrs):
nsattrs = {}
for key in attrs.keys():
nsattrs[(None, key)] = attrs[key]
self.startElementNS((None, name), name, nsattrs)
def endElement(self, name):
self.endElementNS((None, name), name)
def characters(self, content):
if(not content): return
element = self.elements[-1]
if(element.bufferCharsFlag):
element.chars += content
if(element.needsFlushFlag):
# may need to flush
if(content.strip()):
self.flush()
else:
self.writeCharacters(content, 1)
def ignorableWhitespace(self, content):
self.characters(content)
def processingInstruction(self, target, data):
self.flush()
self.writePI(target, data)
#------------------------------------------------------
def process(s, out, evalDict):
# count number of maki start tags in s
makiTags = s.count("<maki:")
handler = makiLogicHandler(out, evalDict, makiTags)
try:
spb.XmlUtil.parseString(s, handler, 1)
except QuitParsingEarly:
(lineno, columnno) = handler.getQuitLocation()
lines = s.split('\n')
line = lines[lineno-1]
idx = line.find('>', columnno) + 1
out.write(line[idx:])
out.write('\n')
for line in lines[lineno:]:
out.write(line)
out.write('\n')
def main():
needClose = 0
if(len(sys.argv) < 2):
input = sys.stdin
else:
input = open(sys.argv[1])
needClose = 1
#handler = makiLogicHandler(sys.stdout, {}, {})
#spb.XmlUtil.parseStream(input, handler, 1)
process(input.read(), sys.stdout, {})
if(needClose): input.close()
if __name__ == "__main__":
main()
|