'''
Read in a configuration file and output one or more configuration files, each
of which contain a single Motion, Axis, Filter and Motor object. Each object is
renamed as "Object 0". For example, the file for Axis 2 would include Motion 2
(renamed "Motion 0"), Axis 2 (renamed "Axis 0"), Filter 2 (renamed "Filter 0")
and Motor 2 (renamed "Motor 0"). All non-portable configuration elements, such
as firmware addresses and object mappings, are stripped from the output.
'''
import sys
# The following is necessary in order to handle UNICODE characters properly.
if not hasattr(sys, 'setdefaultencoding'):
reload(sys)
if hasattr(sys, 'setdefaultencoding'):
sys.setdefaultencoding("utf-8")
del sys.setdefaultencoding
import types
import sets
import gc
from xml.sax import saxutils, make_parser, SAXException, SAXParseException
from xml.dom import minidom
from xml.parsers.expat import ExpatError
_usage = '''%s [inputFile]
inputFile Optional meiConfig configuration file. If omitted, input is
taken from stdin.
Transform the input file, into multiple configuration files. One configuration
file will be created for each Motion/Axis/Filter/Motor set of objects. Each
object is renamed as "Object 0". For example, the file for Axis 2 would
include Motion 2, Axis 2, Filter 2 and Motor 2.
'''
################################################
## classes
class Splitter(saxutils.XMLGenerator):
'''This is a general purpose class that will create a DOM tree from an input
XML file. The dom tree will contain only the elements (and sub-elements)
listed in the elemPathList creation argument.
The DOM elements created for the items listed in elemPathList are not
attached to their parents, making it easier to create multiple files
containing only one of each type of object.
'''
def __init__(self
, elemPathList
, includeElemPathList = []
, excludeElemPathList = []
, encoding = "iso-8859-1"
, warningWriter = sys.stderr
):
self.warningWriter = warningWriter
saxutils.XMLGenerator.__init__(self, NullWriter(), encoding)
self.elemPathList = elemPathList
self.elemPathSet = sets.Set(elemPathList)
self.includeElemPathSet = sets.Set(includeElemPathList)
self.excludeElemPathSet = sets.Set(excludeElemPathList)
self.elemPathSetAll = self.includeElemPathSet | self.elemPathSet
self.nodeMap = {}
self.elementStack = []
self.nodeTypeStack = []
self.idStack = []
self.domElemStack = []
self.dom = None
def characters(self, content):
pass
def documentWrite(self, writer):
self.dom.writexml(writer, indent = '\t', addindent = '\t', newl = '\n')
def elementAppend(self, domElemParent, domElem, idPath, elemPath):
# Don't attach the nodes that are going to be multiplexed out
if elemPath not in self.elemPathSet:
domElemParent.appendChild(domElem)
def elementCreate(self, name, attrs, idPath, elemPath):
domElem = self.dom.createElement(name)
for (attr, value) in attrs.items():
domElem.setAttribute(attr, value)
return domElem
def endElement(self, name):
elementName = self.elementStack.pop(-1)
nodeType = self.nodeTypeStack.pop(-1)
id = self.idStack.pop(-1)
domElem = self.domElemStack.pop(-1)
def endElementNS(self, name, qname):
pass
def ignorableWhitespace(self, content):
pass
def processingInstruction(self, target, data):
pass
def startDocument(self):
dom = minidom.Document()
self.domElemStack.append(dom)
self.dom = dom
def startElement(self, name, attrs):
self.elementStack.append(name)
nodeType = attrs.get(attrNameNodeType, None)
if nodeType is None:
raise InvalidXMLFile(
'Invalid file: no nodeType for %s' % (name, )
)
nodeTypeStack = self.nodeTypeStack
id = name
if attrNameID in attrs:
id = attrs[attrNameID]
parentNodeType = None
if len(nodeTypeStack):
parentNodeType = nodeTypeStack[-1]
if parentNodeType == nodeTypePROPERTY_LIST:
n = attrs[attrNameListElem]
id += ' ' + n
self.idStack.append(id)
idPath = '.'.join(self.idStack)
nodeTypeStack.append(nodeType)
elemPath = '.'.join(self.elementStack)
domElem = None
domElemStack = self.domElemStack
domElemParent = domElemStack[-1]
elemInPathSet = elemPath in self.elemPathSetAll
if not elemInPathSet:
found = False
for p in self.elemPathList:
if p.find(elemPath) == 0 or elemPath.find(p) == 0:
found = True
break
if not found:
name = None
if elemPath in self.excludeElemPathSet:
name = None
if name is not None and domElemParent is not None:
domElem = self.elementCreate(name, attrs, idPath, elemPath)
self.elementAppend(domElemParent, domElem, idPath, elemPath)
if elemInPathSet:
self.nodeMap.setdefault(idPath, []).append(
(domElemParent, domElem)
)
domElemStack.append(domElem)
def startElementNS(self, name, qname, attrs):
pass
def unlink(self):
if self.dom is not None:
# Attach all nodes so that they can all be destroyed
for elemDataList in self.nodeMap.values():
for elemData in elemDataList:
domElemParent, domElem = elemData
domElemParent.appendChild(domElem)
self.dom.unlink()
self.dom = None
self.nodeMap = {}
class NullWriter(object):
def write(self, text):
pass
################################################
## Exceptions
_Exception = Exception
class Exception(_Exception):
''' This is a base class for all exceptions generated in this module. '''
valueTypes = types.StringTypes
def __init__(self, value):
assert type(value) in self.valueTypes
self.value = value
_Exception.__init__(self, value)
def __str__(self):
return self.value
class FileIoError(Exception):
pass
class InvalidXMLFile(Exception):
pass
class ParserError(Exception):
pass
################################################
## Miscellaneous Constants
FILENAME_PREFIX = ',mafm'
MAX_OBJ_COUNT = 64
# Set MEMORY_CHECK to True in order to check for memory leaks
MEMORY_CHECK = False
# Names for the various node types in a .XMC file.
nodeTypePROPERTY = 'property'
nodeTypePROPERTY_LIST = 'propertyList'
nodeTypeOBJECT = 'object'
nodeTypeOBJECT_LIST = 'objectList'
# Names for various node attributes in a .XMC file.
attrNameListElem = '_n'
attrNameListLength = 'length'
attrNameValue = 'value'
attrNameValueList = 'valueList'
attrNameNodeType = 'nodeType'
attrNameObjectType = 'objectType'
attrNameHex = 'hex'
attrNameID = 'id'
################################################
## Functions
def main(argv):
# Create an instance of a Splitter class and use it to create a DOM tree
# of the input file. Only the elements necessary to create the paths listed
# in elemPathList will attached to the DOM tree.
if MEMORY_CHECK:
# Check for memory leaks
gc.enable()
gc.set_debug(gc.DEBUG_LEAK)
# Argument checking
lenArgv = len(argv)
if lenArgv > 2:
sys.stderr.write(_usage)
sys.exit(-1)
if lenArgv <= 1:
input = sys.stdin
else:
input = argv[1]
# elemPathList:
# Paths to element types that are to be included in the output files
elemPathList = [
'MeiXmlConfig.Control.AxisList.Axis',
'MeiXmlConfig.Control.FilterList.Filter',
'MeiXmlConfig.Control.MotorList.Motor',
'MeiXmlConfig.Control.MotionList.Motion',
]
# idPathList:
# Paths used to access nodes stored in Splitter.nodeMap
idPathList = [
'MeiXmlConfig.Control 0.AxisList.Axis %d',
'MeiXmlConfig.Control 0.FilterList.Filter %d',
'MeiXmlConfig.Control 0.MotorList.Motor %d',
'MeiXmlConfig.Control 0.MotionList.Motion %d',
]
# includeElemPathList:
# Paths to elements that are to be included in all the output files.
includeElemPathList = [
'MeiXmlConfig.MPI_VERSION_INTERFACE',
'MeiXmlConfig.BUILD_NO',
]
# excludeElemPathList:
# Paths to elements that are to be excluded from all the output files.
excludeElemPathList = [
# exclude object mapping and firmware address elements
# Axis:
'MeiXmlConfig.Control.AxisList.Axis.MPIAxisConfig.feedbackPtr',
'MeiXmlConfig.Control.AxisList.Axis.MPIAxisConfig.gear.master.data.custom.address',
'MeiXmlConfig.Control.AxisList.Axis.MPIAxisConfig.master.address',
# Filter:
'MeiXmlConfig.Control.FilterList.Filter.MPIFilterConfig.velPtr',
# Motor:
'MeiXmlConfig.Control.MotorList.Motor.MPIMotorConfig.commutation.input.input.ptr',
'MeiXmlConfig.Control.MotorList.Motor.MPIMotorConfig.statusOutput.outPtr',
]
try:
splitter = Parse(input
, contentHandlerClass = Splitter
, elemPathList = elemPathList
, includeElemPathList = includeElemPathList
, excludeElemPathList = excludeElemPathList
)
except Exception, exc:
sys.stderr.write('Transformation failed: %s' % (str(exc), ) + '\n')
else:
errMsg = None
objCount = 0
# Calculate objCount, the highest index for the objects to be split out
# into separate files.
# Also look for multiple objects with the same ID
for objNdx in range(MAX_OBJ_COUNT):
if errMsg is not None:
break
for idPathFmt in idPathList:
idPath = idPathFmt % objNdx
objList = splitter.nodeMap.get(idPath, [])
lenObjList = len(objList)
if lenObjList == 0:
# Stop as soon as there is no object for the objNdx
errMsg = ''
elif lenObjList == 1:
objCount = objNdx
else:
errMsg = 'Multiple objects with ID %s' % idPath
break
if errMsg == '' or errMsg is None:
for objNdx in range(objCount):
# Create the output file
source = '%s_%d.xmc' % (FILENAME_PREFIX, objNdx)
try:
writer = open(source, 'w')
except IOError, exc:
errMsg = '%s: %s' % (exc.strerror, source)
sys.stderr.write(errMsg + '\n')
continue
# Link the nodes for all objects associated with objNdx up with
# their parent nodes.
for idPathFmt in idPathList:
idPath = idPathFmt % objNdx
domElemParent, domElem = splitter.nodeMap[idPath][0]
domElemParent.appendChild(domElem)
# Change the id of the element to be 0
idNameFmt = idPathFmt.split('.')[-1]
idNew = idNameFmt % 0
domElem.setAttribute(attrNameID, idNew)
# Save the new configuration file
print 'Saving %s' % source
splitter.documentWrite(writer)
writer.close()
# Undo the linking performed above
for idPathFmt in idPathList:
idPath = idPathFmt % objNdx
domElemParent, domElem = splitter.nodeMap[idPath][0]
domElemParent.removeChild(domElem)
else:
sys.stderr.write(errMsg + '\n')
# Free up memory attached to splitter
splitter.unlink()
if MEMORY_CHECK:
# Check for memory leaks
memLeakDump(140)
def memLeakDump(lenLine = 80):
'''Use the gc module to check for memory leaks.'''
assert lenLine > 4
if not MEMORY_CHECK:
return
gc.collect()
if len(gc.garbage):
print 'GARBAGE'
for x in gc.garbage:
s = str(x)
if len(s) > lenLine: s = s[:lenLine - 3] + '...'
print '%s: %s' % (`type(x)`, s)
def Parse(input
, contentHandlerClass = Splitter
, *args, **kwargs
):
'''Parse the input using the given contentHandlerClass. Exceptions
generated by the parser are consolidated into exceptions defined in this
module so that they can be handled by a single try-except statement.'''
try:
parser = make_parser()
except SAXException, exc:
raise ParserError(`exc`)
contentHandler = contentHandlerClass(*args, **kwargs)
try:
try:
parser.setContentHandler(contentHandler)
parser.parse(input)
except SAXParseException, exc:
contentHandler.unlink()
raise InvalidXMLFile(
'Parser error at line %d: %s' % (
exc.getLineNumber(),
exc.getMessage(),
)
)
except (SAXException, ExpatError), exc:
contentHandler.unlink()
raise InvalidXMLFile(`exc`)
except IOError, exc:
raise FileIoError('%s: %s' % (exc.strerror, input))
finally:
# There is a bug in the saxutils.XMLGenerator class that causes a
# memory leak if there is an exception while parsing. The following
# statement breaks the cycle created by expat handlers pointing to
# parser methods.
parser._parser = None
return contentHandler
def _test(testFile):
'''Parse testFile.'''
import os
op = os.path
main([
sys.argv[0],
testFile
])
if __name__ == '__main__':
# To test, set "test" to True and set testFile to the path to a test .XMC
# file.
test = False
testFile = r"testDir/meiConfig.xmc"
if test:
_test(testFile)
else:
main(sys.argv)