Package flumotion :: Package common :: Module fxml
[hide private]

Source Code for Module flumotion.common.fxml

  1  # -*- Mode: Python; test-case-name: flumotion.test.test_registry -*- 
  2  # vi:si:et:sw=4:sts=4:ts=4 
  3  # 
  4  # Flumotion - a streaming media server 
  5  # Copyright (C) 2006,2007,2008 Fluendo, S.L. (www.fluendo.com). 
  6  # All rights reserved. 
  7   
  8  # This file may be distributed and/or modified under the terms of 
  9  # the GNU General Public License version 2 as published by 
 10  # the Free Software Foundation. 
 11  # This file is distributed without any warranty; without even the implied 
 12  # warranty of merchantability or fitness for a particular purpose. 
 13  # See "LICENSE.GPL" in the source distribution for more information. 
 14   
 15  # Licensees having purchased or holding a valid Flumotion Advanced 
 16  # Streaming Server license may use this file in accordance with the 
 17  # Flumotion Advanced Streaming Server Commercial License Agreement. 
 18  # See "LICENSE.Flumotion" in the source distribution for more information. 
 19   
 20  # Headers in this file shall remain intact. 
 21   
 22  """xml parsing routines 
 23   
 24  Flumotion deals with two basic kinds of XML: config and registry. They 
 25  correspond to data and schema, more or less. This file defines some base 
 26  parsing routines shared between both kinds of XML. 
 27  """ 
 28   
 29  import sets 
 30  from xml.dom import minidom, Node 
 31  from xml.parsers import expat 
 32   
 33  from flumotion.common import log 
 34   
 35  __version__ = "$Rev: 6964 $" 
 36   
 37   
