Skip to content

Commit

Permalink
Fix xml escaping for import
Browse files Browse the repository at this point in the history
  • Loading branch information
davidcox committed Apr 13, 2012
1 parent 91f4133 commit 0af6c20
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 54 deletions.
104 changes: 88 additions & 16 deletions mwx/ast/ast.py
@@ -1,12 +1,15 @@
from xml.sax.saxutils import escape
from copy import deepcopy, copy
import re

PRIMARY_ARG_STRING = "arg"
from mwx.constants import shorthand_actions

isiterable = lambda x: getattr(x, "__iter__", False)

tab = " "
use_declaration_style_syntax = True
use_declaration_style_syntax_for_states = True


def flatten(x):
Expand Down Expand Up @@ -47,6 +50,9 @@ def quote_once(x):

def mwx_properties_block(props):

if len(props.keys()) == 0:
return ''

props = copy(props)
tag = props.pop('tag', None)

Expand All @@ -63,6 +69,24 @@ def mwx_properties_block(props):
return output_string


def mwx_declaration(obj_type, props, declaration_style_syntax=use_declaration_style_syntax):

output_string = obj_type

if declaration_style_syntax:
props = copy(props)
tag = props.pop('tag')

if re.search(r'\s|[$@]', tag):
props['tag'] = tag # put it back
else:
output_string += ' ' + tag

output_string += mwx_properties_block(props)

return output_string


def mwx_child_block(children, tablevel=0):

if len(children) == 0:
Expand Down Expand Up @@ -141,7 +165,7 @@ class MWASTNode(object):
def __init__(self, obj_type, tag=None, props={}, children=[]):

self.obj_type = obj_type
self.props = deepcopy(props)
self.props = copy(props)

if self.props == None or self.props == '':
self.props = {}
Expand Down Expand Up @@ -246,8 +270,7 @@ def to_mwx(self, tablevel=0):
output_string += ''.join([to_mwx(c) for c in self.children])
return output_string

output_string += self.obj_type
output_string += mwx_properties_block(self.props)
output_string += mwx_declaration(self.obj_type, self.props)

output_string += mwx_child_block(self.children, tablevel)

Expand Down Expand Up @@ -306,6 +329,61 @@ def to_mwx(self):
return ''.join([to_mwx(c) for c in self.children])


class MWVariable(MWASTNode):

def __init__(self, tag, default=None, scope='global', var_type=None, props={}, children=[]):

try:
props = copy(props)
except:
print props
raise('shit!!')
if isinstance(props, str):
props = {}

if props is None:
props = {}

if default is not None:
props['default_value'] = default

if scope is None:
scope = 'global'

if var_type is None or var_type == 'var':
var_type = 'float'

props['scope'] = scope

props['type'] = var_type

MWASTNode.__init__(self, 'variable', tag, props=props, children=children)

def to_mwx(self, tablevel=0):

props = copy(self.props)

tag = props.pop('tag')
scope = props.pop('scope', 'global').lower()
var_type = props.pop('type', 'var').lower()

default = props.pop('default_value', '0.0')

s = tab * tablevel

if scope == 'local':
s += 'local '

s += var_type + ' '
s += tag
s += mwx_properties_block(props)
s += ' = %s' % default

s += '\n'

return s


class Action (MWASTNode):
"""A custom node for representing actions. Provides infrastructure for
handling a default arg, and automatically generating a convenience
Expand Down Expand Up @@ -449,10 +527,9 @@ def to_mwx(self, tablevel=0):
tabs = tab * tablevel
output_string = tabs

output_string += self.obj_type

# [key=val, ...]
output_string += mwx_properties_block(self.props)
output_string += mwx_declaration(self.obj_type,
self.props,
use_declaration_style_syntax_for_states)

# { action\n action\n ... }
output_string += mwx_child_block(self.actions, tablevel)
Expand All @@ -472,7 +549,6 @@ class Transition (MWASTNode):
"""

def __init__(self, condition=None, target=None, **kwargs):

