1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286
|
"""
Contributed by Joshua R English, adapted by Oliver Schoenborn to be
consistent with pubsub API.
An extension for pubsub (http://pubsub.sourceforge.net) so topic tree
specification can be encoded in XML format rather than pubsub's default
Python nested class format.
To use:
xml = '''
<topicdefntree>
<description>Test showing topic hierarchy and inheritance</description>
<topic id="parent">
<description>Parent with a parameter and subtopics</description>
<listenerspec>
<arg id="name" optional="true">given name</arg>
<arg id="lastname">surname</arg>
</listenerspec>
<topic id="child">
<description>This is the first child</description>
<listenerspec>
<arg id="nick">A nickname</arg>
</listenerspec>
</topic>
</topic>
</topicdefntree>
'''
These topic definitions are loaded through an XmlTopicDefnProvider:
pub.addTopicDefnProvider( XmlTopicDefnProvider(xml) )
The XmlTopicDefnProvider also accepts a filename instead of XML string:
provider = XmlTopicDefnProvider("path/to/XMLfile.xml", TOPIC_TREE_FROM_FILE)
pub.addTopicDefnProvider( provider )
Topics can be exported to an XML file using the exportTopicTreeSpecXml function.
This will create a text file for the XML and return the string representation
of the XML tree.
:copyright: Copyright since 2013 by Oliver Schoenborn, all rights reserved.
:license: BSD, see LICENSE_BSD_Simple.txt for details.
"""
__author__ = 'Joshua R English'
__revision__ = 6
__date__ = '2013-07-27'
from ..core.topictreetraverser import ITopicTreeVisitor
from ..core.topicdefnprovider import (
ITopicDefnProvider,
ArgSpecGiven,
TOPIC_TREE_FROM_STRING,
)
from .. import py2and3
try:
from elementtree import ElementTree as ET
except ImportError:
try: # for Python 2.4, must use cElementTree:
from xml.etree import ElementTree as ET
except ImportError:
from cElementTree import ElementTree as ET
__all__ = [
'XmlTopicDefnProvider',
'exportTopicTreeSpecXml',
'TOPIC_TREE_FROM_FILE'
]
def _get_elem(elem):
"""Assume an ETree.Element object or a string representation.
Return the ETree.Element object"""
if not ET.iselement(elem):
try:
elem = ET.fromstring(elem)
except:
py2and3.print_("Value Error", elem)
raise ValueError("Cannot convert to element")
return elem
TOPIC_TREE_FROM_FILE = 'file'
class XmlTopicDefnProvider(ITopicDefnProvider):
class XmlParserError(RuntimeError): pass
class UnrecognizedSourceFormatError(ValueError): pass
def __init__(self, xml, format=TOPIC_TREE_FROM_STRING):
self._topics = {}
self._treeDoc = ''
if format == TOPIC_TREE_FROM_FILE:
self._parse_tree(_get_elem(open(xml,mode="r").read()))
elif format == TOPIC_TREE_FROM_STRING:
self._parse_tree(_get_elem(xml))
else:
raise UnrecognizedSourceFormatError()
def _parse_tree(self, tree):
doc_node = tree.find('description')
if doc_node is None:
self._treeDoc = "UNDOCUMENTED"
else:
self._treeDoc = ' '.join(doc_node.text.split())
for node in tree.findall('topic'):
self._parse_topic(node)
def _parse_topic(self, node, parents=None, specs=None, reqlist=None):
parents = parents or []
specs = specs or {}
reqlist = reqlist or []
descNode = node.find('description')
if descNode is None:
desc = "UNDOCUMENTED"
else:
desc = ' '.join(descNode.text.split())
node_id = node.get('id')
if node_id is None:
raise XmlParserError("topic element must have an id attribute")
for this in (node.findall('listenerspec/arg')):
this_id = this.get('id')
if this_id is None:
raise XmlParserError("arg element must have an id attribute")
this_desc = this.text.strip()
this_desc = this_desc or "UNDOCUMENTED"
this_desc = ' '.join(this_desc.split())
specs[this_id] = this_desc
if this.get('optional', '').lower() not in ['true', 't','yes','y']:
reqlist.append(this_id)
defn = ArgSpecGiven(specs, tuple(reqlist))
parents.append(node.get('id'))
self._topics[tuple(parents)] = desc, defn
for subtopic in node.findall('topic'):
self._parse_topic(subtopic, parents[:], specs.copy(), reqlist[:])
def getDefn(self, topicNameTuple):
return self._topics.get(topicNameTuple, (None, None))
def topicNames(self):
return py2and3.iterkeys(self._topics) # dict_keys iter in 3, list in 2
def getTreeDoc(self):
return self._treeDoc
class XmlVisitor(ITopicTreeVisitor):
def __init__(self, elem):
self.tree = elem
self.known_topics = []
def _startTraversal(self):
self.roots = [self.tree]
def _onTopic(self, topicObj):
if topicObj.isAll():
self.last_elem = self.tree
return
if self.roots:
this_elem = ET.SubElement(self.roots[-1], 'topic',
{'id':topicObj.getNodeName()})
else:
this_elem = ET.Element('topic', {'id':topicObj.getNodeName()})
req, opt = topicObj.getArgs()
req = req or ()
opt = opt or ()
desc_elem = ET.SubElement(this_elem, 'description')
topicDesc = topicObj.getDescription()
if topicDesc:
desc_elem.text = ' '.join(topicDesc.split())
else:
desc_elem.text = "UNDOCUMENTED"
argDescriptions = topicObj.getArgDescriptions()
# pubsub way of getting known_args
known_args = []
parent = topicObj.getParent()
while parent:
if parent in self.known_topics:
p_req, p_opt = parent.getArgs()
if p_req:
known_args.extend(p_req)
if p_opt:
known_args.extend(p_opt)
parent = parent.getParent()
# there is probably a cleaner way to do this
if req or opt:
spec = ET.SubElement(this_elem, 'listenerspec')
for arg in req:
if arg in known_args:
continue
arg_elem = ET.SubElement(spec, 'arg', {'id': arg})
arg_elem.text = ' '.join(argDescriptions.get(arg, 'UNDOCUMENTED').split())
for arg in opt:
if arg in known_args:
continue
arg_elem = ET.SubElement(spec, 'arg', {'id': arg, 'optional':'True'})
arg_elem.text = ' '.join(argDescriptions.get(arg, 'UNDOCUMENTED').split())
self.last_elem = this_elem
self.known_topics.append(topicObj)
def _startChildren(self):
self.roots.append(self.last_elem)
def _endChildren(self):
self.roots.pop()
## http://infix.se/2007/02/06/gentlemen-indent-your-xml
def indent(elem, level=0):
i = "\n" + level*" "
if len(elem):
if not elem.text or not elem.text.strip():
elem.text = i + " "
for e in elem:
indent(e, level+1)
if not e.tail or not e.tail.strip():
e.tail = i + " "
if not e.tail or not e.tail.strip():
e.tail = i
else:
if level and (not elem.tail or not elem.tail.strip()):
elem.tail = i
else:
elem.tail="\n"
def exportTopicTreeSpecXml(moduleName=None, rootTopic=None, bak='bak', moduleDoc=None):
"""
If rootTopic is None, then pub.getDefaultTopicTreeRoot() is assumed.
"""
if rootTopic is None:
from .. import pub
rootTopic = pub.getDefaultTopicTreeRoot()
elif py2and3.isstring(rootTopic):
from .. import pub
rootTopic = pub.getTopic(rootTopic)
tree = ET.Element('topicdefntree')
if moduleDoc:
mod_desc = ET.SubElement(tree, 'description')
mod_desc.text = ' '.join(moduleDoc.split())
traverser = pub.TopicTreeTraverser(XmlVisitor(tree))
traverser.traverse(rootTopic)
indent(tree)
if moduleName:
filename = '%s.xml' % moduleName
if bak:
pub._backupIfExists(filename, bak)
fulltree= ET.ElementTree(tree)
fulltree.write(filename, "utf-8", True)
return ET.tostring(tree)
|