commit bb82695b5f069d0fdcb16118fb3287f78ca57112
Author: Radek Pazdera <rpazdera(a)redhat.com>
Date: Wed Jul 18 17:03:33 2012 +0200
XmlPreprocessor: Changing to XmlTemplates
XmlPreprocessor was reworked into a class called XmlTemplates. This
class is responsible for processing XML templates during recipe
parsing.
Various features were removed (especially the preprocessing code and
several new features were added.
XmlTemplates supports hierarchy of namespaces for aliases. In other
words, alias definitions now have only local effect within the tag
of their definition. They will not be accessible from anywhere else.
Signed-off-by: Radek Pazdera <rpazdera(a)redhat.com>
Common/{XmlPreprocessor.py => XmlTemplates.py} | 135 ++++++++++++++++--------
1 files changed, 89 insertions(+), 46 deletions(-)
---
diff --git a/Common/XmlPreprocessor.py b/Common/XmlTemplates.py
similarity index 62%
rename from Common/XmlPreprocessor.py
rename to Common/XmlTemplates.py
index be2cab2..92ff269 100644
--- a/Common/XmlPreprocessor.py
+++ b/Common/XmlTemplates.py
@@ -1,6 +1,6 @@
"""
-This module contains code for preprocessing templates in XML files/recipes
-before they are parsed.
+This module contains code to aid processing templates in XML files/recipes
+while they're being parsed.
Templates are strings enclosed in curly braces {} and can be present
in all text elements of the XML file (this includes tag values or
@@ -26,46 +26,84 @@ import re
class XmlTemplateError(Exception):
pass
-class XmlPreprocessor:
- """
- This class serves as template processor within a XML DOM tree object.
- """
+class XmlTemplates:
+ """ This class serves as template processor """
_alias_re = "\{\$([a-zA-Z0-9_]+)(\[.+\])*\}"
_func_re = "\{([a-zA-Z0-9_]+)\(([^\(\)]*)\)\}"
- def __init__(self):
- self._definitions = {}
+ def __init__(self, definitions=None):
+ if definitions:
+ self._definitions = [definitions]
+ else:
+ self._definitions = [{}]
+
self._reserved_aliases = ["recipe"]
- def define_alias(self, name, value, skip_reserved_check=False):
+ def set_definitions(self, defs):
+ """ Set alias definitions
+
+ All existing definitions and namespace levels are
+ destroyed and replaced with new definitions.
+ """
+ del self._definitions
+ self._definitions = [defs]
+
+ def get_definitions(self):
+ """ Return definitions dict
+
+ Definitions are returned as a single dictionary of
+ all currently defined aliases, regardless the internal
+ division to namespace levels.
"""
- Associate an alias name with some value. The value can be of
- an atomic type or an array.
+ defs = {}
+ for level in self._definitions:
+ for name, val in level.iteritems():
+ defs[name] = val
+
+ return defs
+
+ def define_alias(self, name, value, skip_reserved_check=False):
+ """ Associate an alias name with some value
+
+ The value can be of an atomic type or an array. The
+ definition is added to the current namespace level.
"""
if not name in self._reserved_aliases \
or skip_reserved_check == True:
- self._definitions[name] = value
+ self._definitions[-1][name] = value
else:
raise XmlTemplateError("Alias name '%s' is reserved" %
name)
- def remove_comments(self, node):
+ def add_namespace_level(self):
+ """ Create new namespace level
+
+ This method will create a new level for definitions on
+ the stack. All aliases, that will be defined after this
+ call will be dropped as soon as `drop_namespace_level'
+ is called.
"""
- Remove all comment nodes from the tree.
+ self._definitions.append({})
+
+ def drop_namespace_level(self):
+ """ Remove one namespace level
+
+ This method will erease all defined aliases since the
+ last call of `add_namespace_level' method. All aliases,
+ that were defined beforehand will be kept.
"""
+ self._definitions.pop()
- comments = []
- for child in node.childNodes:
- if child.nodeType == node.COMMENT_NODE:
- comments.append(child)
- else:
- self.remove_comments(child)
+ def _find_definition(self, name):
+ for level in reversed(self._definitions):
+ if name in level:
+ return level[name]
- for comment in comments:
- node.removeChild(comment)
+ err = "'%s' is not defined here" % name
+ raise XmlTemplateError(err)
- def expand(self, node):
+ def expand_dom(self, node):
"""
Traverse DOM tree from `node' down and expand any
templates along the way.
@@ -76,13 +114,13 @@ class XmlPreprocessor:
num_attributes = node.attributes.length
while(i < num_attributes):
attr = node.attributes.item(i)
- attr.value = self._expand_string(str(attr.value))
+ attr.value = self.expand_string(str(attr.value))
i += 1
elif node.nodeType == node.TEXT_NODE:
- node.data = self._expand_string(str(node.data))
+ node.data = self.expand_string(str(node.data))
for child in node.childNodes:
- self.expand(child)
+ self.expand_dom(child)
def expand_group(self, group):
"""
@@ -92,9 +130,14 @@ class XmlPreprocessor:
"""
for node in group:
- self.expand(node)
+ self.expand_dom(node)
+
+ def expand_string(self, string):
+ """ Expand templates in a string
- def _expand_string(self, string):
+ This method will process and expand all templates
+ contained inside a string.
+ """
while True:
alias_match = re.search(self._alias_re, string)
func_match = re.search(self._func_re, string)
@@ -122,20 +165,17 @@ class XmlPreprocessor:
alias_name = alias_match.group(1)
array_subscript = alias_match.group(2)
- if not alias_name in self._definitions:
- err = "Alias '%s' is not defined here" % alias_name
- raise XmlTemplateError(err)
+ alias_obj = self._find_definition(alias_name)
if array_subscript != None:
try:
- result = str(eval("self._definitions['%s']%s" \
- % (alias_name, array_subscript)))
+ result = str(eval("alias_obj%s" % array_subscript))
except (KeyError, IndexError):
- err = "Wrong array subscript in '%s[%s]'" \
+ err = "Wrong array subscript in '%s%s'" \
% (alias_name, array_subscript)
raise XmlTemplateError(err)
else:
- result = self._definitions[alias_name]
+ result = alias_obj
return result
@@ -177,34 +217,34 @@ class XmlPreprocessor:
def _ip_func(self, params):
self._validate_func_params("ip", params, 2, 1)
- self._check_recipe_data("ip")
+ recipe = self._get_recipe_data("ip")
m_id = int(params[0])
if_id = int(params[1])
ip_id = int(params[2]) if len(params) == 3 else 0
- machines = self._definitions["recipe"]["machines"]
+ machines = recipe["machines"]
ip_addr = machines[m_id]['netconfig'][if_id]['addresses'][ip_id]
return ip_addr.split('/')[0]
def _hwaddr_func(self, params):
self._validate_func_params("hwaddr", params, 2, 0)
- self._check_recipe_data("hwaddr")
+ recipe = self._get_recipe_data("hwaddr")
m_id = int(params[0])
if_id = int(params[1])
- machines = self._definitions["recipe"]["machines"]
+ machines = recipe["machines"]
return machines[m_id]['netconfig'][if_id]['hwaddr']
def _devname_func(self, params):
self._validate_func_params("devname", params, 2, 0)
- self._check_recipe_data("devname")
+ recipe = self._get_recipe_data("devname")
m_id = int(params[0])
if_id = int(params[1])
- machines = self._definitions["recipe"]["machines"]
+ machines = recipe["machines"]
return machines[m_id]['netconfig'][if_id]['name']
@staticmethod
@@ -225,8 +265,11 @@ class XmlPreprocessor:
err = "Non-integer parameter passed to '%s'" % name
raise XmlTemplateError(err)
- def _check_recipe_data(self, template_name):
- if not "recipe" in self._definitions:
- err = "Cannot resolve %s() here, recipe data not available yet" \
- % template_name
- raise XmlTemplateError(err)
+ def _get_recipe_data(self, template_name):
+ try:
+ recipe = self._find_definition("recipe")
+ return recipe
+ except XmlTemplateError, err:
+ msg = "Cannot resolve %s(): " % template_name
+ msg += str(err)
+ raise XmlTemplateError(msg)