38 -class Box:
39 """ 40 Object designed to wrap, or "box", any value. Useful mostly in the 41 context of the table-driven XML parser, so that a handler that wants 42 to set a scalar value can do so, getting around the limitations of 43 Python's lexical scoping. 44 """
45 - def __init__(self, val=None):
46 self.set(val)
47
48 - def set(self, val):
49 self.val = val
50
51 - def unbox(self):
52 return self.val
53 54
55 -class ParserError(Exception):
56 """ 57 Error during parsing of XML. 58 59 args[0]: str 60 """
61 62
63 -class Parser(log.Loggable):
64 """ 65 XML parser base class. 66 67 I add some helper functions for specialized XML parsers, mostly the 68 parseFromTable method. 69 70 I am here so that the config parser and the registry parser can 71 share code. 72 """ 73 74 parserError = ParserError 75
76 - def getRoot(self, file):
77 """ 78 Return the root of the XML tree for the the string or filename 79 passed as an argument. Raises fxml.ParserError if the XML could 80 not be parsed. 81 82 @param file: An open file object, or the name of a file. Note 83 that if you pass a file object, this function will leave the 84 file open. 85 @type file: File object; can be a duck file like StringIO. 86 Alternately, the path of a file on disk. 87 """ 88 self.debug('Parsing XML from %r', file) 89 try: 90 return minidom.parse(file) 91 except expat.ExpatError, e: 92 raise self.parserError('Error parsing XML from %r: %s' % ( 93 file, log.getExceptionMessage(e)))
94
95 - def checkAttributes(self, node, required=None, optional=None):
96 """ 97 Checks that a given XML node has all of the required attributes, 98 and no unknown attributes. Raises fxml.ParserError if unknown 99 or missing attributes are detected. An empty attribute (e.g. 100 'foo=""') is treated as a missing attribute. 101 102 @param node: An XML DOM node. 103 @type node: L{xml.dom.Node} 104 @param required: Set of required attributes, or None. 105 @type required: Sequence (list, tuple, ...) of strings. 106 @param optional: Set of optional attributes, or None. 107 @type optional: Sequence (list, tuple, ...) of strings. 108 """ 109 attrs = sets.Set([k for k in node.attributes.keys() 110 if node.getAttribute(k)]) 111 required = sets.Set(required or ()) 112 optional = sets.Set(optional or ()) 113 for x in attrs - required.union(optional): 114 raise self.parserError("Unknown attribute in <%s>: %s" 115 % (node.nodeName, x)) 116 for x in required - attrs: 117 raise self.parserError("Missing attribute in <%s>: %s" 118 % (node.nodeName, x))
119
120 - def parseAttributes(self, node, required=None, optional=None, 121 type=str):
122 """ 123 Checks the validity of the attributes on an XML node, via 124 Parser.checkAttributes, then parses them out and returns them 125 all as a tuple. 126 127 @param node: An XML DOM node. 128 @type node: L{xml.dom.Node} 129 @param required: Set of required attributes, or None. 130 @type required: Sequence (list, tuple, ...) of strings. 131 @param optional: Set of optional attributes, or None. 132 @type optional: Sequence (list, tuple, ...) of strings. 133 @param type: Type to which to cast attribute values. The 134 original values will always be unicode objects; in most cases 135 you want `str' objects, so this defaults to `str'. 136 @type type: Function of type object -> object. 137 138 @returns: List of all attributes as a tuple. The first element 139 of the returned tuple will be the value of the first required 140 attribute, the second the value of the second required 141 attribute, and so on. The optional attributes follow, with None 142 as the value if the optional attribute was not present. 143 @rtype: tuple of string or None, as long as the combined length 144 of the required and optional attributes. 145 """ 146 self.checkAttributes(node, required, optional) 147 out = [] 148 for k in (required or ()) + (optional or ()): 149 if node.hasAttribute(k): 150 # note that 'a' is of type 'unicode' 151 a = node.getAttribute(k) 152 if a: 153 out.append(type(a)) 154 else: 155 out.append(None) 156 else: 157 out.append(None) 158 return out
159
160 - def parseFromTable(self, parent, parsers):
161 """ 162 A data-driven verifying XML parser. Raises fxml.ParserError if 163 an unexpected child node is encountered. 164 165 @param parent: An XML node whose child nodes you are interested 166 in parsing. 167 @type parent: L{xml.dom.Node} 168 @param parsers: A parse table defining how to parse the child 169 nodes. The keys are the possible child nodes, and the value is a 170 two-tuple of how to parse them consisting of a parser and a 171 value handler. The parser is a one-argument function that will 172 be called with the child node as an argument, and the handler is 173 a one-argument function that will be called with the result of 174 calling the parser. 175 @type parsers: dict of string -> (function, function) 176 """ 177 for child in parent.childNodes: 178 if (child.nodeType == Node.TEXT_NODE or 179 child.nodeType == Node.COMMENT_NODE): 180 continue 181 try: 182 parser, handler = parsers[child.nodeName] 183 except KeyError: 184 raise self.parserError("unexpected node in <%s>: %s" 185 % (parent.nodeName, child)) 186 handler(parser(child))
187
188 - def parseTextNode(self, node, type=str):
189 """Parse a text-containing XML node. 190 191 The node is expected to contain only text children. Recognized 192 node types are L{xml.dom.Node.TEXT_NODE} and 193 L{xml.dom.Node.CDATA_SECTION_NODE}. 194 195 @param node: the node to parse 196 @type node: L{xml.dom.Node} 197 @param type: a function to call on the resulting text 198 @type type: function of type unicode -> object 199 200 @returns: The result of calling type on the unicode text. By 201 default, type is L{str}. 202 """ 203 ret = [] 204 for child in node.childNodes: 205 if (child.nodeType == Node.TEXT_NODE 206 or child.nodeType == Node.CDATA_SECTION_NODE): 207 ret.append(child.data) 208 elif child.nodeType == Node.COMMENT_NODE: 209 continue 210 else: 211 raise self.parserError('unexpected non-text content of ' 212 '%r: %r' % (node, child)) 213 try: 214 return type(''.join(ret)) 215 except Exception, e: 216 raise self.parserError('failed to parse %s as %s: %s', node, 217 type, log.getExceptionMessage(e))
218