alt_tag = "%s -> %s" % (to_mwx(condition), to_mwx(target))
kwargs['alt_tag'] = alt_tag
kwargs['tag'] = alt_tag
Expand All @@ -490,7 +566,7 @@ def to_mwx(self, tablevel=0):
condition = self.props['condition']
target = self.props['target']

output_string += "%s -> %s" % (to_mwx(condition),
output_string += "%s -> %s" % (to_mwx(condition, quote_strings=False),
to_mwx(target))

output_string += '\n'
Expand Down Expand Up @@ -571,7 +647,7 @@ def __init__(self, identifier, fargs=[], **kwargs):
def to_mwx(self, tablevel=0):
return self.to_infix()

def to_infix(self):
def to_infix(self, quote_strings=False):
out = self.props['tag']
out += '(' + to_mwx(self.children) + ')'
return out
Expand All @@ -580,7 +656,6 @@ def __str__(self):
return self.to_mwx()



class MWExpression (MWASTNode):
"""A node representing an arithmetic expression"""
def __init__(self, operator=None, operands=None, **kwargs):
Expand Down Expand Up @@ -622,10 +697,7 @@ def to_mwx(self, tablevel=0):
def to_xml(self):
return self.to_infix()

def to_infix(self, strip_quotes=False):

# qs = not strip_quotes
# output_string = ""
def to_infix(self, quote_strings=False):

if len(self.children) == 1:
operand = to_infix(self.children[0])
Expand All @@ -638,7 +710,7 @@ def to_infix(self, strip_quotes=False):
# this is a bit of a hack for now
# evaluate an expression at parse time (e.g. for macro control flow)
def eval(self):
eval_string = self.to_infix(strip_quotes=True)
eval_string = self.to_infix(quote_strings=False)
try:
return eval(eval_string)
except:
Expand Down
18 changes: 18 additions & 0 deletions mwx/ast/xml_import.py
Expand Up @@ -48,6 +48,21 @@ def action(self, node, parent=None, parent_ctx=None, index=None):
return replacement


@registered
class PromoteVariables (TreeWalker):

def trigger(self, node):
return (isinstance(node, MWASTNode) and
node.obj_type == 'variable')

def action(self, node, parent=None, parent_ctx=None, index=None):

replacement = MWVariable(node.tag, props=node.props, children=node.children)
parent.rewrite(parent_ctx, index, replacement)

return replacement


@registered
class DeleteMarkers (TreeWalker):

Expand Down Expand Up @@ -105,6 +120,9 @@ def action(self, node, parent=None, parent_ctx=None, index=None):

if transition_type == 'conditional':
condition = node.props.get('condition', None)
elif transition_type == 'timer_expired':
timer = node.props.get('timer')
condition = 'timer_expired(%s)' % timer
else:
condition = MWKeyword('always')

Expand Down
89 changes: 51 additions & 38 deletions mwx/parser.py
Expand Up @@ -18,18 +18,6 @@

# Helper functions

def nested_array_to_dict(a):
"""Convert a nested array of key-value pairs (from pyparsing) to a
dictionary
"""
d = {}

if a is not None and a != '':
for pair in a:
d[pair[0]] = pair[1]
return d


def list_to_literals(list_of_names):
"""Convert a list of strings to an OR'd sequence of pyparsing Literal
objects
Expand Down Expand Up @@ -86,6 +74,7 @@ def quoted_string_fn(drop_quotes=True):

return qs


class MWXParser:
"""A parser object for the 'MWX' lightweight MWorks DSL."""

Expand Down Expand Up @@ -326,7 +315,7 @@ def unary_expression_helper(pr):
generic_action = (action_name("type") + arg_list_open -
Optional(value)("arg") + Optional(property_list)("props") +
arg_list_close)
generic_action.setParseAction(lambda a: Action(a.type, a.arg, props=a.props))
generic_action.setParseAction(lambda a: Action(a.type, a.arg, props=dict(a.props)))

assignment_action = NotAny(def_keyword) + identifier("variable") + assign - value("value")
assignment_action.setParseAction(lambda a: AssignmentAction(a.variable, a.value))
Expand All @@ -350,28 +339,28 @@ def unary_expression_helper(pr):
valid_tag = (quoted_string_fn(True) | template_reference)
unquoted_tag = identifier

std_obj_decl << ((
(object_name("obj_type") + # "alt" syntax
unquoted_tag("tag") -
prop_list_open)
def decl_and_properties(object_name_combinator):
return ((object_name_combinator + # "alt" syntax
unquoted_tag("tag") -
Optional(prop_list_open -
Optional(property_list)('props') +
prop_list_close))

| # OR
| # OR

(object_name("obj_type") + # "regular" syntax
(object_name_combinator + # "regular" syntax
prop_list_open -
Optional(valid_tag)("tag")) +
Optional(Suppress(","))

) +
Optional(valid_tag + Optional(Suppress(',')))("tag") +
Optional(property_list)("props") +
prop_list_close))

(Optional(property_list)("props") + # remaining syntax
prop_list_close +
Optional(block(object_declaration, "children") +
LineEnd()))
std_obj_decl << (decl_and_properties(object_name("obj_type")) +
Optional(block(object_declaration, "children") + # remaining syntax
LineEnd())
)

std_obj_decl.setParseAction(lambda c: MWASTNode(c.obj_type, c.tag,
props=nested_array_to_dict(c.props),
props=dict(c.props),
children=getattr(c, 'children', [])))

transition = ((dummy_token("transition") | macro_element |
Expand All @@ -391,20 +380,31 @@ def unary_expression_helper(pr):
else:
state_payload = block(action, "actions") + transition_list_marker + block(transition, "transitions")

state = Literal("state") - \
prop_list_open + \
Optional(valid_tag)("tag") + Optional(Suppress(",")) + Optional(property_list)("props") + \
prop_list_close + \
state_payload
state = decl_and_properties(Literal('state')) + state_payload

state.setParseAction(lambda s: State(s.tag, props=s.props, actions=s.actions, transitions=s.transitions))
state.setParseAction(lambda s: State(s.tag, props=dict(s.props), actions=s.actions, transitions=s.transitions))

# ------------------------------
# Variable Declarations
# ------------------------------

variable_declaration = LineStart() + Literal("var") - identifier("tag") + Optional(assign + value("default"))
variable_declaration.setParseAction(lambda x: MWASTNode("variable", x.tag, props={'default': x.default}))
scope_type = (Literal('global') | Literal('local'))
variable_type = (dummy_token('variabletype') |
Literal('integer') |
Literal('float') |
Literal('bool') |
Literal('string') |
Literal('struct') |
Literal('var'))

variable_declaration = (LineStart() +
Optional(scope_type) +
variable_type('type') -
identifier("tag") +
Optional(prop_list_open + Optional(property_list('props')) + prop_list_close) + # property list
Optional(assign + value("default"))) # default value assignment

variable_declaration.setParseAction(lambda x: MWVariable(x.tag, default=x.default, props=dict(x.props)))

# ----------------------------------------------------
# Top-level object declarations and aliases thereof
Expand Down Expand Up @@ -495,21 +495,34 @@ class MWXMLParser:
def __init__(self):
self.handlers = {}

def mwxml_unescape(self, s):
replacements = {'#GT': '>',
'#LT': '<',
'#GE': '>=',
'#LE': '<=',
'#AND': '&&',
'#OR': '||'
}
for a, b in replacements.items():
s = re.sub(r'%s' % a, b, s)

return s

def xml_element_to_ast(self, element):
"""Converts an xml element to an MWASTNode"""
props = {}
children = []

for key in element.keys():
props[key] = element.get(key)
props[key] = self.mwxml_unescape(element.get(key))

for child in element.getchildren():
children.append(self.xml_element_to_ast(child))

return MWASTNode(element.tag, element.get('tag'),
props=deepcopy(props), children=children)

def parse_string(self, s, process_templates=True):
def parse_string(self, s, process_templates=True, base_path='.'):
"""Process a string containing MW XML, and return a tree of MWASTNode
objects
"""
Expand Down

0 comments on commit 0af6c20

Please sign in to comment.