OMSNameDocXML.py010066400007650000024000000452050770343754100126020ustar00dougstaff#! /usr/bin/env python ############################################################################################## ## Copyright 2003 by Doug Wyatt / Sonosphere, Sunnyvale CA / Ithaca NY ## ## All Rights Reserved ## ## Permission to use, copy, modify, and distribute this software and its documentation ## for any purpose and without fee is hereby granted, provided that the above copyright ## notice appears in all copies and that both that copyright notice and this permission ## notice appear in supporting documentation, and that the name of Sonosphere or ## the author not be used in advertising or publicity pertaining to distribution of the ## software without specific, written prior permission. ## ## SONOSPHERE AND THE AUTHOR DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, ## INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL ## LIVINGLOGIC AG OR THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL ## DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER ## IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR ## IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ############################################################################################## ''' OMSNameDocXML.py Version 0.2, 10 Jul 2003 Python library for reading OMS name docs and converting them to XML. Currently undocumented. ''' import struct import array import string import re from dswxml import * ### options kPlainTextNCLists = 0 ### constants kNoteList = 0 kControlList = 1 if 1: sysid = "http://www.midi.org/dtds/MIDINameDocument10.dtd" pubid = "-//MIDI Manufacturers Association//DTD MIDINameDocument 1.0//EN" dtd = sysid else: sysid = None pubid = None ################################################################################################ def boolean(x): if x: return 'true' else: return 'false' def Read(f, n, fmt): return struct.unpack(fmt, f.read(n)) def Write(f, fmt, *values): l = len(values) if l == 1: data = struct.pack(fmt, values[0]) elif l == 2: data = struct.pack(fmt, values[0], values[1]) elif l == 3: data = struct.pack(fmt, values[0], values[1], values[2]) elif l == 4: data = struct.pack(fmt, values[0], values[1], values[2], values[3]) else: raise Exception('too many values to write') f.write(data) def ReadByte(f): return ord(f.read(1)) re_nonprint = re.compile('[^ -~]') re_control = re.compile('[\x00-\x1F\x7F]+') def dumpstr(s): dump = '' for c in s: v = ord(c) if v >= 0x20 and v < 127: dump += c elif v < 0x100: dump += ' $%02X ' % v else: dump += " $%04X " % v return dump def MacRomanToUnicode(s): m = re_nonprint.search(s) if m: prev = s # kill control characters s, nsub = re.subn(re_control, '', s) if nsub: print '# removed control characters from %s (%s)' % (s, dumpstr(prev)) prev = s #s = string.replace(s, '\xC9', '...') #s = string.replace(s, '\xD2', '"') #s = string.replace(s, '\xD3', '"') #s = string.replace(s, '\xD5', "'") s = string.replace(s, '\xB4', "'") # think this is a Windows apostrophe m = re_nonprint.search(s) if m: s = s.decode('macroman') print '# encoded unicode in', #unicode(s), print '(%s)' % dumpstr(s) return s def ReadPStr(f): s = f.read(ReadByte(f)) # macroman s = string.replace(s, '\0', '') # Nord is full of 'em s = string.strip(string.replace(s, '\t', ' ')) return MacRomanToUnicode(s) def PStr(s): return chr(len(s)) + s def hexstr(a): return ' '.join( ['%02X' for c in a] ) def parsehex(s): arr = array.array('B') bytes = string.split(str(s)) for b in bytes: arr.append(int(b, 16)) return arr def WriteMIDI(x, tag, msg): n = len(msg) if n == 0: return x.BeginElement(tag) i = 0 while i < n: c = msg[i] & 0xF0 if c == 0xB0: x.Element('ControlChange', (XMLAttr('Control', msg[i+1]), XMLAttr('Value', msg[i+2]))) i += 3 elif c == 0xC0: x.Element('ProgramChange', XMLAttr('Number', msg[i+1])) i += 2 elif msg[i] == 0xFF: x.Element('MIDIDelay', XMLAttr('Milliseconds', msg[i+1])) i += 2 else: raise Exception("unknown MIDI message: " + hexstr(msg)) x.EndElement(tag) ################################################################################################ re_emptyNCName = re.compile('\[[0-9]*\]') class NCList(XMLElement): def __init__(self, listID=-1): self.listID = listID self.names = {} def ReadOMS(self, f): f.read(8) # skip window loc version, numNames = Read(f, 4, '>hh') if version != 1: raise Exception("Note or control name list %d's version is %d (1 expected)" % (self.listID, version)) for i in range(0, numNames): n = ReadPStr(f) if len(n) > 0: self.names[i] = n def WriteOMS(self, f): s = struct.pack('>hh', 1, 128) # version, numNames for i in range(0, 128): if self.names.has_key(i): name = self.names[i] else: name = '' s += PStr(name) Write(f, '>hlB', len(s), self.listID, self.sListType) f.write(8 * '\0') # window location f.write(s) def WriteXML(self, x): x.BeginElement(self.sListTag, XMLAttr('Name', self.listID)) if kPlainTextNCLists: for i in range(0, 128): if self.names.has_key(i): name = self.names[i] else: name = '[%d]' % i x.write('%s\n' % name) else: keys = self.names.keys() keys.sort() for k in keys: x.Element(self.sItemTag, (XMLAttr('Number', k), XMLAttr('Name', self.names[k]))) x.EndElement(self.sListTag) x.write('\n') def xml_data(self, data): if len(string.strip(data)) == 0: return lines = string.split(data, '\n')[1:-1] i = 0 for lin in lines: if re_emptyNCName.match(lin): pass else: #print 'name',i,'=',lin self.names[i] = lin i += 1 # manipulation def Set(self, index, name): self.names[index] = name class NoteNameList(NCList): sListTag = 'NoteNameList' sItemTag = 'Note' sListType = kNoteList def xml_note(self, el): self.names[int(el.number)] = unicode(el.name) class ControlNameList(NCList): sListTag = 'ControlNameList' sItemTag = 'Control' sListType = kControlList def xml_control(self, el): self.names[int(el.number)] = unicode(el.name) ################################################################################################ class NCListOwner(XMLElement): def __init__(self): self.noteNames = -1 self.controlNames = -1 def WriteOMSNoteControlNameRefs(self, f, index): if self.noteNames != -1: Write(f, '>Bhl', kNoteList, index, self.noteNames) if self.controlNames != -1: Write(f, '>Bhl', kControlList, index, self.controlNames) def WriteXMLNoteControlNameRefs(self, x): if self.noteNames != -1: x.Element('UsesNoteNameList', XMLAttr('Name', self.noteNames)) if self.controlNames != -1: x.Element('UsesControlNameList', XMLAttr('Name', self.controlNames)) def HasNCNames(self): return self.noteNames != -1 or self.controlNames != -1 def xml_usesNoteNameList(self, nclist): self.noteNames = int(nclist.id) def xml_usesControlNameList(self, nclist): self.controlNames = int(nclist.id) ################################################################################################ class Patch(NCListOwner): def __init__(self): NCListOwner.__init__(self) self.gmEquiv = -1 def SameAs(a, b): if a.noteNames != b.noteNames or a.controlNames != b.controlNames: return 0 if a.patchName != b.patchName or a.patchNumStr != b.patchNumStr: return 0 if a.bankMIDI != b.bankMIDI: return 0 if a.patchMIDI != b.patchMIDI: return 0 return 1 def ReadOMS(self, f): self.patchID, self.gmEquiv = Read(f, 4, '>hh') self.bankMIDI = [ ] self.patchMIDI = [ ] self.bankKeyStr = "" x = ReadByte(f) if x == 0xFF: x = ReadByte(f) if x != 0xFF: self.bankMIDI += [0xB0, 0x00, x ] self.bankKeyStr += '%02X' % x x = ReadByte(f) if x != 0xFF: self.bankMIDI += [0xB0, 0x20, x] self.bankKeyStr += '%02X' % x x = ReadByte(f) self.patchMIDI += [0xC0, x] else: while 1: # got status byte in x if (x & 0xE0) == 0xC0: if len(self.patchMIDI) > 0: self.bankMIDI += self.patchMIDI self.bankKeyStr += '%02X' % self.patchMIDI[-1] self.patchMIDI = [ ] pc = ReadByte(f) self.patchMIDI += [x, pc] elif (x & 0xF0) == 0xB0: ctl = ReadByte(f) val = ReadByte(f) self.bankMIDI += [x, ctl, val] if ctl == 0x00: self.bankKeyStr += '%02X' % val if ctl == 0x20: self.bankKeyStr += '%02X' % val else: raise Exception("unsupported patch MIDI status byte: %02X" % x) # read delay or terminator x = ReadByte(f) if x == 0xFF: break self.bankMIDI += [0xFF, (x * 100 + 30) / 60] # read next status byte x = ReadByte(f) #print hexstr(self.bankMIDI), ':', hexstr(self.patchMIDI) self.patchName = ReadPStr(f) self.patchNumStr = ReadPStr(f) #print self.patchNumStr, self.patchName def ToOMS(self): s = struct.pack('>hh', self.patchID, self.gmEquiv) s += self.midiMsg.tostring() s += PStr(self.patchName) s += PStr(self.patchNumStr) return s def WriteXML(self, x): simplePC = (len(self.patchMIDI) == 2) and self.patchMIDI[0] == 0xC0 atts = [ XMLAttr('Number', self.patchNumStr), XMLAttr('Name', self.patchName) ] if simplePC: atts.append( XMLAttr('ProgramChange', self.patchMIDI[1]) ) if not self.HasNCNames(): # and self.gmEquiv == -1 x.Element('Patch', atts) else: x.BeginElement('Patch', atts) #,XMLAttr('omsid', self.patchID)) #if self.gmEquiv != -1: # x.Element('gmEquiv', None, `self.gmEquiv`) if not simplePC: WriteMIDI(x, 'PatchMIDICommands', self.patchMIDI) self.WriteXMLNoteControlNameRefs(x) x.EndElement('Patch') def xml_midi(self, midi): self.midiMsg = parsehex(midi.Data()) def xml_number(self, num): self.patchNumStr = unicode(num.Data()) def xml_name(self, name): self.patchName = unicode(name.Data()) ################################################################################################ class PatchNameList: def __init__(self, name): self.name = name self.patches = [ ] def WriteXML(self, x): x.BeginElement('PatchNameList', XMLAttr('Name', self.name)) for patch in self.patches: patch.WriteXML(x) x.EndElement('PatchNameList') x.write('\n') def SameAs(a, b): n = len(a.patches) if n != len(b.patches): return 0 for i in range(0, n): if not a.patches[i].SameAs(b.patches[i]): return 0 return 1 ################################################################################################ class Bank: def __init__(self, modeName, name, midiMsg): self.name = '%s %s' % (modeName, name) self.midiMsg = midiMsg self.isRom = 0 self.patchNameList = PatchNameList(self.name) #print ' ',self.name def WriteXML(self, x): x.BeginElement('PatchBank', (XMLAttr('Name', self.name), XMLAttr('ROM', boolean(self.isRom)))) if len(self.midiMsg): WriteMIDI(x, 'MIDICommands', self.midiMsg) x.Element('UsesPatchNameList', XMLAttr('Name', self.patchNameList.name)) x.EndElement('PatchBank') ################################################################################################ class Mode(NCListOwner): def __init__(self): NCListOwner.__init__(self) self.patches = [] self.banks = [] self.availableForChannels = [ 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16 ] self.defaultForChannels = [ ] def ReadOMS(self, f): pstr = f.read(32) self.name = pstr[1:ord(pstr[0])+1] f.read(8) # skip window loc nlsize, version, numNames = Read(f, 8, '>lhh') if version == 1: for i in range(0, numNames): p = Patch() p.ReadOMS(f) self.patches.append(p) elif version == 0x100: pos = f.tell() endpos = pos + nlsize - 4 while pos < endpos: p = Patch() p.ReadOMS(f) self.patches.append(p) pos = f.tell() else: raise Exception(" ### Patch name list %s's version is %d" % (self.name, version)) while 1: listtype = ReadByte(f) if listtype == 0xFF: break patchNum, ncid = Read(f, 6, '>hl') #print listtype, patchNum, ncid if patchNum == -1: p = self; else: p = self.patches[patchNum] if listtype == kNoteList: p.noteNames = ncid else: p.controlNames = ncid def WriteOMS(self, f): n = len(self.name) if n > 31: n = 31 name = chr(n) + self.name[:n] + (31-n) * '\0' f.write(name) f.write(8 * '\0') # window loc s = '' for p in self.patches: s += p.ToOMS() Write(f, '>lhh', len(s), 1, len(self.patches)) # nlsize, version, nPatches f.write(s) self.WriteOMSNoteControlNameRefs(f, -1) i = 0 for p in self.patches: p.WriteOMSNoteControlNameRefs(f, i) i += 1 f.write('\xFF') def WriteXML(self, x): x.BeginElement('ChannelNameSet', XMLAttr('Name', self.name)) x.BeginElement('AvailableForChannels') for i in range(0, 16): x.Element('AvailableChannel', (XMLAttr('Channel', str(i+1)), XMLAttr('Available', boolean(self.availableForChannels[i])))) x.EndElement('AvailableForChannels') x.write('\n') self.WriteXMLNoteControlNameRefs(x) for bank in self.banks: bank.WriteXML(x) x.EndElement('ChannelNameSet') x.write('\n') def xml_patch(self, patch): self.patches.append(patch) ################################################################################################ kOMSSig = 0x6f6d4e50 kOMSVers = 3 class NameDoc(XMLElement): def __init__(self, author, f = None): self.noteCtlLists = [] self.patchNameLists = [] self.modes = [] self.defaultModes = array.array('B', 16*[0xFF]) self.valid = 0 self.author = author if f: self.valid = self.ReadOMS(f) def ReadOMS(self, f): # f may be filename if isinstance(f, (str, unicode)): f = open(f, 'rb') try: sig, version, nNoteAndControlNameLists = Read(f, 8, '>lhh') except: print "### error reading file, perhaps it's an alias" return 0 if sig != kOMSSig: raise Exception('file does not appear to be an OMS patch name document') if (version & 0x7FFF) != kOMSVers: raise Exception('Cannot read version %d OMS patch name document (3 expected)' % version) for i in range(0, nNoteAndControlNameLists): nlsize, listID, listtype = Read(f, 7, '>hlB') #print listID, listtype if listtype == kNoteList: nclist = NoteNameList(listID) else: nclist = ControlNameList(listID) nclist.ReadOMS(f) self.noteCtlLists.append(nclist) self.defaultModes = array.array('B') x = f.read(16) self.defaultModes.fromstring(x) reserved, numModes = Read(f, 4, '>hh') # read the modes, collecting the patches from the modes into banks # and patchNameLists and adjusting their MIDI messages accordingly. banks = [ ] for modeIndex in range(0, numModes): mode = Mode() mode.ReadOMS(f) self.modes.append(mode) bankByMIDI = { } for ch in range(0, 16): if self.defaultModes[ch] == modeIndex: mode.defaultForChannels.append(ch + 1) for patch in mode.patches: if not bankByMIDI.has_key(patch.bankKeyStr): bank = Bank(mode.name, patch.bankKeyStr, patch.bankMIDI) mode.banks.append(bank) banks.append(bank) bankByMIDI[patch.bankKeyStr] = bank else: bank = bankByMIDI[patch.bankKeyStr] bank.patchNameList.patches.append(patch) # search for duplicate patch name lists for ibank in range(0, len(banks)): ipnl = banks[ibank].patchNameList for jbank in range(0, ibank): jpnl = banks[jbank].patchNameList if ipnl.SameAs(jpnl): print '### dup patch name lists', ipnl.name, 'and', jpnl.name banks[ibank].patchNameList = jpnl ipnl = jpnl for bank in banks: if not bank.patchNameList in self.patchNameLists: self.patchNameLists.append(bank.patchNameList) return 1 def WriteOMS(self, f): if type(f) == type(''): f = open(f, 'wb') Write(f, '>lhh', kOMSSig, kOMSVers, len(self.noteCtlLists)) self.defaultModes.tofile(f) Write(f, '>hh', 0, len(self.modes)) for mode in self.modes: mode.WriteOMS(f) for nclist in self.noteCtlLists(): nclist.WriteOMS(f) def WriteXML(self, f, encoding, manuf, models): # f can be filename or file object x = XMLWriter(f, encoding) x.DocType('MIDINameDocument', pubid, sysid) x.BeginElement('MIDINameDocument') x.Element('Author', None, self.author) x.BeginElement('MasterDeviceNames') if manuf: x.Element('Manufacturer', None, manuf) for model in models: x.Element('Model', None, model) # Device mode x.BeginElement('CustomDeviceMode', XMLAttr('Name', '')) x.BeginElement('ChannelNameSetAssignments') for ch in range(0,16): x.Element('ChannelNameSetAssign', (XMLAttr('Channel', str(ch+1)), XMLAttr('NameSet', self.modes[self.defaultModes[ch]].name))) x.EndElement('ChannelNameSetAssignments') x.write('\n') x.EndElement('CustomDeviceMode') for mode in self.modes: mode.WriteXML(x) for pnl in self.patchNameLists: pnl.WriteXML(x) for nclist in self.noteCtlLists: nclist.WriteXML(x) x.EndElement('MasterDeviceNames') x.EndElement('MIDINameDocument') ### XML import def xml_noteNameList(self, list): self.noteCtlLists.append(list) def xml_controlNameList(self, list): self.noteCtlLists.append(list) def xml_patchSelectMode(self, mode): self.modes.append(mode) def xml_channelDefaultMode(self, dm): self.defaultModes[int(dm.channel)-1] = int(dm.mode) ### document manipulation def NCList(self, listtype, listID): if self.noteCtlLists.has_key(listID): nclist = self.noteCtlLists[listID] if nclist.listtype != listtype: raise Exception('wrong note/control list type') else: if listtype == kNoteList: nclist = NoteNameList(listID) else: nclist = ControlNameList(listID) self.noteCtlLists[listID] = nclist return nclist def NoteList(self, listID): return self.NCList(kNoteList, listID) def ControlList(self, listID): return self.NCList(kControlList, listID) ################################################################################################ ''' nameDocElems = { 'OMSPatchNameDoc' : [ NameDoc, EMPTY ], 'noteNameList' : [ NoteNameList, LITERAL, ATT(int, 'id', 'listID') ], 'note' : [ XMLElement, EMPTY, ATT(int, 'number'), ATT(str, 'name') ], 'controlNameList' : [ ControlNameList, LITERAL, ATT(int, 'id', 'listID') ], 'control' : [ XMLElement, EMPTY, ATT(int, 'number'), ATT(str, 'name') ], 'patchSelectMode' : [ Mode, EMPTY, ATT(str, 'id', 'name') ], 'usesNoteNameList' : [ XMLElement, EMPTY ], 'usesControlNameList' : [ XMLElement, EMPTY ], 'patch' : [ Patch, EMPTY, ATT(int, 'id', 'patchID'), ATT(int, 'gmEquiv') ], 'midi' : [ XMLElement, STRIPPED ], 'number' : [ XMLElement, STRIPPED ], 'name' : [ XMLElement, STRIPPED ], 'channelDefaultMode' : [ XMLElement, EMPTY ] } ''' def OMSReadXML(f): return dswxml.ReadXML(f, nameDocElems, 'OMSPatchNameDoc') dswxml.py010075500007650000024000000244050770343760100116470ustar00dougstaff############################################################################################## ## Copyright 2003 by Doug Wyatt / Sonosphere, Sunnyvale CA / Ithaca NY ## ## All Rights Reserved ## ## Permission to use, copy, modify, and distribute this software and its documentation ## for any purpose and without fee is hereby granted, provided that the above copyright ## notice appears in all copies and that both that copyright notice and this permission ## notice appear in supporting documentation, and that the name of Sonosphere or ## the author not be used in advertising or publicity pertaining to distribution of the ## software without specific, written prior permission. ## ## SONOSPHERE AND THE AUTHOR DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, ## INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL ## LIVINGLOGIC AG OR THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL ## DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER ## IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR ## IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ############################################################################################## ''' dswxml.py Version 0.2, 10 Jul 2003 Python library for reading/writing XML. Currently undocumented. ''' import re import string import xml.sax import xml.sax.saxutils import codecs kVerbose = 0 def sequence(x): if x == None: return tuple() if type(x) == tuple or type(x) == list: return x return (x, ) class literalstr(str): pass ################################################################################################ # DTD class ATTRIBUTE: ''' An ATT object describes an element's attribute. It has a Python data type, an XML name (what we see in the element start tag, and the XMLElement's member name into which this value will be converted to the correct type and stored. ''' def __init__(self, name, type, pyName=None): self.attrType = type self.attrName = name if pyName == None: pyName = name self.pyName = pyName class ELEMENT: def __init__(self, name, type = None, contentType = str): if type == None: type = XMLElement self.elemType = type self.elemName = name self.attrs = { } self.contentType = contentType # only used when type is subclass of XMLElement class XMLDTD: def __init__(self, *items): ''' Items is a list of ELEMENT and ATT objects; attributes apply to the preceding element. ''' self.elements = { } self.baseElement = None curElem = None for i in items: if isinstance(i, ELEMENT): self.elements[i.elemName] = i if self.baseElement == None: self.baseElement = i.elemName curElem = i elif isinstance(i, ATTRIBUTE): curElem.attrs[i.attrName] = i else: raise 'unknown DTD item: ' + `i` ################################################################################################ # Our XML parser reads elements into these objects, or subclasses of them. class XMLElement: def SetAttr(self, k, v): self.__dict__[k] = v def SetData(self, data): self._cdata = data def Data(self): return self._cdata def AddSubElement(self, name, obj): d = self.__dict__ if d.has_key(name): sub = d[name] if isinstance(sub, list): sub.append(obj) else: d[name] = [ sub, obj ] else: d[name] = obj def xml__start(self, parent): pass def xml__end(self, parent): pass class GenericXMLElement: def __init__(self): self.attrs = { } self.subs = { } self._cdata = '' def SetAttr(self, k, v): self.attrs[k] = v def SetData(self, data): self._cdata = data def Data(self): return self._cdata def xml__end(self, parent): pass def AddSubElement(self, name, obj): if self.subs.has_key(name): sub = self.subs[name] if type(sub) == list: sub.append(obj) else: self.subs[name] = [ sub, obj ] else: self.subs[name] = obj def Print(self, indent=0): keys = self.attrs.keys() if len(keys) == 1: print keys[0],'=',self.attrs[keys[0]] return dideol = 0 keys.sort() if len(keys) > 0: print dideol = 1 for k in keys: sub = self.attrs[k] print (indent*' ')+k+' :', self.attrs[k] if len(self.subs) > 0: keys = self.subs.keys() keys.sort() if dideol == 0: print for k in keys: sub = self.subs[k] print (indent*' ')+k+' :', PrintObject(indent + 1, sub) else: print self._cdata def PrintObject(indent, obj): if isinstance(obj, GenericXMLElement): obj.Print(indent) elif isinstance(obj, list): print for i in range(0, len(obj)): print (indent*' ')+('[%d] :' % i), PrintObject(indent + 1, obj[i]) else: print obj ################################################################################################ reg_ws = re.compile("[%s]+" % string.whitespace) class DSWContentHandler(xml.sax.ContentHandler): def __init__(self, dtd): self.baseObj = None self.dtd = dtd self.current = None self.stack = [] self.ignore = 0 def startElement(self, elementName, attrs): if self.dtd == None: #print 'start', elementName, dict(attrs) obj = GenericXMLElement() for a in attrs.keys(): obj.SetAttr(str(a), attrs[a]) self.keepChars = 1 if self.baseObj == None: self.baseObj = obj else: if self.dtd.elements.has_key(elementName) == 0: self.ignore += 1 print '### start unknown element:', elementName return dtdElement = self.dtd.elements[elementName] obj = dtdElement.elemType() if kVerbose: print 'start', elementName, dict(attrs), ':', obj if elementName == self.dtd.baseElement: self.baseObj = obj #print '*** base object ***' for attrName in attrs.keys(): if dtdElement.attrs.has_key(attrName): a = dtdElement.attrs[attrName] obj.SetAttr(a.pyName, a.attrType(attrs[attrName])) else: print '### unknown attribute %s of element %s:' % (attrName, elementName), attrs[attrName] if isinstance(obj, XMLElement): if kVerbose: print ' attrs:', obj.__dict__.keys() if dtdElement.contentType: if issubclass(dtdElement.contentType, literalstr): self.keepChars = 2 else: self.keepChars = 1 else: self.keepChars = 0 obj.xml__start(self.current) elif isinstance(obj, literalstr): self.keepChars = 2 else: self.keepChars = 1 self.stack.append(obj) self.current = obj self.curData = '' def characters(self, data): if self.ignore or self.current == None or self.keepChars == 0: return self.curData = self.curData + data def endElement(self, elementName): if self.ignore: self.ignore -= 1 #print 'end unknown element:', elementName return obj = self.current #print 'end',elementName if self.keepChars: #print ' data =', self.curData if self.keepChars == 1: self.curData = string.strip(self.curData) if isinstance(obj, XMLElement): dtdElement = self.dtd.elements[elementName] obj.SetData(dtdElement.contentType(self.curData)) elif not isinstance(obj, GenericXMLElement): obj = type(obj)(self.curData) else: obj.SetData(self.curData) del self.stack[-1] if len(self.stack): self.current = self.stack[-1] methodName = 'xml_' + elementName if hasattr(self.current, methodName): getattr(self.current, methodName)(obj) else: #if self.dtd != None: # print '### warning: no sub-element method for %s in %s' % (elementName, type(obj)) #print self.current, elementName, obj self.current.AddSubElement(str(elementName), obj) else: self.current = None if isinstance(obj, XMLElement): obj.xml__end(self.current) self.keepChars = 0 def ReadXML(f, dtd=None): parser = xml.sax.make_parser() parser.setFeature(xml.sax.handler.feature_namespaces, 0) ch = DSWContentHandler(dtd) parser.setContentHandler(ch) parser.parse(f) return ch.baseObj ################################################################################################ escape = xml.sax.saxutils.escape quoteattr = xml.sax.saxutils.quoteattr class XMLAttr: def __init__(self, name, value): self.name = name self.value = value # escape(str(value), { '"' : '"' } ) class XMLWriter: def __init__(self, file, encoding='UTF-8'): if isinstance(file, (str, unicode)): self.f = codecs.open(file, 'w', encoding) else: self.f = file self.indent = '' self.write('\n' % encoding) def BeginDocType(self, doctype): self.write('\n\n') self.indent = '' def DocType(self, doctype, pubid=None, sysid=None): if pubid or sysid: self.BeginDocType(doctype) if pubid == None: self.WriteIndented('SYSTEM "%s"\n' % sysid) else: self.WriteIndented('PUBLIC "%s"\n' % pubid) self.WriteIndented('"%s"\n' % sysid) self.EndDocType() else: self.write('\n\n' % doctype) self.doctype = doctype def TagAttrs(self, tag, attrs): s = self.indent + '<' + tag if attrs != None: attrs = sequence(attrs) if attrs[0] == '\n': attrs = sequence(attrs[1:]) ntabs = len(self.indent) + ((len(tag) + 1 + 3) >> 2) tabs = ntabs * '\t' first = 1 for a in sequence(attrs): if first: s += '\t%s=%s' % (a.name, quoteattr(unicode(a.value))) first = 0 else: s += '\n%s%s=%s' % (tabs, a.name, quoteattr(unicode(a.value))) else: for a in sequence(attrs): s += ' %s=%s' % (a.name, quoteattr(unicode(a.value))) return s def BeginElement(self, tag, attrs=None): self.write('%s>\n' % self.TagAttrs(tag, attrs)) #if tag != self.doctype: self.indent += '\t' def EndElement(self, tag): #if tag != self.doctype: self.indent = self.indent[:-1] self.write('%s\n' % (self.indent, tag)) def Element(self, tag, attrs=None, content=''): s = self.TagAttrs(tag, attrs) content = str(content) if len(content) == 0: self.write('%s />\n' % s) else: self.write('%s>' % s) lines = string.split(escape(content), '\n') if len(lines) < 2: self.write(lines[0]) else: self.write('\n') for l in lines: self.write('%s\t%s\n' % (self.indent, l)) self.write(self.indent) self.write('\n' % tag) def write(self, s): self.f.write(s) def WriteIndented(self, s): self.write(self.indent) self.write(s)