libxml2dom

tools/libxml2macro.py

310:20d44607697b
2007-10-01 paulb [project @ 2007-10-01 23:27:00 by paulb] Added safety checks for nodes.
     1 #!/usr/bin/env python     2      3 import compiler     4 import marshal, imp, os, stat, struct     5      6 # Originally from Analysis/Writers/Python.     7      8 def write_module(module, source_filename, target_filename, syntax_check=1):     9     10     """    11     Write the given 'module', associated with the given 'source_filename', to a    12     file with the given 'target_filename'. The optional 'syntax_check' flag (set    13     by default) ensures that the module is syntactically correct.    14     """    15     16     if syntax_check:    17         compiler.syntax.check(module)    18     compiler.misc.set_filename(source_filename, module)    19     generator = compiler.pycodegen.ModuleCodeGenerator(module)    20     f = open(target_filename, "wb")    21     f.write(get_header(source_filename))    22     marshal.dump(generator.getCode(), f)    23     f.close()    24     25 def get_header(filename):    26     27     "Taken from compiler.pycodegen. Prepare the compiled module header."    28     29     MAGIC = imp.get_magic()    30     mtime = os.stat(filename)[stat.ST_MTIME]    31     mtime = struct.pack('<i', mtime)    32     return MAGIC + mtime    33     34 # Processing functions.    35     36 def process_nodes(root_node, prefix=None):    37     38     """    39     Under the 'root_node', process all suitable expression nodes which employ    40     special names using the given 'prefix'.    41     """    42     43     for node in root_node.getChildNodes():    44     45         # Find imports and discover if they are really used to define the    46         # special prefix.    47     48         if isinstance(node, compiler.ast.Import):    49             prefix = process_import(node, root_node) or prefix    50     51         # Identify suitable names and add replacement nodes.    52     53         elif isinstance(node, compiler.ast.CallFunc):    54             process_callfunc(node, prefix)    55         elif isinstance(node, compiler.ast.Getattr):    56             process_getattr(node, prefix, root_node)    57         else:    58             process_nodes(node, prefix)    59     60 def process_import(node, parent):    61     62     """    63     Process the Import 'node' searching for the special incantation required to    64     set the prefix. Remove compliant nodes from their 'parent' node. If no new    65     prefix is found, return None.    66     """    67     68     for name, alias in node.names:    69         if name == "libxml2macro":    70     71             # Remove this node from its parent.    72     73             parent.nodes.remove(node)    74             return alias    75     76     return None    77     78 def process_callfunc(node, prefix):    79     80     """    81     Process the CallFunc 'node' searching for special names with the given    82     'prefix'.    83     """    84     85     # Check the prefix.    86     87     if prefix is None:    88         return    89     90     # Check the target.    91     92     target = node.node    93     if isinstance(target, compiler.ast.Getattr):    94         if process_getattr(target, prefix, node):    95     96             # Process all sibling arguments of the new first argument, too.    97     98             process_callfunc_args(node, prefix, start_index=1)    99    100         else:   101             process_callfunc_args(node, prefix)   102    103     else:   104         process_callfunc_args(node, prefix)   105    106 def process_callfunc_args(node, prefix, start_index=0):   107    108     """   109     Process the arguments of the given CallFunc 'node' searching for special   110     names with the given 'prefix'. The optional 'start_index' is used to   111     indicate the first argument to be processed.   112     """   113    114     for index in range(start_index, len(node.args)):   115         arg = node.args[index]   116         if isinstance(arg, compiler.ast.Getattr):   117             process_getattr(arg, prefix, node, index=index)   118    119 def process_getattr(node, prefix, parent, index=None):   120    121     """   122     Process the Getattr 'node' searching for special names with the given   123     'prefix', using the given 'parent' to add transformed expressions in place   124     of the original ones.   125    126     The optional 'index' is used when arguments of CallFunc nodes are being   127     processed and where a replacement of such arguments is occurring.   128     """   129    130     # Check the prefix.   131    132     if prefix is None:   133         return   134    135     # Detected cases:   136     # node.attr plus node.attr.attr   137     # (obj.node).attr plus (obj.node).attr.attr   138     # Note that the deep cases are dealt with first using a recursive call.   139    140     if getattr_has_prefix(node, prefix) or \   141         isinstance(node.expr, compiler.ast.Getattr) and propagated_prefix(process_getattr(node.expr, prefix, node)):   142    143         # Replace CallFunc plus Getattr occurrences:   144         # node.attr(args) -> Node_attr(node, args)   145         # fn(node.attr) -> fn(Node_attr(node))   146    147         if isinstance(parent, compiler.ast.CallFunc):   148    149             # If this node is not an argument, transform the call.   150    151             if index is None:   152                 parent.node = compiler.ast.Name("Node_%s" % node.attrname)   153                 parent.args.insert(0, node.expr)   154    155             else:   156                 replacement = compiler.ast.CallFunc(   157                     compiler.ast.Name("Node_%s" % node.attrname),   158                     [node.expr]   159                     )   160                 parent.args[index] = replacement   161    162         # Replace plain Getattr nodes:   163         # node.attr -> Node_attr(node)   164         # NOTE: Nasty but necessary rewiring of the parent node required.   165    166         else:   167             replacement = compiler.ast.CallFunc(   168                 compiler.ast.Name("Node_%s" % node.attrname),   169                 [node.expr]   170                 )   171             for key, value in parent.__dict__.items():   172                 # Detect lists.   173                 if hasattr(value, "__len__") and node in value:   174                     index = value.index(node)   175                     value[index] = replacement   176                 elif value is node:   177                     parent.__dict__[key] = replacement   178    179         # Propagate whether the kind of result might need transforming itself.   180    181         return node.attrname   182    183     else:   184         process_nodes(node, prefix)   185         return None   186    187 def propagated_prefix(attrname):   188    189     """   190     Return whether the given 'attrname' used in a transformation should be   191     considered significant at the parent level.   192     """   193    194     return attrname in ("ownerElement", "ownerDocument")   195    196 def getattr_has_prefix(node, prefix):   197    198     """   199     Determine whether the given Getattr 'node' employs the special 'prefix' in a   200     number of ways.   201     """   202    203     # Check the expression as a simple name:   204     # node.attr   205    206     if isinstance(node.expr, compiler.ast.Name) and node.expr.name.startswith(prefix):   207         return 1   208    209     # Check the attribute name of child expressions:   210     # (obj.node).attr   211    212     elif isinstance(node.expr, compiler.ast.Getattr) and node.expr.attrname.startswith(prefix):   213         return 1   214     else:   215         return 0   216    217 def include_import(module):   218    219     """   220     Include an import statement in 'module' to make the macro library available.   221     """   222    223     module.node.nodes.insert(0, compiler.ast.From("libxml2dom.macrolib", [("*", None)]))   224    225 def process_file(filename):   226    227     """   228     Process the module given by the specified 'filename'. The optional special   229     'prefix' marks those variables to be processed.   230     """   231    232     # Open the module as an AST.   233    234     module = compiler.parseFile(filename)   235    236     # Find references to special variables.   237    238     process_nodes(module)   239    240     # Add necessary imports.   241    242     include_import(module)   243    244     # Write the module.   245    246     write_module(module, filename, os.path.splitext(filename)[0] + ".pyc")   247     return module   248    249 if __name__ == "__main__":   250     import sys   251     if len(sys.argv) < 2:   252         print "libxml2macro.py <module-filename>"   253         sys.exit(1)   254     process_file(sys.argv[1])   255    256 # vim: tabstop=4 expandtab shiftwidth=4