def __init__(self, type, level=1): # xml type being parsed self.type = type # indent level self.level = level # dict of names-tuples self.attrnames = {} # sets of (tag, id) preserve order to avoid dangling references during loading self.ids_deleted = OrderedMultiSet() self.ids_created = OrderedMultiSet() # dict from (tag, id) to (names, values, children) self.id_attrs = {} # dict from tag to (names, values)-sets, need to preserve order (CAVEAT5) self.idless_deleted = defaultdict(lambda:OrderedMultiSet()) self.idless_created = defaultdict(lambda:OrderedMultiSet())
def __init__(self, type, copy_tags, level=1): # xml type being parsed self.type = type # tag names to copy even if unchanged self.copy_tags = copy_tags # indent level self.level = level # dict of names-tuples self.attrnames = {} # sets of (tag, id) preserve order to avoid dangling references during # loading self.ids_deleted = OrderedMultiSet() self.ids_created = OrderedMultiSet() self.ids_copied = OrderedMultiSet() # dict from (tag, id) to (names, values, children) self.id_attrs = {} # dict from tag to (names, values)-sets, need to preserve order # (CAVEAT5) self.idless_deleted = defaultdict(OrderedMultiSet) self.idless_created = defaultdict(OrderedMultiSet) self.idless_copied = defaultdict(OrderedMultiSet)
def __init__(self, type, copy_tags, level=1): # xml type being parsed self.type = type # tag names to copy even if unchanged self.copy_tags = copy_tags # indent level self.level = level # dict of names-tuples self.attrnames = {} # sets of (tag, id) preserve order to avoid dangling references during loading self.ids_deleted = OrderedMultiSet() self.ids_created = OrderedMultiSet() self.ids_copied = OrderedMultiSet() # dict from (tag, id) to (names, values, children) self.id_attrs = {} # dict from tag to (names, values)-sets, need to preserve order (CAVEAT5) self.idless_deleted = defaultdict(lambda: OrderedMultiSet()) self.idless_created = defaultdict(lambda: OrderedMultiSet()) self.idless_copied = defaultdict(lambda: OrderedMultiSet())
class AttributeStore: def __init__(self, type, copy_tags, level=1): # xml type being parsed self.type = type # tag names to copy even if unchanged self.copy_tags = copy_tags # indent level self.level = level # dict of names-tuples self.attrnames = {} # sets of (tag, id) preserve order to avoid dangling references during # loading self.ids_deleted = OrderedMultiSet() self.ids_created = OrderedMultiSet() self.ids_copied = OrderedMultiSet() # dict from (tag, id) to (names, values, children) self.id_attrs = {} # dict from tag to (names, values)-sets, need to preserve order # (CAVEAT5) self.idless_deleted = defaultdict(lambda: OrderedMultiSet()) self.idless_created = defaultdict(lambda: OrderedMultiSet()) self.idless_copied = defaultdict(lambda: OrderedMultiSet()) # getAttribute returns "" if not present def getValue(self, node, name): if node.hasAttribute(name): return node.getAttribute(name) else: return None def getNames(self, xmlnode): idattrs = IDATTRS[xmlnode.localName] a = xmlnode.attributes all = [a.item(i).localName for i in range(a.length)] instance = tuple([n for n in all if n not in idattrs]) if not instance in self.attrnames: self.attrnames[instance] = instance # only store a single instance of this tuple to conserve memory return self.attrnames[instance] def getAttrs(self, xmlnode): names = self.getNames(xmlnode) values = tuple([self.getValue(xmlnode, a) for a in names]) children = None if any([c.nodeType == Node.ELEMENT_NODE for c in xmlnode.childNodes]): children = AttributeStore(self.type, self.copy_tags, self.level + 1) tag = xmlnode.localName id = tuple([ xmlnode.getAttribute(a) for a in IDATTRS[tag] if xmlnode.hasAttribute(a) ]) return tag, id, children, (names, values, children) def store(self, xmlnode): tag, id, children, attrs = self.getAttrs(xmlnode) tagid = (tag, id) if id != (): self.ids_deleted.add(tagid) self.id_attrs[tagid] = attrs if children: for child in xmlnode.childNodes: if child.nodeType == Node.ELEMENT_NODE: children.store(child) else: self.no_children_supported(children, tag) self.idless_deleted[tag].add(attrs) def compare(self, xmlnode): tag, id, children, attrs = self.getAttrs(xmlnode) tagid = (tag, id) if id != (): if tagid in self.ids_deleted: self.ids_deleted.remove(tagid) self.id_attrs[tagid] = self.compareAttrs( self.id_attrs[tagid], attrs, tag) else: self.ids_created.add(tagid) self.id_attrs[tagid] = attrs children = self.id_attrs[tagid][2] if children: for child in xmlnode.childNodes: if child.nodeType == Node.ELEMENT_NODE: children.compare(child) if tag == TAG_TLL or tag in self.copy_tags: # see CAVEAT2 child_strings = StringIO.StringIO() children.writeDeleted(child_strings) children.writeCreated(child_strings) children.writeChanged(child_strings) if child_strings.len > 0 or tag in self.copy_tags: # there are some changes. Go back and store everything children = AttributeStore(self.type, self.copy_tags, self.level + 1) for child in xmlnode.childNodes: if child.nodeType == Node.ELEMENT_NODE: children.compare(child) self.id_attrs[tagid] = self.id_attrs[tagid][0:2] + ( children, ) else: self.no_children_supported(children, tag) if attrs in self.idless_deleted[tag]: self.idless_deleted[tag].remove(attrs) if tag in self.copy_tags: self.idless_copied[tag].add(attrs) else: self.idless_created[tag].add(attrs) def no_children_supported(self, children, tag): if children: print( "WARNING: Handling of children only supported for elements with id. Ignored for element '%s'" % tag) def compareAttrs(self, sourceAttrs, destAttrs, tag): snames, svalues, schildren = sourceAttrs dnames, dvalues, dchildren = destAttrs # for traffic lights, always use dchildren if schildren and dchildren: dchildren = schildren if snames == dnames: values = tuple([ self.diff(n, s, d) for n, s, d in zip(snames, svalues, dvalues) ]) return snames, values, dchildren else: sdict = defaultdict(lambda: None, zip(snames, svalues)) ddict = defaultdict(lambda: None, zip(dnames, dvalues)) names = tuple(set(snames + dnames)) values = tuple([self.diff(n, sdict[n], ddict[n]) for n in names]) return names, values, dchildren def diff(self, name, sourceValue, destValue): if sourceValue == destValue: return None elif destValue == None: return DEFAULT_VALUES[name] else: return destValue def writeDeleted(self, file): # data loss if two elements with different tags # have the same id for tag, id in self.ids_deleted: additional = "" if self.type == TYPE_TLLOGICS and tag == TAG_CONNECTION: # see CAVEAT4 names, values, children = self.id_attrs[(tag, id)] additional = " " + self.attr_string(names, values) comment_start, comment_end = ("", "") if tag == TAG_TLL: # see CAVEAT3 comment_start, comment_end = ( "<!-- implicit via changed node type: ", " -->") self.write( file, '%s<%s %s%s/>%s\n' % (comment_start, DELETE_ELEMENT, self.id_string( tag, id), additional, comment_end)) # data loss if two elements with different tags # have the same list of attributes and values for value_set in self.idless_deleted.itervalues(): self.write_idless(file, value_set, DELETE_ELEMENT) def writeCreated(self, file): self.write_tagids(file, self.ids_created, True) for tag, value_set in self.idless_created.iteritems(): self.write_idless(file, value_set, tag) def writeChanged(self, file): tagids_changed = OrderedMultiSet( self.id_attrs.keys()) - (self.ids_deleted | self.ids_created) self.write_tagids(file, tagids_changed, False) def writeCopies(self, file, copy_tags): tagids_unchanged = OrderedMultiSet( self.id_attrs.keys()) - (self.ids_deleted | self.ids_created) self.write_tagids(file, tagids_unchanged, False) for tag, value_set in self.idless_copied.iteritems(): self.write_idless(file, value_set, tag) def write_idless(self, file, attr_set, tag): for names, values, children in attr_set: self.write(file, '<%s %s/>\n' % (tag, self.attr_string(names, values))) def write_tagids(self, file, tagids, create): for tagid in tagids: tag, id = tagid names, values, children = self.id_attrs[tagid] attrs = self.attr_string(names, values) child_strings = StringIO.StringIO() if children: # writeDeleted is not supported children.writeCreated(child_strings) children.writeChanged(child_strings) if len( attrs ) > 0 or child_strings.len > 0 or create or tag in self.copy_tags: close_tag = "/>\n" if child_strings.len > 0: close_tag = ">\n%s" % child_strings.getvalue() self.write( file, '<%s %s %s%s' % (tag, self.id_string(tag, id), attrs, close_tag)) if child_strings.len > 0: self.write(file, "</%s>\n" % tag) def write(self, file, item): file.write(" " * INDENT * self.level) file.write(item) def attr_string(self, names, values): return ' '.join( ['%s="%s"' % (n, v) for n, v in zip(names, values) if v != None]) def id_string(self, tag, id): idattrs = IDATTRS[tag] return ' '.join(['%s="%s"' % (n, v) for n, v in zip(idattrs, id)])
def writeCopies(self, file, copy_tags): tagids_unchanged = OrderedMultiSet( self.id_attrs.keys()) - (self.ids_deleted | self.ids_created) self.write_tagids(file, tagids_unchanged, False) for tag, value_set in self.idless_copied.iteritems(): self.write_idless(file, value_set, tag)
def writeChanged(self, file): tagids_changed = OrderedMultiSet( self.id_attrs.keys()) - (self.ids_deleted | self.ids_created) self.write_tagids(file, tagids_changed, False)
class AttributeStore: def __init__(self, type, copy_tags, level=1): # xml type being parsed self.type = type # tag names to copy even if unchanged self.copy_tags = copy_tags # indent level self.level = level # dict of names-tuples self.attrnames = {} # sets of (tag, id) preserve order to avoid dangling references during loading self.ids_deleted = OrderedMultiSet() self.ids_created = OrderedMultiSet() self.ids_copied = OrderedMultiSet() # dict from (tag, id) to (names, values, children) self.id_attrs = {} # dict from tag to (names, values)-sets, need to preserve order (CAVEAT5) self.idless_deleted = defaultdict(lambda:OrderedMultiSet()) self.idless_created = defaultdict(lambda:OrderedMultiSet()) self.idless_copied = defaultdict(lambda:OrderedMultiSet()) # getAttribute returns "" if not present def getValue(self, node, name): if node.hasAttribute(name): return node.getAttribute(name) else: return None def getNames(self, xmlnode): idattrs = IDATTRS[xmlnode.localName] a = xmlnode.attributes all = [a.item(i).localName for i in range(a.length)] instance = tuple([n for n in all if n not in idattrs]) if not instance in self.attrnames: self.attrnames[instance] = instance # only store a single instance of this tuple to conserve memory return self.attrnames[instance] def getAttrs(self, xmlnode): names = self.getNames(xmlnode) values = tuple([self.getValue(xmlnode, a) for a in names]) children = None if any([c.nodeType == Node.ELEMENT_NODE for c in xmlnode.childNodes]): children = AttributeStore(self.type, self.copy_tags, self.level + 1) tag = xmlnode.localName id = tuple([xmlnode.getAttribute(a) for a in IDATTRS[tag] if xmlnode.hasAttribute(a)]) return tag, id, children, (names, values, children) def store(self, xmlnode): tag, id, children, attrs = self.getAttrs(xmlnode) tagid = (tag, id) if id != (): self.ids_deleted.add(tagid) self.id_attrs[tagid] = attrs if children: for child in xmlnode.childNodes: if child.nodeType == Node.ELEMENT_NODE: children.store(child) else: self.no_children_supported(children, tag) self.idless_deleted[tag].add(attrs) def compare(self, xmlnode): tag, id, children, attrs = self.getAttrs(xmlnode) tagid = (tag, id) if id != (): if tagid in self.ids_deleted: self.ids_deleted.remove(tagid) self.id_attrs[tagid] = self.compareAttrs(self.id_attrs[tagid], attrs, tag) else: self.ids_created.add(tagid) self.id_attrs[tagid] = attrs children = self.id_attrs[tagid][2] if children: for child in xmlnode.childNodes: if child.nodeType == Node.ELEMENT_NODE: children.compare(child) if tag == TAG_TLL or tag in self.copy_tags: # see CAVEAT2 child_strings = StringIO.StringIO() children.writeDeleted(child_strings) children.writeCreated(child_strings) children.writeChanged(child_strings) if child_strings.len > 0 or tag in self.copy_tags: # there are some changes. Go back and store everything children = AttributeStore(self.type, self.copy_tags, self.level + 1) for child in xmlnode.childNodes: if child.nodeType == Node.ELEMENT_NODE: children.compare(child) self.id_attrs[tagid] = self.id_attrs[tagid][0:2] + (children,) else: self.no_children_supported(children, tag) if attrs in self.idless_deleted[tag]: self.idless_deleted[tag].remove(attrs) if tag in self.copy_tags: self.idless_copied[tag].add(attrs) else: self.idless_created[tag].add(attrs) def no_children_supported(self, children, tag): if children: print("WARNING: Handling of children only supported for elements with id. Ignored for element '%s'" % tag) def compareAttrs(self, sourceAttrs, destAttrs, tag): snames, svalues, schildren = sourceAttrs dnames, dvalues, dchildren = destAttrs # for traffic lights, always use dchildren if schildren and dchildren: dchildren = schildren if snames == dnames: values = tuple([self.diff(n,s,d) for n,s,d in zip (snames,svalues,dvalues)]) return snames, values, dchildren else: sdict = defaultdict(lambda:None, zip(snames, svalues)) ddict = defaultdict(lambda:None, zip(dnames, dvalues)) names = tuple(set(snames + dnames)) values = tuple([self.diff(n,sdict[n],ddict[n]) for n in names]) return names, values, dchildren def diff(self, name, sourceValue, destValue): if sourceValue == destValue: return None elif destValue == None: return DEFAULT_VALUES[name] else: return destValue def writeDeleted(self, file): # data loss if two elements with different tags # have the same id for tag, id in self.ids_deleted: additional = "" if self.type == TYPE_TLLOGICS and tag == TAG_CONNECTION: # see CAVEAT4 names, values, children = self.id_attrs[(tag, id)] additional = " " + self.attr_string(names, values) comment_start, comment_end = ("", "") if tag == TAG_TLL: # see CAVEAT3 comment_start, comment_end = ("<!-- implicit via changed node type: ", " -->") self.write(file, '%s<%s %s%s/>%s\n' % ( comment_start, DELETE_ELEMENT, self.id_string(tag, id), additional, comment_end)) # data loss if two elements with different tags # have the same list of attributes and values for value_set in self.idless_deleted.itervalues(): self.write_idless(file, value_set, DELETE_ELEMENT) def writeCreated(self, file): self.write_tagids(file, self.ids_created, True) for tag, value_set in self.idless_created.iteritems(): self.write_idless(file, value_set, tag) def writeChanged(self, file): tagids_changed = OrderedMultiSet(self.id_attrs.keys()) - (self.ids_deleted | self.ids_created) self.write_tagids(file, tagids_changed, False) def writeCopies(self, file, copy_tags): tagids_unchanged = OrderedMultiSet(self.id_attrs.keys()) - (self.ids_deleted | self.ids_created) self.write_tagids(file, tagids_unchanged, False) for tag, value_set in self.idless_copied.iteritems(): self.write_idless(file, value_set, tag) def write_idless(self, file, attr_set, tag): for names, values, children in attr_set: self.write(file, '<%s %s/>\n' % (tag, self.attr_string(names, values))) def write_tagids(self, file, tagids, create): for tagid in tagids: tag, id = tagid names, values, children = self.id_attrs[tagid] attrs = self.attr_string(names, values) child_strings = StringIO.StringIO() if children: # writeDeleted is not supported children.writeCreated(child_strings) children.writeChanged(child_strings) if len(attrs) > 0 or child_strings.len > 0 or create or tag in self.copy_tags: close_tag = "/>\n" if child_strings.len > 0: close_tag = ">\n%s" % child_strings.getvalue() self.write(file, '<%s %s %s%s' % ( tag, self.id_string(tag, id), attrs, close_tag)) if child_strings.len > 0: self.write(file, "</%s>\n" % tag) def write(self, file, item): file.write(" " * INDENT * self.level) file.write(item) def attr_string(self, names, values): return ' '.join(['%s="%s"' % (n,v) for n,v in zip(names, values) if v != None]) def id_string(self, tag, id): idattrs = IDATTRS[tag] return ' '.join(['%s="%s"' % (n,v) for n,v in zip(idattrs, id)])
class AttributeStore: patchImport = False def __init__(self, type, copy_tags, level=1): # xml type being parsed self.type = type # tag names to copy even if unchanged self.copy_tags = copy_tags # indent level self.level = level # dict of names-tuples self.attrnames = {} # sets of (tag, id) preserve order to avoid dangling references during # loading self.ids_deleted = OrderedMultiSet() self.ids_created = OrderedMultiSet() self.ids_copied = OrderedMultiSet() # dict from (tag, id) to (names, values, children) self.id_attrs = {} # dict from tag to (names, values)-sets, need to preserve order # (CAVEAT5) self.idless_deleted = defaultdict(OrderedMultiSet) self.idless_created = defaultdict(OrderedMultiSet) self.idless_copied = defaultdict(OrderedMultiSet) # getAttribute returns "" if not present def getValue(self, node, name): if node.hasAttribute(name): return node.getAttribute(name) else: return None def getNames(self, xmlnode): idattrs = IDATTRS[xmlnode.localName] a = xmlnode.attributes all = [a.item(i).localName for i in range(a.length)] instance = tuple([n for n in all if n not in idattrs]) if instance not in self.attrnames: self.attrnames[instance] = instance # only store a single instance of this tuple to conserve memory return self.attrnames[instance] def getAttrs(self, xmlnode): names = self.getNames(xmlnode) values = tuple([self.getValue(xmlnode, a) for a in names]) children = None if any([c.nodeType == Node.ELEMENT_NODE for c in xmlnode.childNodes]): children = AttributeStore(self.type, self.copy_tags, self.level + 1) tag = xmlnode.localName id = tuple([ xmlnode.getAttribute(a) for a in IDATTRS[tag] if xmlnode.hasAttribute(a) ]) return tag, id, children, (names, values, children) def store(self, xmlnode): tag, id, children, attrs = self.getAttrs(xmlnode) tagid = (tag, id) if id != (): self.ids_deleted.add(tagid) self.ids_copied.add(tagid) self.id_attrs[tagid] = attrs if children: for child in xmlnode.childNodes: if child.nodeType == Node.ELEMENT_NODE: children.store(child) else: self.no_children_supported(children, tag) self.idless_deleted[tag].add(attrs) def compare(self, xmlnode): tag, id, children, attrs = self.getAttrs(xmlnode) tagid = (tag, id) if id != (): if AttributeStore.patchImport: if self.hasChangedConnection(tagid, attrs): # export all connections from the same edge fromEdge = id[0] markChanged = [] for tagid2 in self.ids_deleted: fromEdge2 = tagid2[1][0] if fromEdge == fromEdge2: markChanged.append(tagid2) for tagid2 in markChanged: self.ids_deleted.remove(tagid2) return if tagid in self.ids_deleted: self.ids_deleted.remove(tagid) self.id_attrs[tagid] = self.compareAttrs( self.id_attrs[tagid], attrs, tag) else: self.ids_created.add(tagid) self.id_attrs[tagid] = attrs children = self.id_attrs[tagid][2] if children: for child in xmlnode.childNodes: if child.nodeType == Node.ELEMENT_NODE: children.compare(child) if tag == TAG_TLL or tag in self.copy_tags: # see CAVEAT2 child_strings = StringIO() children.writeDeleted(child_strings) children.writeCreated(child_strings) children.writeChanged(child_strings) if len(child_strings.getvalue() ) > 0 or tag in self.copy_tags: # there are some changes. Go back and store everything children = AttributeStore(self.type, self.copy_tags, self.level + 1) for child in xmlnode.childNodes: if child.nodeType == Node.ELEMENT_NODE: children.compare(child) self.id_attrs[tagid] = self.id_attrs[tagid][0:2] + ( children, ) else: self.no_children_supported(children, tag) if attrs in self.idless_deleted[tag]: self.idless_deleted[tag].remove(attrs) if tag in self.copy_tags: self.idless_copied[tag].add(attrs) elif tag in IGNORE_TAGS: self.idless_deleted[tag].clear() else: self.idless_created[tag].add(attrs) def no_children_supported(self, children, tag): if children: print( "WARNING: Handling of children only supported for elements with id. Ignored for element '%s'" % tag) def compareAttrs(self, sourceAttrs, destAttrs, tag): snames, svalues, schildren = sourceAttrs dnames, dvalues, dchildren = destAttrs # for traffic lights, always use dchildren if schildren and dchildren: dchildren = schildren if snames == dnames: values = tuple([ self.diff(tag, n, s, d) for n, s, d in zip(snames, svalues, dvalues) ]) return snames, values, dchildren else: sdict = defaultdict(lambda: None, zip(snames, svalues)) ddict = defaultdict(lambda: None, zip(dnames, dvalues)) names = tuple(set(snames + dnames)) values = tuple( [self.diff(tag, n, sdict[n], ddict[n]) for n in names]) return names, values, dchildren def diff(self, tag, name, sourceValue, destValue): if (sourceValue == destValue or # CAVEAT7 (tag == TAG_EDGE and name == "type")): return None elif destValue is None: return DEFAULT_VALUES[name] else: return destValue def hasChangedConnection(self, tagid, attrs): tag, id = tagid if tag != TAG_CONNECTION: return False if tagid in self.ids_deleted: names, values, children = self.compareAttrs( self.id_attrs[tagid], attrs, tag) for v in values: if v is not None: return True return False else: return True def writeDeleted(self, file): # data loss if two elements with different tags # have the same id for tag, id in self.ids_deleted: comment_start, comment_end = ("", "") additional = "" delete_element = DELETE_ELEMENT if self.type == TYPE_TLLOGICS and tag == TAG_CONNECTION: # see CAVEAT4 names, values, children = self.id_attrs[(tag, id)] additional = " " + self.attr_string(names, values) if tag == TAG_TLL: # see CAVEAT3 comment_start, comment_end = ( "<!-- implicit via changed node type: ", " -->") if tag == TAG_CROSSING: delete_element = tag additional = ' discard="true"' if tag == TAG_ROUNDABOUT: delete_element = tag additional = ' discard="true"' comment_start, comment_end = ( "<!-- deletion of roundabouts not yet supported. see #2225 ", " -->") self.write( file, '%s<%s %s%s/>%s\n' % (comment_start, delete_element, self.id_string( tag, id), additional, comment_end)) # data loss if two elements with different tags # have the same list of attributes and values for value_set in self.idless_deleted.values(): self.write_idless(file, value_set, DELETE_ELEMENT) def writeCreated(self, file, whiteList=None, blackList=None): self.write_tagids( file, self.filterTags(self.ids_created, whiteList, blackList), True) for tag, value_set in self.idless_created.items(): if ((whiteList is not None and tag not in whiteList) or (blackList is not None and tag in blackList)): continue self.write_idless(file, value_set, tag) def getTagidsChanged(self): return self.ids_copied - (self.ids_deleted | self.ids_created) def writeChanged(self, file, whiteList=None, blackList=None): tagids_changed = self.getTagidsChanged() self.write_tagids( file, self.filterTags(tagids_changed, whiteList, blackList), False) def writeCopies(self, file, copy_tags): tagids_unchanged = self.ids_copied - \ (self.ids_deleted | self.ids_created) self.write_tagids(file, tagids_unchanged, False) for tag, value_set in self.idless_copied.items(): self.write_idless(file, value_set, tag) def write_idless(self, file, attr_set, tag): for names, values, children in attr_set: self.write(file, '<%s %s/>\n' % (tag, self.attr_string(names, values))) def write_tagids(self, file, tagids, create): for tagid in tagids: tag, id = tagid names, values, children = self.id_attrs[tagid] attrs = self.attr_string(names, values) child_strings = StringIO() if children: # writeDeleted is not supported children.writeCreated(child_strings) children.writeChanged(child_strings) if len(attrs) > 0 or len(child_strings.getvalue() ) > 0 or create or tag in self.copy_tags: close_tag = "/>\n" if len(child_strings.getvalue()) > 0: close_tag = ">\n%s" % child_strings.getvalue() self.write( file, '<%s %s %s%s' % (tag, self.id_string(tag, id), attrs, close_tag)) if len(child_strings.getvalue()) > 0: self.write(file, "</%s>\n" % tag) def write(self, file, item): file.write(" " * INDENT * self.level) file.write(item) def attr_string(self, names, values): return ' '.join([ '%s="%s"' % (n, v) for n, v in sorted(zip(names, values)) if v is not None ]) def id_string(self, tag, id): idattrs = IDATTRS[tag] return ' '.join( ['%s="%s"' % (n, v) for n, v in sorted(zip(idattrs, id))]) def filterTags(self, tagids, whiteList, blackList): if whiteList is not None: return [tagid for tagid in tagids if tagid[0] in whiteList] elif blackList is not None: return [tagid for tagid in tagids if tagid[0] not in blackList] else: return tagids def reorderTLL(self): for tag, id in self.ids_created: if tag == TAG_CONNECTION: for tag2, id2 in self.getTagidsChanged(): if tag2 == TAG_TLL: return True return False return False
class AttributeStore: patchImport = False def __init__(self, type, copy_tags, level=1): # xml type being parsed self.type = type # tag names to copy even if unchanged self.copy_tags = copy_tags # indent level self.level = level # dict of names-tuples self.attrnames = {} # sets of (tag, id) preserve order to avoid dangling references during # loading self.ids_deleted = OrderedMultiSet() self.ids_created = OrderedMultiSet() self.ids_copied = OrderedMultiSet() # dict from (tag, id) to (names, values, children) self.id_attrs = {} # dict from tag to (names, values)-sets, need to preserve order # (CAVEAT5) self.idless_deleted = defaultdict(OrderedMultiSet) self.idless_created = defaultdict(OrderedMultiSet) self.idless_copied = defaultdict(OrderedMultiSet) # getAttribute returns "" if not present def getValue(self, node, name): if node.hasAttribute(name): return node.getAttribute(name) else: return None def getNames(self, xmlnode): idattrs = IDATTRS[xmlnode.localName] a = xmlnode.attributes all = [a.item(i).localName for i in range(a.length)] instance = tuple([n for n in all if n not in idattrs]) if instance not in self.attrnames: self.attrnames[instance] = instance # only store a single instance of this tuple to conserve memory return self.attrnames[instance] def getAttrs(self, xmlnode): names = self.getNames(xmlnode) values = tuple([self.getValue(xmlnode, a) for a in names]) children = None if any([c.nodeType == Node.ELEMENT_NODE for c in xmlnode.childNodes]): children = AttributeStore( self.type, self.copy_tags, self.level + 1) tag = xmlnode.localName id = tuple([xmlnode.getAttribute(a) for a in IDATTRS[tag] if xmlnode.hasAttribute(a)]) return tag, id, children, (names, values, children) def store(self, xmlnode): tag, id, children, attrs = self.getAttrs(xmlnode) tagid = (tag, id) if id != (): self.ids_deleted.add(tagid) self.ids_copied.add(tagid) self.id_attrs[tagid] = attrs if children: for child in xmlnode.childNodes: if child.nodeType == Node.ELEMENT_NODE: children.store(child) else: self.no_children_supported(children, tag) self.idless_deleted[tag].add(attrs) def compare(self, xmlnode): tag, id, children, attrs = self.getAttrs(xmlnode) tagid = (tag, id) if id != (): if AttributeStore.patchImport: if self.hasChangedConnection(tagid, attrs): # export all connections from the same edge fromEdge = id[0] markChanged = [] for tagid2 in self.ids_deleted: fromEdge2 = tagid2[1][0] if fromEdge == fromEdge2: markChanged.append(tagid2) for tagid2 in markChanged: self.ids_deleted.remove(tagid2) return if tagid in self.ids_deleted: self.ids_deleted.remove(tagid) self.id_attrs[tagid] = self.compareAttrs( self.id_attrs[tagid], attrs, tag) else: self.ids_created.add(tagid) self.id_attrs[tagid] = attrs children = self.id_attrs[tagid][2] if children: for child in xmlnode.childNodes: if child.nodeType == Node.ELEMENT_NODE: children.compare(child) if tag == TAG_TLL or tag in self.copy_tags: # see CAVEAT2 child_strings = StringIO() children.writeDeleted(child_strings) children.writeCreated(child_strings) children.writeChanged(child_strings) if len(child_strings.getvalue()) > 0 or tag in self.copy_tags: # there are some changes. Go back and store everything children = AttributeStore( self.type, self.copy_tags, self.level + 1) for child in xmlnode.childNodes: if child.nodeType == Node.ELEMENT_NODE: children.compare(child) self.id_attrs[tagid] = self.id_attrs[ tagid][0:2] + (children,) else: self.no_children_supported(children, tag) if attrs in self.idless_deleted[tag]: self.idless_deleted[tag].remove(attrs) if tag in self.copy_tags: self.idless_copied[tag].add(attrs) elif tag in IGNORE_TAGS: self.idless_deleted[tag].clear() else: self.idless_created[tag].add(attrs) def no_children_supported(self, children, tag): if children: print( "WARNING: Handling of children only supported for elements with id. Ignored for element '%s'" % tag) def compareAttrs(self, sourceAttrs, destAttrs, tag): snames, svalues, schildren = sourceAttrs dnames, dvalues, dchildren = destAttrs # for traffic lights, always use dchildren if schildren and dchildren: dchildren = schildren if snames == dnames: values = tuple([self.diff(tag, n, s, d) for n, s, d in zip(snames, svalues, dvalues)]) return snames, values, dchildren else: sdict = defaultdict(lambda: None, zip(snames, svalues)) ddict = defaultdict(lambda: None, zip(dnames, dvalues)) names = tuple(set(snames + dnames)) values = tuple([self.diff(tag, n, sdict[n], ddict[n]) for n in names]) return names, values, dchildren def diff(self, tag, name, sourceValue, destValue): if (sourceValue == destValue or # CAVEAT7 (tag == TAG_EDGE and name == "type")): return None elif destValue is None: return DEFAULT_VALUES[name] else: return destValue def hasChangedConnection(self, tagid, attrs): tag, id = tagid if tag != TAG_CONNECTION: return False if tagid in self.ids_deleted: names, values, children = self.compareAttrs(self.id_attrs[tagid], attrs, tag) for v in values: if v is not None: return True return False else: return True def writeDeleted(self, file): # data loss if two elements with different tags # have the same id for tag, id in self.ids_deleted: comment_start, comment_end = ("", "") additional = "" delete_element = DELETE_ELEMENT if self.type == TYPE_TLLOGICS and tag == TAG_CONNECTION: # see CAVEAT4 names, values, children = self.id_attrs[(tag, id)] additional = " " + self.attr_string(names, values) if tag == TAG_TLL: # see CAVEAT3 comment_start, comment_end = ( "<!-- implicit via changed node type: ", " -->") if tag == TAG_CROSSING: delete_element = tag additional = ' discard="true"' if tag == TAG_ROUNDABOUT: delete_element = tag additional = ' discard="true"' comment_start, comment_end = ( "<!-- deletion of roundabouts not yet supported. see #2225 ", " -->") self.write(file, '%s<%s %s%s/>%s\n' % ( comment_start, delete_element, self.id_string(tag, id), additional, comment_end)) # data loss if two elements with different tags # have the same list of attributes and values for value_set in self.idless_deleted.values(): self.write_idless(file, value_set, DELETE_ELEMENT) def writeCreated(self, file, whiteList=None, blackList=None): self.write_tagids(file, self.filterTags(self.ids_created, whiteList, blackList), True) for tag, value_set in self.idless_created.items(): if ((whiteList is not None and tag not in whiteList) or (blackList is not None and tag in blackList)): continue self.write_idless(file, value_set, tag) def getTagidsChanged(self): return self.ids_copied - (self.ids_deleted | self.ids_created) def writeChanged(self, file, whiteList=None, blackList=None): tagids_changed = self.getTagidsChanged() self.write_tagids(file, self.filterTags(tagids_changed, whiteList, blackList), False) def writeCopies(self, file, copy_tags): tagids_unchanged = self.ids_copied - \ (self.ids_deleted | self.ids_created) self.write_tagids(file, tagids_unchanged, False) for tag, value_set in self.idless_copied.items(): self.write_idless(file, value_set, tag) def write_idless(self, file, attr_set, tag): for names, values, children in attr_set: self.write(file, '<%s %s/>\n' % (tag, self.attr_string(names, values))) def write_tagids(self, file, tagids, create): for tagid in tagids: tag, id = tagid names, values, children = self.id_attrs[tagid] attrs = self.attr_string(names, values) child_strings = StringIO() if children: # writeDeleted is not supported children.writeCreated(child_strings) children.writeChanged(child_strings) if len(attrs) > 0 or len(child_strings.getvalue()) > 0 or create or tag in self.copy_tags: close_tag = "/>\n" if len(child_strings.getvalue()) > 0: close_tag = ">\n%s" % child_strings.getvalue() self.write(file, '<%s %s %s%s' % ( tag, self.id_string(tag, id), attrs, close_tag)) if len(child_strings.getvalue()) > 0: self.write(file, "</%s>\n" % tag) def write(self, file, item): file.write(" " * INDENT * self.level) file.write(item) def attr_string(self, names, values): return ' '.join(['%s="%s"' % (n, v) for n, v in sorted(zip(names, values)) if v is not None]) def id_string(self, tag, id): idattrs = IDATTRS[tag] return ' '.join(['%s="%s"' % (n, v) for n, v in sorted(zip(idattrs, id))]) def filterTags(self, tagids, whiteList, blackList): if whiteList is not None: return [tagid for tagid in tagids if tagid[0] in whiteList] elif blackList is not None: return [tagid for tagid in tagids if tagid[0] not in blackList] else: return tagids def reorderTLL(self): for tag, id in self.ids_created: if tag == TAG_CONNECTION: for tag2, id2 in self.getTagidsChanged(): if tag2 == TAG_TLL: return True return False return False