# HG changeset patch # User laurent # Date 1322003967 -3600 # Node ID 89ff2738ef20a763bb86ace971332257de3f9076 # Parent 4d6719c51f05bc8e10f897d5f81b47da93196a87 Adding support in xmlclass for handling some not yet supported XML syntaxes diff -r 4d6719c51f05 -r 89ff2738ef20 xmlclass/xmlclass.py --- a/xmlclass/xmlclass.py Fri Nov 18 17:40:40 2011 +0100 +++ b/xmlclass/xmlclass.py Wed Nov 23 00:19:27 2011 +0100 @@ -22,7 +22,7 @@ #License along with this library; if not, write to the Free Software #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -import sys +import os, sys import re import datetime from types import * @@ -92,8 +92,8 @@ return ZERO [SYNTAXELEMENT, SYNTAXATTRIBUTE, SIMPLETYPE, COMPLEXTYPE, COMPILEDCOMPLEXTYPE, - ATTRIBUTESGROUP, ELEMENTSGROUP, ATTRIBUTE, ELEMENT, CHOICE, ANY, TAG -] = range(12) + ATTRIBUTESGROUP, ELEMENTSGROUP, ATTRIBUTE, ELEMENT, CHOICE, ANY, TAG, CONSTRAINT, +] = range(13) def NotSupportedYet(type): """ @@ -126,13 +126,13 @@ if not extract: return attr if len(attr.childNodes) == 1: - return unescape(attr.childNodes[0].data.encode()) + return unescape(attr.childNodes[0].data.encode("utf-8")) else: # content is a CDATA text = "" for node in attr.childNodes: if node.nodeName != "#text": - text += node.data.encode() + text += node.data.encode("utf-8") return text @@ -532,23 +532,29 @@ return values return GetModelNameList -def GenerateAnyInfos(): +def GenerateAnyInfos(infos): def ExtractAny(tree): - return tree.data.encode("utf-8") + if tree.nodeName == "#cdata-section": + return tree.data.encode("utf-8") + else: + return tree def GenerateAny(value, name=None, indent=0): - try: - value = value.decode("utf-8") - except: - pass - return u'\n' % value + if isinstance(value, (StringType, UnicodeType)): + try: + value = value.decode("utf-8") + except: + pass + return u'\n' % value + else: + return value.toprettyxml(indent=" "*indent, encoding="utf-8") return { "type": COMPLEXTYPE, "extract": ExtractAny, "generate": GenerateAny, "initial": lambda: "", - "check": lambda x: isinstance(x, (StringType, UnicodeType)) + "check": lambda x: isinstance(x, (StringType, UnicodeType, minidom.Node)) } def GenerateTagInfos(name): @@ -573,80 +579,239 @@ "initial": lambda: None, "check": lambda x: x == None } + +def FindTypeInfos(factory, infos): + if isinstance(infos, (UnicodeType, StringType)): + namespace, name = DecomposeQualifiedName(infos) + return factory.GetQualifiedNameInfos(name, namespace) + return infos -def GenerateContentInfos(factory, choices): +def GetElementInitialValue(factory, infos): + infos["elmt_type"] = FindTypeInfos(factory, infos["elmt_type"]) + if infos["minOccurs"] == 0 and infos["maxOccurs"] == 1: + if infos.has_key("default"): + return infos["elmt_type"]["extract"](infos["default"], False) + else: + return None + elif infos["minOccurs"] == 1 and infos["maxOccurs"] == 1: + return infos["elmt_type"]["initial"]() + else: + return [infos["elmt_type"]["initial"]() for i in xrange(infos["minOccurs"])] + +def HandleError(message, raise_exception): + if raise_exception: + raise ValueError(message) + return False + +def CheckElementValue(factory, name, infos, value, raise_exception=True): + infos["elmt_type"] = FindTypeInfos(factory, infos["elmt_type"]) + if value is None and raise_exception: + if not (infos["minOccurs"] == 0 and infos["maxOccurs"] == 1): + return HandleError("Attribute '%s' isn't optional." % name, raise_exception) + elif infos["maxOccurs"] == "unbounded" or infos["maxOccurs"] > 1: + if not isinstance(value, ListType): + return HandleError("Attribute '%s' must be a list." % name, raise_exception) + if len(value) < infos["minOccurs"] or infos["maxOccurs"] != "unbounded" and len(value) > infos["maxOccurs"]: + return HandleError("List out of bounds for attribute '%s'." % name, raise_exception) + if not reduce(lambda x, y: x and y, map(infos["elmt_type"]["check"], value), True): + return HandleError("Attribute '%s' must be a list of valid elements." % name, raise_exception) + elif infos.has_key("fixed") and value != infos["fixed"]: + return HandleError("Value of attribute '%s' can only be '%s'." % (name, str(infos["fixed"])), raise_exception) + else: + return infos["elmt_type"]["check"](value) + return True + +def GetContentInfos(name, choices): + for choice_infos in choices: + if choices_infos["type"] == "sequence": + for element_infos in choices_infos["elements"]: + if element_infos["type"] == CHOICE: + if GetContentInfos(name, element_infos["choices"]): + return choices_infos + elif element_infos["name"] == name: + return choices_infos + elif choice_infos["name"] == name: + return choices_infos + return None + +def ComputeContentChoices(factory, name, infos): + choices = [] + for choice in infos["choices"]: + if choice["type"] == "sequence": + choice["name"] = "sequence" + for sequence_element in choice["elements"]: + if sequence_element["type"] != CHOICE: + element_infos = factory.ExtractTypeInfos(sequence_element["name"], name, sequence_element["elmt_type"]) + if element_infos is not None: + sequence_element["elmt_type"] = element_infos + elif choice["elmt_type"] == "tag": + choice["elmt_type"] = GenerateTagInfos(choice["name"]) + else: + choice_infos = factory.ExtractTypeInfos(choice["name"], name, choice["elmt_type"]) + if choice_infos is not None: + choice["elmt_type"] = choice_infos + choices.append((choice["name"], choice)) + return choices + +def ExtractContentElement(factory, tree, infos, content): + infos["elmt_type"] = FindTypeInfos(factory, infos["elmt_type"]) + if infos["maxOccurs"] == "unbounded" or infos["maxOccurs"] > 1: + if isinstance(content, ListType) and len(content) > 0 and \ + content[-1]["name"] == tree.nodeName: + content_item = content.pop(-1) + content_item["value"].append(infos["elmt_type"]["extract"](tree)) + return content_item + elif not isinstance(content, ListType) and \ + content is not None and \ + content["name"] == tree.nodeName: + return {"name": tree.nodeName, + "value": content["value"] + [infos["elmt_type"]["extract"](tree)]} + else: + return {"name": tree.nodeName, + "value": [infos["elmt_type"]["extract"](tree)]} + else: + return {"name": tree.nodeName, + "value": infos["elmt_type"]["extract"](tree)} + +def GenerateContentInfos(factory, name, choices): + choices_dict = {} + for choice_name, infos in choices: + if choice_name == "sequence": + for element in infos["elements"]: + if element["type"] == CHOICE: + element["elmt_type"] = GenerateContentInfos(factory, name, ComputeContentChoices(factory, name, element)) + elif choices_dict.has_key(element["name"]): + raise ValueError("'%s' element defined two times in choice" % choice_name) + else: + choices_dict[element["name"]] = infos + else: + if choices_dict.has_key(choice_name): + raise ValueError("'%s' element defined two times in choice" % choice_name) + choices_dict[choice_name] = infos + def GetContentInitial(): content_name, infos = choices[0] - if isinstance(infos["elmt_type"], (UnicodeType, StringType)): - namespace, name = DecomposeQualifiedName(infos["elmt_type"]) - infos["elmt_type"] = factory.GetQualifiedNameInfos(name, namespace) - if infos["maxOccurs"] == "unbounded" or infos["maxOccurs"] > 1: - return {"name": content_name, - "value": map(infos["elmt_type"]["initial"], - range(infos["minOccurs"]))} - else: - return {"name": content_name, - "value": infos["elmt_type"]["initial"]()} + if content_name == "sequence": + content_value = [] + for i in xrange(infos["minOccurs"]): + for element_infos in infos["elements"]: + value = GetElementInitialValue(factory, element_infos) + if value is not None: + if element_infos["type"] == CHOICE: + content_value.append(value) + else: + content_value.append({"name": element_infos["name"], "value": value}) + else: + content_value = GetElementInitialValue(factory, infos) + return {"name": content_name, "value": content_value} + + def CheckContent(value): + if value["name"] != "sequence": + infos = choices_dict.get(value["name"], None) + if infos is not None: + return CheckElementValue(factory, value["name"], infos, value["value"], False) + elif len(value["value"]) > 0: + infos = choices_dict.get(value["value"][0]["name"], None) + if infos is None: + for choice_name, infos in choices: + if infos["type"] == "sequence": + for element_infos in infos["elements"]: + if element_infos["type"] == CHOICE: + infos = GetContentInfos(value["value"][0]["name"], element_infos["choices"]) + if infos is not None: + sequence_number = 0 + element_idx = 0 + while element_idx < len(value["value"]): + for element_infos in infos["elements"]: + if element_infos["name"] == value["value"][element_idx]["name"]: + element_value = value["value"][element_idx]["value"] + element_idx += 1 + else: + element_value = None + if not CheckElementValue(factory, element_infos["name"], element_infos, element_value, False): + raise ValueError("Invalid sequence value in attribute 'content'") + sequence_number += 1 + if sequence_number < infos["minOccurs"] or infos["maxOccurs"] != "unbounded" and sequence_number > infos["maxOccurs"]: + raise ValueError("Invalid sequence value in attribute 'content'") + return True + else: + for element_name, infos in choices: + if element_name == "sequence": + required = 0 + for element in infos["elements"]: + if element["minOccurs"] > 0: + required += 1 + if required == 0: + return True + return False - def CheckContent(value): - for content_name, infos in choices: - if content_name == value["name"]: - if isinstance(infos["elmt_type"], (UnicodeType, StringType)): - namespace, name = DecomposeQualifiedName(infos["elmt_type"]) - infos["elmt_type"] = factory.GetQualifiedNameInfos(name, namespace) - if infos["maxOccurs"] == "unbounded" or infos["maxOccurs"] > 1: - if isinstance(value["value"], ListType) and \ - infos["minOccurs"] <= len(value["value"]) <= infos["maxOccurs"]: - return reduce(lambda x, y: x and y, - map(infos["elmt_type"]["check"], - value["value"]), - True) - else: - return infos["elmt_type"]["check"](value["value"]) - return False - def ExtractContent(tree, content): - for content_name, infos in choices: - if content_name == tree.nodeName: - if isinstance(infos["elmt_type"], (UnicodeType, StringType)): - namespace, name = DecomposeQualifiedName(infos["elmt_type"]) - infos["elmt_type"] = factory.GetQualifiedNameInfos(name, namespace) - if infos["maxOccurs"] == "unbounded" or infos["maxOccurs"] > 1: - if isinstance(content, ListType) and len(content) > 0 and \ - content[-1]["name"] == content_name: - content_item = content.pop(-1) - content_item["value"].append(infos["elmt_type"]["extract"](tree)) - return content_item - elif not isinstance(content, ListType) and \ - content is not None and \ - content["name"] == content_name: - return {"name": content_name, - "value": content["value"] + \ - [infos["elmt_type"]["extract"](tree)]} - else: - return {"name": content_name, - "value": [infos["elmt_type"]["extract"](tree)]} - else: - return {"name": content_name, - "value": infos["elmt_type"]["extract"](tree)} + infos = choices_dict.get(tree.nodeName, None) + if infos is not None: + if infos["name"] == "sequence": + sequence_dict = dict([(element_infos["name"], element_infos) for element_infos in infos["elements"] if element_infos["type"] != CHOICE]) + element_infos = sequence_dict.get(tree.nodeName) + if content is not None and \ + content["name"] == "sequence" and \ + len(content["value"]) > 0 and \ + choices_dict.get(content["value"][-1]["name"]) == infos: + return {"name": "sequence", + "value": content["value"] + [ExtractContentElement(factory, tree, element_infos, content["value"][-1])]} + else: + return {"name": "sequence", + "value": [ExtractContentElement(factory, tree, element_infos, None)]} + else: + return ExtractContentElement(factory, tree, infos, content) + else: + for choice_name, infos in choices: + if infos["type"] == "sequence": + for element_infos in infos["elements"]: + if element_infos["type"] == CHOICE: + try: + if content is not None and \ + content["name"] == "sequence" and \ + len(content["value"]) > 0: + return {"name": "sequence", + "value": content["value"] + [element_infos["elmt_type"]["extract"](tree, content["value"][-1])]} + else: + return {"name": "sequence", + "value": [element_infos["elmt_type"]["extract"](tree, None)]} + except: + pass raise ValueError("Invalid element \"%s\" for content!" % tree.nodeName) def GenerateContent(value, name=None, indent=0): - for content_name, infos in choices: - if content_name == value["name"]: - if isinstance(infos["elmt_type"], (UnicodeType, StringType)): - namespace, name = DecomposeQualifiedName(infos["elmt_type"]) - infos["elmt_type"] = factory.GetQualifiedNameInfos(name, namespace) + text = "" + if value["name"] != "sequence": + infos = choices_dict.get(value["name"], None) + if infos is not None: + infos["elmt_type"] = FindTypeInfos(factory, infos["elmt_type"]) if infos["maxOccurs"] == "unbounded" or infos["maxOccurs"] > 1: - text = "" for item in value["value"]: - text += infos["elmt_type"]["generate"](item, content_name, indent) - return text - else: - return infos["elmt_type"]["generate"](value["value"], content_name, indent) - return "" + text += infos["elmt_type"]["generate"](item, value["name"], indent) + else: + text += infos["elmt_type"]["generate"](value["value"], value["name"], indent) + elif len(value["value"]) > 0: + infos = choices_dict.get(value["value"][0]["name"], None) + if infos is None: + for choice_name, infos in choices: + if infos["type"] == "sequence": + for element_infos in infos["elements"]: + if element_infos["type"] == CHOICE: + infos = GetContentInfos(value["value"][0]["name"], element_infos["choices"]) + if infos is not None: + sequence_dict = dict([(element_infos["name"], element_infos) for element_infos in infos["elements"]]) + for element_value in value["value"]: + element_infos = sequence_dict.get(element_value["name"]) + if element_infos["maxOccurs"] == "unbounded" or element_infos["maxOccurs"] > 1: + for item in element_value["value"]: + text += element_infos["elmt_type"]["generate"](item, element_value["name"], indent) + else: + text += element_infos["elmt_type"]["generate"](element_value["value"], element_infos["name"], indent) + return text return { + "type": COMPLEXTYPE, "initial": GetContentInitial, "check": CheckContent, "extract": ExtractContent, @@ -707,8 +872,12 @@ """ class ClassFactory: - def __init__(self, document, debug=False): + def __init__(self, document, filepath=None, debug=False): self.Document = document + if filepath is not None: + self.BaseFolder, self.FileName = os.path.split(filepath) + else: + self.BaseFolder = self.FileName = None self.Debug = debug # Dictionary for stocking Classes and Types definitions created from @@ -724,7 +893,10 @@ # Dictionaries for stocking Classes and Types generated self.ComputeAfter = [] - self.ComputedClasses = {} + if self.FileName is not None: + self.ComputedClasses = {self.FileName: {}} + else: + self.ComputedClasses = {} self.ComputedClassesInfos = {} self.AlreadyComputed = {} @@ -732,7 +904,7 @@ if namespace is None: if self.Namespaces[self.SchemaNamespace].has_key(name): return self.Namespaces[self.SchemaNamespace][name] - for space, elements in self.Namespaces.items(): + for space, elements in self.Namespaces.iteritems(): if space != self.SchemaNamespace and elements.has_key(name): return elements[name] parts = name.split("_", 1) @@ -846,15 +1018,18 @@ def ReduceElements(self, elements, schema=False): result = [] for child_infos in elements: - if child_infos[1].has_key("name") and schema: - self.CurrentCompilations.append(child_infos[1]["name"]) - namespace, name = DecomposeQualifiedName(child_infos[0]) - infos = self.GetQualifiedNameInfos(name, namespace) - if infos["type"] != SYNTAXELEMENT: - raise ValueError("\"%s\" can't be a member child!" % name) - result.append(infos["reduce"](self, child_infos[1], child_infos[2])) - if child_infos[1].has_key("name") and schema: - self.CurrentCompilations.pop(-1) + if child_infos is not None: + if child_infos[1].has_key("name") and schema: + self.CurrentCompilations.append(child_infos[1]["name"]) + namespace, name = DecomposeQualifiedName(child_infos[0]) + infos = self.GetQualifiedNameInfos(name, namespace) + if infos["type"] != SYNTAXELEMENT: + raise ValueError("\"%s\" can't be a member child!" % name) + element = infos["reduce"](self, child_infos[1], child_infos[2]) + if element is not None: + result.append(element) + if child_infos[1].has_key("name") and schema: + self.CurrentCompilations.pop(-1) annotations = [] children = [] for element in result: @@ -948,10 +1123,11 @@ # If base classes haven't been generated bases = [] - if classinfos.has_key("base"): - result = self.ExtractTypeInfos("base", name, classinfos["base"]) + base_infos = classinfos.get("base", None) + if base_infos is not None: + result = self.ExtractTypeInfos("base", name, base_infos) if result is None: - namespace, base_name = DecomposeQualifiedName(classinfos["base"]) + namespace, base_name = DecomposeQualifiedName(base_infos) if self.AlreadyComputed.get(base_name, False): self.ComputeAfter.append((name, parent, classinfos)) if self.TargetNamespace is not None: @@ -959,8 +1135,19 @@ else: return classname elif result is not None: - classinfos["base"] = self.ComputedClasses[result["name"]] - bases.append(self.ComputedClasses[result["name"]]) + if self.FileName is not None: + classinfos["base"] = self.ComputedClasses[self.FileName].get(result["name"], None) + if classinfos["base"] is None: + for filename, classes in self.ComputedClasses.iteritems(): + if filename != self.FileName: + classinfos["base"] = classes.get(result["name"], None) + if classinfos["base"] is not None: + break + else: + classinfos["base"] = self.ComputedClasses.get(result["name"], None) + if classinfos["base"] is None: + raise ValueError("No class found for base type") + bases.append(classinfos["base"]) bases.append(object) bases = tuple(bases) classmembers = {"__doc__": classinfos.get("doc", ""), "IsBaseClass": baseclass} @@ -984,30 +1171,22 @@ else: raise ValueError("\"%s\" type unrecognized!" % attribute["attr_type"]) attribute["attr_type"] = infos - + for element in classinfos["elements"]: if element["type"] == CHOICE: elmtname = element["name"] - choices = [] - for choice in element["choices"]: - if choice["elmt_type"] == "tag": - choice["elmt_type"] = GenerateTagInfos(choice["name"]) - else: - infos = self.ExtractTypeInfos(choice["name"], name, choice["elmt_type"]) - if infos is not None: - choice["elmt_type"] = infos - choices.append((choice["name"], choice)) + choices = ComputeContentChoices(self, name, element) classmembers["get%schoices"%elmtname] = generateGetChoicesMethod(element["choices"]) if element["maxOccurs"] == "unbounded" or element["maxOccurs"] > 1: classmembers["append%sbytype" % elmtname] = generateAppendChoiceByTypeMethod(element["maxOccurs"], self, element["choices"]) classmembers["insert%sbytype" % elmtname] = generateInsertChoiceByTypeMethod(element["maxOccurs"], self, element["choices"]) else: classmembers["set%sbytype" % elmtname] = generateSetChoiceByTypeMethod(self, element["choices"]) - infos = GenerateContentInfos(self, choices) + infos = GenerateContentInfos(self, name, choices) elif element["type"] == ANY: elmtname = element["name"] = "text" element["minOccurs"] = element["maxOccurs"] = 1 - infos = GenerateAnyInfos() + infos = GenerateAnyInfos(element) else: elmtname = element["name"] infos = self.ExtractTypeInfos(element["name"], name, element["elmt_type"]) @@ -1050,7 +1229,10 @@ "extract": generateClassExtractFunction(class_definition), "generate": class_definition.generateXMLText} - self.ComputedClasses[classname] = class_definition + if self.FileName is not None: + self.ComputedClasses[self.FileName][classname] = class_definition + else: + self.ComputedClasses[classname] = class_definition self.ComputedClassesInfos[classname] = class_infos return class_infos @@ -1061,8 +1243,16 @@ def PrintClasses(self): items = self.ComputedClasses.items() items.sort() - for classname, xmlclass in items: - print "%s : %s" % (classname, str(xmlclass)) + if self.FileName is not None: + for filename, classes in items: + print "File '%s':" % filename + class_items = classes.items() + class_items.sort() + for classname, xmlclass in class_items: + print "%s: %s" % (classname, str(xmlclass)) + else: + for classname, xmlclass in items: + print "%s: %s" % (classname, str(xmlclass)) def PrintClassNames(self): classnames = self.XMLClassDefinitions.keys() @@ -1107,9 +1297,7 @@ def setattrMethod(self, name, value): if attributes.has_key(name): - if isinstance(attributes[name]["attr_type"], (UnicodeType, StringType)): - namespace, name = DecomposeQualifiedName(infos) - attributes[name]["attr_type"] = factory.GetQualifiedNameInfos(name, namespace) + attributes[name]["attr_type"] = FindTypeInfos(factory, attributes[name]["attr_type"]) if value is None: if optional_attributes.get(name, False): return object.__setattr__(self, name, None) @@ -1122,22 +1310,7 @@ else: raise ValueError("Invalid value for attribute '%s'." % (name)) elif elements.has_key(name): - if isinstance(elements[name]["elmt_type"], (UnicodeType, StringType)): - namespace, name = DecomposeQualifiedName(infos) - elements[name]["elmt_type"] = factory.GetQualifiedNameInfos(name, namespace) - if value is None: - if elements[name]["minOccurs"] == 0 and elements[name]["maxOccurs"] == 1: - return object.__setattr__(self, name, None) - else: - raise ValueError("Attribute '%s' isn't optional." % name) - elif elements[name]["maxOccurs"] == "unbounded" or elements[name]["maxOccurs"] > 1: - if isinstance(value, ListType) and elements[name]["minOccurs"] <= len(value) <= elements[name]["maxOccurs"]: - if reduce(lambda x, y: x and y, map(elements[name]["elmt_type"]["check"], value), True): - return object.__setattr__(self, name, value) - raise ValueError, "Attribute '%s' must be a list of valid elements."%name - elif elements[name].has_key("fixed") and value != elements[name]["fixed"]: - raise ValueError("Value of attribute '%s' can only be '%s'." % (name, str(elements[name]["fixed"]))) - elif elements[name]["elmt_type"]["check"](value): + if CheckElementValue(factory, name, elements[name], value): return object.__setattr__(self, name, value) else: raise ValueError("Invalid value for attribute '%s'." % (name)) @@ -1166,7 +1339,7 @@ if infos["maxOccurs"] == "unbounded": return "(?:%s)+" % name elif infos["maxOccurs"] == 1: - return name + return "(?:%s)" % name else: return "(?:%s){1,%d}" % (name, infos["maxOccurs"]) else: @@ -1176,23 +1349,32 @@ return "(?:%s){%d,%d}" % (name, infos["minOccurs"], infos["maxOccurs"]) -def generateStructureMethod(classinfos): +def GetStructure(classinfos): elements = [] for element in classinfos["elements"]: if element["type"] == ANY: - elements.append(ComputeMultiplicity("(?:#cdata-section )?", element)) + elements.append(ComputeMultiplicity("#cdata-section |\w* ", element)) elif element["type"] == CHOICE: - elements.append(ComputeMultiplicity( - "|".join([ComputeMultiplicity("%s " % infos["name"], infos) for infos in element["choices"]]), - element)) + choices = [] + for infos in element["choices"]: + if infos["type"] == "sequence": + structure = "(?:%s)" % GetStructure(infos) + else: + structure = "%s " % infos["name"] + choices.append(ComputeMultiplicity(structure, infos)) + elements.append(ComputeMultiplicity("|".join(choices), element)) + elif element["name"] == "content" and element["elmt_type"]["type"] == SIMPLETYPE: + elements.append("(?:#cdata-section )?") else: elements.append(ComputeMultiplicity("%s " % element["name"], element)) if classinfos.get("order", True) or len(elements) == 0: - structure = "".join(elements) + return "".join(elements) else: raise ValueError("XSD structure not yet supported!") - + +def generateStructureMethod(classinfos): def getStructureMethod(self): + structure = GetStructure(classinfos) if classinfos.has_key("base"): return classinfos["base"].getStructure(self) + structure return structure @@ -1213,20 +1395,20 @@ for node in tree.childNodes: if node.nodeName not in ["#comment", "#text"]: children_structure += "%s " % node.nodeName - structure_model = re.compile("(%s)$" % self.getStructure()) - result = structure_model.match(children_structure) - if not result: - raise ValueError("Invalid structure for \"%s\" children!." % tree.nodeName) + structure_pattern = self.getStructure() + if structure_pattern != "": + structure_model = re.compile("(%s)$" % structure_pattern) + result = structure_model.match(children_structure) + if not result: + raise ValueError("Invalid structure for \"%s\" children!." % tree.nodeName) required_attributes = dict([(attr["name"], True) for attr in classinfos["attributes"] if attr["use"] == "required"]) if classinfos.has_key("base"): extras.extend([attr["name"] for attr in classinfos["attributes"] if attr["use"] != "prohibited"]) classinfos["base"].loadXMLTree(self, tree, extras, True) for attrname, attr in tree._attrs.iteritems(): if attributes.has_key(attrname): - if isinstance(attributes[attrname]["attr_type"], (UnicodeType, StringType)): - namespace, name = DecomposeQualifiedName(infos) - attributes[attrname]["attr_type"] = factory.GetQualifiedNameInfos(name, namespace) - setattr(self, attrname, attributes[attrname]["attr_type"]["extract"](attr)) + attributes[attrname]["attr_type"] = FindTypeInfos(factory, attributes[attrname]["attr_type"]) + object.__setattr__(self, attrname, attributes[attrname]["attr_type"]["extract"](attr)) elif not classinfos.has_key("base") and attrname not in extras: raise ValueError("Invalid attribute \"%s\" for \"%s\" element!" % (attrname, tree.nodeName)) required_attributes.pop(attrname, None) @@ -1237,37 +1419,39 @@ name = node.nodeName if name in ["#text", "#comment"]: continue - if elements.has_key(name): - if isinstance(elements[name]["elmt_type"], (UnicodeType, StringType)): - namespace, name = DecomposeQualifiedName(infos) - elements[name]["elmt_type"] = factory.GetQualifiedNameInfos(name, namespace) + elif elements.has_key(name): + elements[name]["elmt_type"] = FindTypeInfos(factory, elements[name]["elmt_type"]) if elements[name]["maxOccurs"] == "unbounded" or elements[name]["maxOccurs"] > 1: if first.get(name, True): - setattr(self, name, [elements[name]["elmt_type"]["extract"](node)]) + object.__setattr__(self, name, [elements[name]["elmt_type"]["extract"](node)]) first[name] = False else: getattr(self, name).append(elements[name]["elmt_type"]["extract"](node)) else: - setattr(self, name, elements[name]["elmt_type"]["extract"](node)) - elif name == "#cdata-section" and elements.has_key("text"): + object.__setattr__(self, name, elements[name]["elmt_type"]["extract"](node)) + elif elements.has_key("text"): if elements["text"]["maxOccurs"] == "unbounded" or elements["text"]["maxOccurs"] > 1: if first.get("text", True): - setattr(self, "text", [elements["text"]["elmt_type"]["extract"](node)]) + object.__setattr__(self, "text", [elements["text"]["elmt_type"]["extract"](node)]) first["text"] = False else: getattr(self, "text").append(elements["text"]["elmt_type"]["extract"](node)) else: - setattr(self, "text", elements["text"]["elmt_type"]["extract"](node)) + object.__setattr__(self, "text", elements["text"]["elmt_type"]["extract"](node)) elif elements.has_key("content"): - content = getattr(self, "content") - if elements["content"]["maxOccurs"] == "unbounded" or elements["content"]["maxOccurs"] > 1: - if first.get("content", True): - setattr(self, "content", [elements["content"]["elmt_type"]["extract"](node, None)]) - first["content"] = False + if name == "#cdata-section": + if elements["content"]["elmt_type"]["type"] == SIMPLETYPE: + object.__setattr__(self, "content", elements["content"]["elmt_type"]["extract"](node.data, False)) + else: + content = getattr(self, "content") + if elements["content"]["maxOccurs"] == "unbounded" or elements["content"]["maxOccurs"] > 1: + if first.get("content", True): + object.__setattr__(self, "content", [elements["content"]["elmt_type"]["extract"](node, None)]) + first["content"] = False + else: + content.append(elements["content"]["elmt_type"]["extract"](node, content)) else: - content.append(elements["content"]["elmt_type"]["extract"](node, content)) - else: - setattr(self, "content", elements["content"]["elmt_type"]["extract"](node, content)) + object.__setattr__(self, "content", elements["content"]["elmt_type"]["extract"](node, content)) return loadXMLTreeMethod @@ -1293,9 +1477,7 @@ extras.clear() for attr in classinfos["attributes"]: if attr["use"] != "prohibited": - if isinstance(attr["attr_type"], (UnicodeType, StringType)): - namespace, name = DecomposeQualifiedName(infos) - attr["attr_type"] = factory.GetQualifiedNameInfos(name, namespace) + attr["attr_type"] = FindTypeInfos(factory, attr["attr_type"]) value = getattr(self, attr["name"], None) if value != None: computed_value = attr["attr_type"]["generate"](value) @@ -1316,9 +1498,7 @@ else: first = True for element in classinfos["elements"]: - if isinstance(element["elmt_type"], (UnicodeType, StringType)): - namespace, name = DecomposeQualifiedName(infos) - element["elmt_type"] = factory.GetQualifiedNameInfos(name, namespace) + element["elmt_type"] = FindTypeInfos(factory, element["elmt_type"]) value = getattr(self, element["name"], None) if element["minOccurs"] == 0 and element["maxOccurs"] == 1: if value is not None: @@ -1330,7 +1510,10 @@ if first: text += u'>\n' first = False - text += element["elmt_type"]["generate"](value, element["name"], indent + 1) + if element["name"] == "content" and element["elmt_type"]["type"] == SIMPLETYPE: + text += element["elmt_type"]["generate"](value) + else: + text += element["elmt_type"]["generate"](value, element["name"], indent + 1) else: if first and len(value) > 0: text += u'>\n' @@ -1422,14 +1605,20 @@ for element_name, element in elements.items(): if element["minOccurs"] == 0: use = "optional" - if element_name == "content": + if element_name == "content" and element["type"] == CHOICE: attr_type = [(choice["name"], None) for choice in element["choices"]] if self.content is None: value = "" else: value = self.content["name"] if self.content["value"] is not None: - children.extend(self.content["value"].getElementInfos(self.content["name"])["children"]) + if self.content["name"] == "sequence": + choices_dict = dict([(choice["name"], choice) for choice in element["choices"]]) + sequence_infos = choices_dict.get("sequence", None) + if sequence_infos is not None: + children.extend([item.getElementInfos(infos["name"]) for item, infos in zip(self.content["value"], sequence_infos["elements"])]) + else: + children.extend(self.content["value"].getElementInfos(self.content["name"])["children"]) elif element["elmt_type"]["type"] == SIMPLETYPE: children.append({"name": element_name, "require": element["minOccurs"] != 0, "type": gettypeinfos(element["elmt_type"]["basename"], @@ -1495,9 +1684,7 @@ if classinfos.has_key("base"): classinfos["base"].__init__(self) for attribute in classinfos["attributes"]: - if isinstance(attribute["attr_type"], (UnicodeType, StringType)): - namespace, name = DecomposeQualifiedName(attribute["attr_type"]) - attribute["attr_type"] = factory.GetQualifiedNameInfos(name, namespace) + attribute["attr_type"] = FindTypeInfos(factory, attribute["attr_type"]) if attribute["use"] == "required": setattr(self, attribute["name"], attribute["attr_type"]["initial"]()) elif attribute["use"] == "optional": @@ -1506,21 +1693,7 @@ else: setattr(self, attribute["name"], None) for element in classinfos["elements"]: - if isinstance(element["elmt_type"], (UnicodeType, StringType)): - namespace, name = DecomposeQualifiedName(element["elmt_type"]) - element["elmt_type"] = factory.GetQualifiedNameInfos(name, namespace) - if element["minOccurs"] == 0 and element["maxOccurs"] == 1: - if "default" in element: - setattr(self, element["name"], element["elmt_type"]["extract"](element["default"], False)) - else: - setattr(self, element["name"], None) - elif element["minOccurs"] == 1 and element["maxOccurs"] == 1: - setattr(self, element["name"], element["elmt_type"]["initial"]()) - else: - value = [] - for i in xrange(element["minOccurs"]): - value.append(element["elmt_type"]["initial"]()) - setattr(self, element["name"], value) + setattr(self, element["name"], GetElementInitialValue(factory, element)) return initMethod def generateSetMethod(attr): @@ -1536,15 +1709,11 @@ def generateAddMethod(attr, factory, infos): def addMethod(self): if infos["type"] == ATTRIBUTE: - if isinstance(infos["attr_type"], (UnicodeType, StringType)): - namespace, name = DecomposeQualifiedName(infos) - infos["attr_type"] = factory.GetQualifiedNameInfos(name, namespace) + infos["attr_type"] = FindTypeInfos(factory, infos["attr_type"]) initial = infos["attr_type"]["initial"] extract = infos["attr_type"]["extract"] elif infos["type"] == ELEMENT: - if isinstance(infos["elmt_type"], (UnicodeType, StringType)): - namespace, name = DecomposeQualifiedName(infos) - infos["elmt_type"] = factory.GetQualifiedNameInfos(name, namespace) + infos["elmt_type"] = FindTypeInfos(factory, infos["elmt_type"]) initial = infos["elmt_type"]["initial"] extract = infos["elmt_type"]["extract"] else: @@ -1562,9 +1731,7 @@ def generateAppendMethod(attr, maxOccurs, factory, infos): def appendMethod(self, value): - if isinstance(infos["elmt_type"], (UnicodeType, StringType)): - namespace, name = DecomposeQualifiedName(infos) - infos["elmt_type"] = factory.GetQualifiedNameInfos(name, namespace) + infos["elmt_type"] = FindTypeInfos(factory, infos["elmt_type"]) attr_list = getattr(self, attr) if maxOccurs == "unbounded" or len(attr_list) < maxOccurs: if infos["elmt_type"]["check"](value): @@ -1577,9 +1744,7 @@ def generateInsertMethod(attr, maxOccurs, factory, infos): def insertMethod(self, index, value): - if isinstance(infos["elmt_type"], (UnicodeType, StringType)): - namespace, name = DecomposeQualifiedName(infos) - infos["elmt_type"] = factory.GetQualifiedNameInfos(name, namespace) + infos["elmt_type"] = FindTypeInfos(factory, infos["elmt_type"]) attr_list = getattr(self, attr) if maxOccurs == "unbounded" or len(attr_list) < maxOccurs: if infos["elmt_type"]["check"](value): @@ -1600,9 +1765,7 @@ def setChoiceMethod(self, type): if not choices.has_key(type): raise ValueError("Unknown \"%s\" choice type for \"content\"!" % type) - if isinstance(choices[type]["elmt_type"], (UnicodeType, StringType)): - namespace, name = DecomposeQualifiedName(choices[type]["elmt_type"]) - choices[name]["elmt_type"] = factory.GetQualifiedNameInfos(name, namespace) + choices[type]["elmt_type"] = FindTypeInfos(factory, choices[type]["elmt_type"]) new_element = choices[type]["elmt_type"]["initial"]() self.content = {"name": type, "value": new_element} return new_element @@ -1613,9 +1776,7 @@ def appendChoiceMethod(self, type): if not choices.has_key(type): raise ValueError("Unknown \"%s\" choice type for \"content\"!" % type) - if isinstance(choices[type]["elmt_type"], (UnicodeType, StringType)): - namespace, name = DecomposeQualifiedName(choices[type]["elmt_type"]) - choices[type]["elmt_type"] = factory.GetQualifiedNameInfos(name, namespace) + choices[type]["elmt_type"] = FindTypeInfos(factory, choices[type]["elmt_type"]) if maxOccurs == "unbounded" or len(self.content) < maxOccurs: new_element = choices[type]["elmt_type"]["initial"]() self.content.append({"name": type, "value": new_element}) @@ -1629,9 +1790,7 @@ def insertChoiceMethod(self, index, type): if not choices.has_key(type): raise ValueError("Unknown \"%s\" choice type for \"content\"!" % type) - if isinstance(choices[type]["elmt_type"], (UnicodeType, StringType)): - namespace, name = DecomposeQualifiedName(choices[type]["elmt_type"]) - choices[name]["elmt_type"] = factory.GetQualifiedNameInfos(name, namespace) + choices[type]["elmt_type"] = FindTypeInfos(factory, choices[type]["elmt_type"]) if maxOccurs == "unbounded" or len(self.content) < maxOccurs: new_element = choices[type]["elmt_type"]["initial"]() self.content.insert(index, {"name" : type, "value" : new_element}) @@ -1665,6 +1824,10 @@ sys._getframe(1).f_locals[ClassName] = Class for TypeName, Type in pluginTypes.items(): sys._getframe(1).f_locals[TypeName] = Type - globals().update(ComputedClasses) - return ComputedClasses - + if factory.FileName is not None and len(ComputedClasses) == 1: + globals().update(ComputedClasses[factory.FileName]) + return ComputedClasses[factory.FileName] + else: + globals().update(ComputedClasses) + return ComputedClasses + diff -r 4d6719c51f05 -r 89ff2738ef20 xmlclass/xsdschema.py --- a/xmlclass/xsdschema.py Fri Nov 18 17:40:40 2011 +0100 +++ b/xmlclass/xsdschema.py Wed Nov 23 00:19:27 2011 +0100 @@ -22,7 +22,7 @@ #License along with this library; if not, write to the Free Software #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -import re +import os, re import datetime from xml.dom import minidom from types import * @@ -143,19 +143,14 @@ return union -def ReduceSimpleType(factory, attributes, elements): - # Reduce all the simple type children - annotations, children = factory.ReduceElements(elements) - - typeinfos = children[0] - +def CreateSimpleType(factory, attributes, typeinfos): # Initialize type informations facets = {} - simpleType = {"type": SIMPLETYPE, "final": attributes.get("final", []), "doc": annotations} + simpleType = {"type": SIMPLETYPE, "final": attributes.get("final", [])} if attributes.has_key("name"): simpleType["name"] = attributes["name"] - if typeinfos["type"] == "restriction": + if typeinfos["type"] in ["restriction", "extension"]: # Search for base type definition if isinstance(typeinfos["base"], (StringType, UnicodeType)): basetypeinfos = factory.FindSchemaElement(typeinfos["base"], SIMPLETYPE) @@ -172,13 +167,13 @@ # Check that derivation is allowed if basetypeinfos.has_key("final"): - if basetypeinfos["final"].has_key("#all"): + if "#all" in basetypeinfos["final"]: raise ValueError("Base type can't be derivated!") - if basetypeinfos["final"].has_key("restriction"): + if "restriction" in basetypeinfos["final"] and typeinfos["type"] == "restriction": raise ValueError("Base type can't be derivated by restriction!") # Extract simple type facets - for facet in typeinfos["facets"]: + for facet in typeinfos.get("facets", []): facettype = facet["type"] if not basetypeinfos["facets"].has_key(facettype): raise ValueError("\"%s\" facet can't be defined for \"%s\" type!" % (facettype, type)) @@ -186,18 +181,20 @@ raise ValueError("\"%s\" facet is fixed on base type!" % facettype) value = facet["value"] basevalue = basetypeinfos["facets"][facettype][0] - if facettype == "enumeration": + if facettype in ["enumeration", "pattern"]: value = basetypeinfos["extract"](value, False) if len(facets) == 0: - facets["enumeration"] = ([value], False) + facets[facettype] = ([value], False) continue - elif facets.keys() == ["enumeration"]: - facets["enumeration"][0].append(value) + elif facets.keys() == [facettype]: + facets[facettype][0].append(value) continue else: - raise ValueError("\"enumeration\" facet can't be defined with another facet type!") + raise ValueError("\"%s\" facet can't be defined with another facet type!" % facettype) elif facets.has_key("enumeration"): raise ValueError("\"enumeration\" facet can't be defined with another facet type!") + elif facets.has_key("pattern"): + raise ValueError("\"pattern\" facet can't be defined with another facet type!") elif facets.has_key(facettype): raise ValueError("\"%s\" facet can't be defined two times!" % facettype) elif facettype == "length": @@ -313,10 +310,13 @@ elif facetname == "maxExclusive" and value >= facetvalue: raise ValueError("value must be lesser than %s" % str(facetvalue)) elif facetname == "pattern": - model = re.compile("(?:%s)?$" % facetvalue) + model = re.compile("(?:%s)?$" % "|".join(map(lambda x: "(?:%s)" % x, facetvalue))) result = model.match(value) if result is None: - raise ValueError("value doesn't follow the pattern %s" % facetvalue) + if len(facetvalue) > 1: + raise ValueError("value doesn't follow any of the patterns %s" % ",".join(facetvalue)) + else: + raise ValueError("value doesn't follow the pattern %s" % facetvalue[0]) elif facetname == "whiteSpace": if facetvalue == "replace": value = GetNormalizedString(value, False) @@ -344,10 +344,13 @@ elif facetname == "maxExclusive" and value >= facetvalue: return False elif facetname == "pattern": - model = re.compile("(?:%s)?$" % facetvalue) + model = re.compile("(?:%s)?$" % "|".join(map(lambda x: "(?:%s)" % x, facetvalue))) result = model.match(value) if result is None: - raise ValueError("value doesn't follow the pattern %s" % facetvalue) + if len(facetvalue) > 1: + raise ValueError("value doesn't follow any of the patterns %s" % ",".join(facetvalue)) + else: + raise ValueError("value doesn't follow the pattern %s" % facetvalue[0]) return True def SimpleTypeInitialValue(): @@ -478,19 +481,25 @@ simpleType["generate"] = GenerateSimpleType return simpleType +def ReduceSimpleType(factory, attributes, elements): + # Reduce all the simple type children + annotations, children = factory.ReduceElements(elements) + + simpleType = CreateSimpleType(factory, attributes, children[0]) + simpleType["doc"] = annotations + + return simpleType # Complex type -def ExtractAttributes(factory, elements, base = None): +def ExtractAttributes(factory, elements, base=None): + attrs = [] + attrnames = {} if base is not None: - basetypeinfos = factory.FindSchemaElement(base, COMPLEXTYPE) - if isinstance(basetypeinfos, (UnicodeType, StringType)): - attrnames = {} - else: + basetypeinfos = factory.FindSchemaElement(base) + if not isinstance(basetypeinfos, (UnicodeType, StringType)) and basetypeinfos["type"] == COMPLEXTYPE: attrnames = dict(map(lambda x:(x["name"], True), basetypeinfos["attributes"])) - else: - attrnames = {} - attrs = [] + for element in elements: if element["type"] == ATTRIBUTE: if attrnames.get(element["name"], False): @@ -524,13 +533,15 @@ while len(children) > 0 and children[0]["type"] in ALL_FACETS: restriction["facets"].append(children.pop(0)) - restriction["attributes"] = ExtractAttributes(factory, children) + restriction["attributes"] = ExtractAttributes(factory, children, restriction["base"]) return restriction def ReduceExtension(factory, attributes, elements): annotations, children = factory.ReduceElements(elements) - extension = {"type": "extension", "attributes": [], "elements": [], "base": attributes.get("base", None), "doc": annotations} + if not attributes.has_key("base"): + raise ValueError("No base type has been defined for extension!") + extension = {"type": "extension", "attributes": [], "elements": [], "base": attributes["base"], "doc": annotations} if len(children) > 0: if children[0]["type"] in ["group", "all", CHOICE, "sequence"]: group = children.pop(0) @@ -550,13 +561,33 @@ content = elmtgroup.copy() content["name"] = "content" extension["elements"].append(content) - extension["attributes"] = ExtractAttributes(factory, children, extension["base"]) + extension["attributes"] = ExtractAttributes(factory, children) return extension def ReduceSimpleContent(factory, attributes, elements): annotations, children = factory.ReduceElements(elements) + simpleContent = children[0].copy() + + basetypeinfos = factory.FindSchemaElement(simpleContent["base"]) + if basetypeinfos["type"] == SIMPLETYPE: + contenttypeinfos = simpleContent.copy() + simpleContent.pop("base") + elif basetypeinfos["type"] == COMPLEXTYPE and \ + len(basetypeinfos["elements"]) == 1 and \ + basetypeinfos["elements"][0]["name"] == "content" and \ + basetypeinfos["elements"][0].has_key("elmt_type") and \ + basetypeinfos["elements"][0]["elmt_type"]["type"] == SIMPLETYPE: + contenttypeinfos = simpleContent.copy() + contenttypeinfos["base"] = basetypeinfos["elements"][0]["elmt_type"] + else: + raise ValueError("No compatible base type defined for simpleContent!") + contenttypeinfos = CreateSimpleType(factory, attributes, contenttypeinfos) + + simpleContent["elements"] = [{"name": "content", "type": ELEMENT, + "elmt_type": contenttypeinfos, "doc": annotations, + "minOccurs": 1, "maxOccurs": 1}] simpleContent["type"] = "simpleContent" return simpleContent @@ -570,7 +601,7 @@ def ReduceComplexType(factory, attributes, elements): annotations, children = factory.ReduceElements(elements) - + if len(children) > 0: if children[0]["type"] in ["simpleContent", "complexContent"]: complexType = children[0].copy() @@ -582,15 +613,24 @@ complexType.update(attributes) group = children.pop(0) if group["type"] in ["all", "sequence"]: - if group["minOccurs"] == 0 or group["maxOccurs"] != 1: - if len(group["elements"]) > 1: - raise ValueError("Not supported yet!") - if group["minOccurs"] == 0: - group["elements"][0]["minOccurs"] = group["minOccurs"] - if group["maxOccurs"] != 1: - group["elements"][0]["maxOccurs"] = group["maxOccurs"] - complexType["elements"] = group["elements"] - complexType["order"] = group["order"] + choice_number = 0 + for element in group["elements"]: + if element["type"] == CHOICE: + choice_number += 1 + if (group["minOccurs"] == 0 or group["maxOccurs"] != 1) and len(group["elements"]) > 1 or choice_number > 1: + content = {"type": CHOICE, "name": "content", "choices": [group], "minOccurs": 1, "maxOccurs": 1} + complexType["elements"].append(content) + else: + if len(group["elements"]) == 1: + if group["minOccurs"] == 0: + group["elements"][0]["minOccurs"] = group["minOccurs"] + if group["maxOccurs"] != 1: + group["elements"][0]["maxOccurs"] = group["maxOccurs"] + for element in group["elements"]: + if element["type"] == CHOICE: + element["name"] = "content" + complexType["elements"] = group["elements"] + complexType["order"] = group["order"] elif group["type"] == CHOICE: content = group.copy() content["name"] = "content" @@ -673,23 +713,32 @@ return any def ReduceElement(factory, attributes, elements): + annotations, children = factory.ReduceElements(elements) + + types = [] + constraints = [] + for child in children: + if child["type"] == CONSTRAINT: + constraints.append(child) + else: + types.append(child) + if attributes.has_key("default") and attributes.has_key("fixed"): raise ValueError("\"default\" and \"fixed\" can't be defined at the same time!") if attributes.has_key("ref"): - annotations, children = factory.ReduceElements(elements) - for attr in ["name", "default", "fixed", "form", "block", "type"]: if attributes.has_key(attr): raise ValueError("\"ref\" and \"%s\" can't be defined at the same time!" % attr) if attributes.has_key("nillable"): raise ValueError("\"ref\" and \"nillable\" can't be defined at the same time!") - if len(children) > 0: + if len(types) > 0: raise ValueError("No type and no constraints can be defined where \"ref\" is defined!") infos = factory.FindSchemaElement(attributes["ref"], ELEMENT) if infos is not None: element = infos.copy() + element["constraints"] = constraints element["minOccurs"] = attributes["minOccurs"] element["maxOccurs"] = attributes["maxOccurs"] return element @@ -697,12 +746,10 @@ raise ValueError("\"%s\" base type isn't defined or circular referenced!" % name) elif attributes.has_key("name"): - annotations, children = factory.ReduceElements(elements) - - element = {"type": ELEMENT, "elmt_type": attributes.get("type", None), "doc": annotations} - if len(children) > 0: + element = {"type": ELEMENT, "elmt_type": attributes.get("type", None), "constraints": constraints, "doc": annotations} + if len(types) > 0: if element["elmt_type"] is None: - element["elmt_type"] = children[0] + element["elmt_type"] = types[0] else: raise ValueError("Only one type can be defined for attribute!") elif element["elmt_type"] is None: @@ -738,7 +785,9 @@ if child["type"] in [ELEMENT, ANY, TAG]: choices.append(child) elif child["type"] == "sequence": - raise ValueError("\"sequence\" in \"choice\" is not supported. Create instead a new complex type!") + child["minOccurs"] = child["maxOccurs"] = 1 + choices.append(child) + #raise ValueError("\"sequence\" in \"choice\" is not supported. Create instead a new complex type!") elif child["type"] == CHOICE: choices.extend(child["choices"]) elif child["type"] == "group": @@ -767,12 +816,8 @@ sequence = [] for child in children: - if child["type"] in [ELEMENT, ANY, TAG]: + if child["type"] in [ELEMENT, ANY, TAG, CHOICE]: sequence.append(child) - elif child["type"] == CHOICE: - content = child.copy() - content["name"] = "content" - sequence.append(content) elif child["type"] == "sequence": sequence.extend(child["elements"]) elif child["type"] == "group": @@ -815,23 +860,38 @@ def ReduceUnique(factory, attributes, elements): annotations, children = factory.ReduceElements(elements) - raise ValueError("\"unique\" element isn't supported yet!") - + + unique = {"type": CONSTRAINT, "const_type": "unique", "selector": children[0], "fields": children[1:]} + unique.update(attributes) + return unique + def ReduceKey(factory, attributes, elements): annotations, children = factory.ReduceElements(elements) - raise ValueError("\"key\" element isn't supported yet!") - + + key = {"type": CONSTRAINT, "const_type": "key", "selector": children[0], "fields": children[1:]} + key.update(attributes) + return key + def ReduceKeyRef(factory, attributes, elements): annotations, children = factory.ReduceElements(elements) - raise ValueError("\"keyref\" element isn't supported yet!") + + keyref = {"type": CONSTRAINT, "const_type": "keyref", "selector": children[0], "fields": children[1:]} + keyref.update(attributes) + return keyref def ReduceSelector(factory, attributes, elements): annotations, children = factory.ReduceElements(elements) - raise ValueError("\"selector\" element isn't supported yet!") + + selector = {"type": CONSTRAINT, "const_type": "selector"} + selector.update(attributes) + return selector def ReduceField(factory, attributes, elements): annotations, children = factory.ReduceElements(elements) - raise ValueError("\"field\" element isn't supported yet!") + + field = {"type": CONSTRAINT, "const_type": "field"} + field.update(attributes) + return field # Inclusion elements @@ -842,7 +902,25 @@ def ReduceInclude(factory, attributes, elements): annotations, children = factory.ReduceElements(elements) - raise ValueError("\"include\" element isn't supported yet!") + + if factory.FileName is None: + raise ValueError("Include in XSD string not yet supported") + filepath = attributes["schemaLocation"] + if filepath is not None and not os.path.exists(filepath): + filepath = os.path.join(factory.BaseFolder, filepath) + if not os.path.exists(filepath): + raise ValueError("No file '%s' found for include" % attributes["schemaLocation"]) + xsdfile = open(filepath, 'r') + include_factory = XSDClassFactory(minidom.parse(xsdfile), filepath) + xsdfile.close() + include_factory.CreateClasses() + + if factory.TargetNamespace == include_factory.TargetNamespace: + factory.Namespaces[factory.TargetNamespace].update(include_factory.Namespaces[include_factory.TargetNamespace]) + else: + factory.Namespaces[include_factory.TargetNamespace] = include_factory.Namespaces[include_factory.TargetNamespace] + factory.ComputedClasses.update(include_factory.ComputedClasses) + return None def ReduceRedefine(factory, attributes, elements): annotations, children = factory.ReduceElements(elements) @@ -885,7 +963,7 @@ return False for name, value in schema.items(): ref_value = reference.get(name, None) - if ref_value is None: + if ref_value is None and value != None: return False result = CompareSchema(value, ref_value) if not result: @@ -905,8 +983,8 @@ class XSDClassFactory(ClassFactory): - def __init__(self, document, debug = False): - ClassFactory.__init__(self, document, debug) + def __init__(self, document, filepath=None, debug=False): + ClassFactory.__init__(self, document, filepath, debug) self.Namespaces["xml"] = { "lang": { "type": SYNTAXATTRIBUTE, @@ -943,7 +1021,10 @@ } def ParseSchema(self): - schema = self.Document.childNodes[0] + for child in self.Document.childNodes: + if child.nodeType == self.Document.ELEMENT_NODE: + schema = child + break for qualified_name, attr in schema._attrs.items(): value = GetAttributeValue(attr) if value == "http://www.w3.org/2001/XMLSchema": @@ -957,7 +1038,7 @@ self.Schema = XSD_NAMESPACE["schema"]["extract"]["default"](self, schema) ReduceSchema(self, self.Schema[1], self.Schema[2]) - def FindSchemaElement(self, element_name, element_type): + def FindSchemaElement(self, element_name, element_type=None): namespace, name = DecomposeQualifiedName(element_name) element = self.GetQualifiedNameInfos(name, namespace, True) if element is None and namespace == self.TargetNamespace and name not in self.CurrentCompilations: @@ -970,29 +1051,28 @@ if name in self.CurrentCompilations: if self.Debug: print "Warning : \"%s\" is circular referenced!" % element_name - return element_name else: raise ValueError("\"%s\" isn't defined!" % element_name) - if element["type"] != element_type: - raise ValueError("\"%s\" isn't a group!" % element_name) + if element_type is not None and element["type"] != element_type: + raise ValueError("\"%s\" isn't of the expected type!" % element_name) return element def CreateSchemaElement(self, element_name, element_type): for type, attributes, elements in self.Schema[2]: namespace, name = DecomposeQualifiedName(type) - if attributes.has_key("name") and attributes["name"] == element_name: + if attributes.get("name", None) == element_name: element_infos = None - if element_type == ATTRIBUTE and name == "attribute": + if element_type in (ATTRIBUTE, None) and name == "attribute": element_infos = ReduceAttribute(self, attributes, elements) - elif element_type == ELEMENT and name == "element": + elif element_type in (ELEMENT, None) and name == "element": element_infos = ReduceElement(self, attributes, elements) - elif element_type == ATTRIBUTESGROUP and name == "attributeGroup": + elif element_type in (ATTRIBUTESGROUP, None) and name == "attributeGroup": element_infos = ReduceAttributeGroup(self, attributes, elements) - elif element_type == ELEMENTSGROUP and name == "group": + elif element_type in (ELEMENTSGROUP, None) and name == "group": element_infos = ReduceGroup(self, attributes, elements) - elif element_type == SIMPLETYPE and name == "simpleType": + elif element_type in (SIMPLETYPE, None) and name == "simpleType": element_infos = ReduceSimpleType(self, attributes, elements) - elif element_type == COMPLEXTYPE and name == "complexType": + elif element_type in (COMPLEXTYPE, None) and name == "complexType": element_infos = ReduceComplexType(self, attributes, elements) if element_infos is not None: self.Namespaces[self.TargetNamespace][element_name] = element_infos @@ -1002,20 +1082,17 @@ """ This function opens the xsd file and generate the classes from the xml tree """ -def GenerateClassesFromXSD(filename, declare = False): - xsdfile = open(filename, 'r') - factory = XSDClassFactory(minidom.parse(xsdfile)) +def GenerateClassesFromXSD(filepath, declare=False): + xsdfile = open(filepath, 'r') + factory = XSDClassFactory(minidom.parse(xsdfile), filepath) xsdfile.close() - factory.ParseSchema() return GenerateClasses(factory, declare) """ This function generate the classes from the xsd given as a string """ -def GenerateClassesFromXSDstring(xsdstring, declare = False): - factory = XSDClassFactory(minidom.parseString(xsdstring)) - factory.ParseSchema() - return GenerateClasses(factory, declare) +def GenerateClassesFromXSDstring(xsdstring, declare=False): + return GenerateClasses(XSDClassFactory(minidom.parseString(xsdstring)), declare) #------------------------------------------------------------------------------- @@ -1371,7 +1448,7 @@ "type": SYNTAXELEMENT, "extract": { "default": GenerateElement("key", ["id", "name"], - re.compile("((?:annotation )?(?:selector |(?:field )+))")) + re.compile("((?:annotation )?(?:selector (?:field )+))")) }, "reduce": ReduceKey }, @@ -1387,7 +1464,7 @@ "type": SYNTAXELEMENT, "extract": { "default": GenerateElement("keyref", ["id", "name", "refer"], - re.compile("((?:annotation )?(?:selector |(?:field )+))")) + re.compile("((?:annotation )?(?:selector (?:field )+))")) }, "reduce": ReduceKeyRef }, @@ -1986,7 +2063,8 @@ "xpath": { "type": SYNTAXATTRIBUTE, "extract": { - "default": NotSupportedYet("xpath") +# "default": NotSupportedYet("xpath") + "default": GetAttributeValue } }, @@ -2031,7 +2109,7 @@ "facets": STRING_FACETS, "generate": GenerateSimpleTypeXMLText(str), "initial": lambda: 0, - "check": lambda x: isinstance(x, IntType) + "check": lambda x: isinstance(x, (IntType, LongType)) }, "hexBinary": { @@ -2041,7 +2119,7 @@ "facets": STRING_FACETS, "generate": GenerateSimpleTypeXMLText(lambda x: ("%."+str(int(round(len("%X"%x)/2.)*2))+"X")%x), "initial": lambda: 0, - "check": lambda x: isinstance(x, IntType) + "check": lambda x: isinstance(x, (IntType, LongType)) }, "integer": {