Analizator de scheme XML (Revizuirea codului, Python, Parsing, Xml, Xsd)

Mike J a intrebat.

Am lucrat la un parser de schemă XML ușor, și am ceea ce cred că este o soluție moderat curată (unele părți ajutate de întrebările anterioare pe care le-am postat aici) până acum pentru obținerea tuturor detaliilor schemei, dar aș dori orice critică care ar putea ajuta la îmbunătățirea în continuare a acestui cod.

Mai jos am furnizat clasa de schemă pe care am scris-o și apoi un exemplu de fișier schema.txt pe care clasa de schemă îl va deschide dacă este rulată ca main. Apelurile clasei schema de sub „main” pot fi modificate dacă doriți să vedeți mai bine structura de date a schemei și am câteva funcții de însoțire pe care le-am scris pentru clasă pentru a extrage detalii specifice pe care nu le-am pus aici pentru că încă mai trebuie să lucrez la ele.

schema.py:

from lxml import etree

INDICATORS = ["all", "sequence", "choice"]
TYPES = ["simpleType", "complexType"]

class schema:

    def __init__(self, schemafile):
        if schemafile is None:
            print "Error creating Schema: Invalid schema file used"
            return

        self.schema = self.create_schema(etree.parse(schemafile))

    def create_schema(self, schema_data):
        def getXSVal(element): #removes namespace
            return element.tag.split('}')[-1]

        def get_simple_type(element):
            return {
                "name": element.get("name"),
                "restriction": element.getchildren()[0].attrib,
                "elements": [ e.get("value") for e in element.getchildren()[0].getchildren() ]
        }

        def get_simple_content(element):
            return {
                "simpleContent": {
                    "extension": element.getchildren()[0].attrib,
                    "attributes": [ a.attrib for a in element.getchildren()[0].getchildren() ]
                }
            }

        def get_elements(element):


            if len(element.getchildren()) == 0:
                return element.attrib

            data = {}

            ename = element.get("name")
            tag = getXSVal(element)

            if ename is None:
                if tag == "simpleContent":
                    return get_simple_content(element)
                elif tag in INDICATORS:
                    data["indicator"] = tag
                elif tag in TYPES:
                    data["type"] = tag
                else:
                    data["option"] = tag

            else:
                if tag == "simpleType":
                    return get_simple_type(element)
                else: 
                    data.update(element.attrib)

            data["elements"] = []
            data["attributes"] = []
            children = element.getchildren()        

            for child in children:
                if child.get("name") is not None:
                    data[getXSVal(child)+"s"].append(get_elements(child))
                elif tag in INDICATORS and getXSVal(child) in INDICATORS:
                    data["elements"].append(get_elements(child))
                else:
                    data.update(get_elements(child))

            if len(data["elements"]) == 0:
                del data["elements"]
            if len(data["attributes"]) == 0:
                del data["attributes"]

            return data

        schema = {}
        root = schema_data.getroot()
        children = root.getchildren()
        for child in children:
            c_type = getXSVal(child)
            if child.get("name") is not None and not c_type in schema:
                schema[c_type] = []
            schema[c_type].append(get_elements(child))
        return schema

    def get_Types(self, t_name):
        types = []
        for t in self.schema[t_name]:
            types.append(t["name"])
        return types

    def get_simpleTypes(self):
        return self.get_Types("simpleType")

    def get_complexTypes(self):
        return self.get_Types("complexType")


if __name__ == '__main__':
    fschema = open("schema.txt")

    schema = schema(fschema)

    print schema.get_simpleTypes()
    print schema.get_complexTypes()

schema.txt:

<?xml version="1.0" encoding="utf-8"?>
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" version="3.0" xmlns_xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="main_object">
    <xs:complexType>
      <xs:choice maxOccurs="unbounded">
        <xs:element minOccurs="1" maxOccurs="1" name="source">
          <xs:complexType>
            <xs:all>
              <xs:element name="name" type="xs:string" />
              <xs:element name="group_id" type="xs:integer" />
              <xs:element minOccurs="0" name="description" type="xs:string" />
            </xs:all>
            <xs:attribute name="id" type="xs:integer" use="required" />
          </xs:complexType>
        </xs:element>
        <xs:element minOccurs="1" maxOccurs="1" name="event">
          <xs:complexType>
            <xs:all>
              <xs:element name="date" type="xs:date" />
              <xs:element minOccurs="0" name="event_type" type="xs:string" />
              <xs:element minOccurs="0" name="event_hours" type="xs:string" />
            </xs:all>
            <xs:attribute name="id" type="xs:integer" use="required" />
          </xs:complexType>
        </xs:element>
        <xs:element name="state">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="name" type="xs:string" />
            </xs:sequence>
            <xs:attribute name="id" type="xs:integer" use="required" />
          </xs:complexType>
        </xs:element>
        <xs:element name="location">
          <xs:complexType>
            <xs:all>
              <xs:element name="address" type="simpleAddressType" />
              <xs:element minOccurs="0" name="directions" type="xs:string" />
              <xs:element minOccurs="0" name="hours" type="xs:string" />
            </xs:all>
            <xs:attribute name="id" type="xs:integer" use="required" />
          </xs:complexType>
        </xs:element>
        <xs:element name="selection_item">
          <xs:complexType>
            <xs:sequence>
              <xs:element minOccurs="0" name="selection_type_id" type="xs:integer" />
              <xs:element minOccurs="0" maxOccurs="unbounded" name="option_id">
                <xs:complexType>
                  <xs:simpleContent>
                    <xs:extension base="xs:integer">
                      <xs:attribute name="sort_order" type="xs:integer" />
                    </xs:extension>
                  </xs:simpleContent>
                </xs:complexType>
              </xs:element>
            </xs:sequence>
            <xs:attribute name="id" type="xs:integer" use="required" />
          </xs:complexType>
        </xs:element>
        <xs:element name="custom_selection">
          <xs:complexType>
            <xs:choice maxOccurs="unbounded">
              <xs:element name="heading" type="xs:string" />
              <xs:sequence maxOccurs="unbounded">
                <xs:element name="response_id">
                  <xs:complexType>
                    <xs:simpleContent>
                      <xs:extension base="xs:integer">
                        <xs:attribute name="sort_order" type="xs:integer" />
                      </xs:extension>
                    </xs:simpleContent>
                  </xs:complexType>
                </xs:element>
              </xs:sequence>
            </xs:choice>
            <xs:attribute name="id" type="xs:integer" use="required" />
          </xs:complexType>
        </xs:element>
        <xs:element name="response">
          <xs:complexType>
            <xs:all>
              <xs:element name="text" type="xs:string" />
              <xs:element minOccurs="0" name="sort_order" type="xs:integer" />
            </xs:all>
            <xs:attribute name="id" type="xs:integer" use="required" />
          </xs:complexType>
        </xs:element>
       </xs:choice>
      <xs:attribute fixed="3.0" name="schemaVersion" type="xs:decimal" use="required" />
    </xs:complexType>
  </xs:element>

<xs:complexType name="simpleAddressType">
    <xs:all>
        <xs:element minOccurs="0" name="location_name" type="xs:string"/> 
        <xs:element name="line1" type="xs:string"/>
        <xs:element minOccurs="0" name="line2" type="xs:string"/>
        <xs:element minOccurs="0" name="line3" type="xs:string"/>
        <xs:element name="city" type="xs:string"/>
        <xs:element name="state" type="xs:string"/>
        <xs:element name="zip" type="xs:string"/>
    </xs:all> 
</xs:complexType> 

<xs:complexType name="certified">
  <xs:simpleContent>
    <xs:extension base="xs:integer">
      <xs:attribute name="certification" type="certificationEnum" />
    </xs:extension>
  </xs:simpleContent>
</xs:complexType>

  <xs:simpleType name="certificationEnum">
    <xs:restriction base="xs:string">
      <xs:enumeration value="unofficial_partial"/>
      <xs:enumeration value="unofficial_complete"/>
      <xs:enumeration value="certified"/>
      <xs:enumeration value="Unofficial_partial"/>
      <xs:enumeration value="Unofficial_complete"/>
      <xs:enumeration value="Unofficial_Partial"/>
      <xs:enumeration value="Unofficial_Complete"/>
      <xs:enumeration value="Certified"/>
    </xs:restriction>
  </xs:simpleType> 

  <xs:simpleType name="yesNoEnum">
    <xs:restriction base="xs:string">
      <xs:enumeration value="yes"/>
      <xs:enumeration value="no"/>
      <xs:enumeration value="Yes"/>
      <xs:enumeration value="No"/>
      <xs:enumeration value="YES"/>
      <xs:enumeration value="NO"/>
    </xs:restriction>
  </xs:simpleType> 

</xs:schema>

Comentarii

2 răspunsuri
Winston Ewert
from lxml import etree

INDICATORS = ["all", "sequence", "choice"]
TYPES = ["simpleType", "complexType"]

class schema:

Convenția Python este de a numi clasele folosind CamelCase.

    def __init__(self, schemafile):
        if schemafile is None:
            print "Error creating Schema: Invalid schema file used"
            return

Utilizați excepțiile raportează erori în python. Nu imprimați problemele la ieșirea standard și apoi încercați să continuați. Nimic bun nu va ieși din asta. De fapt, nici măcar nu trebuie să verificați dacă există None, pentru că oricum va eșua pe linia următoare.

        self.schema = self.create_schema(etree.parse(schemafile))

    def create_schema(self, schema_data):
        def getXSVal(element): #removes namespace
            return element.tag.split('}')[-1]

Nu ar trebui cel puțin să verificați dacă spațiul de nume este corect?

        def get_simple_type(element):
            return {
                "name": element.get("name"),
                "restriction": element.getchildren()[0].attrib,
                "elements": [ e.get("value") for e in element.getchildren()[0].getchildren() ]
        }

Se pare că folosiți un dicționar ca pe un obiect. Poate că ar trebui să creați de fapt un obiect SimpleType cu aceste atribute.

        def get_simple_content(element):
            return {
                "simpleContent": {
                    "extension": element.getchildren()[0].attrib,
                    "attributes": [ a.attrib for a in element.getchildren()[0].getchildren() ]
                }
            }

        def get_elements(element):

Nu am nicio idee despre ce încearcă să facă această funcție.

            if len(element.getchildren()) == 0:
                return element.attrib

            data = {}

            ename = element.get("name")
            tag = getXSVal(element)

            if ename is None:

Pare ciudat că se verifică numele, dar nu se face nimic cu el.

                if tag == "simpleContent":
                    return get_simple_content(element)

Este confuz modul în care uneori returnezi ceva, alteori adaugi într-un dicționar.

                elif tag in INDICATORS:
                    data["indicator"] = tag
                elif tag in TYPES:
                    data["type"] = tag
                else:
                    data["option"] = tag

            else:
                if tag == "simpleType":
                    return get_simple_type(element)
                else: 
                    data.update(element.attrib)

Nu prea înțeleg care este teoria pentru această condiție. Văd că același cod apare de mai multe ori, ceea ce mă face să mă întreb dacă poate fi refactorizat pentru a fi mai curat.

            data["elements"] = []
            data["attributes"] = []
            children = element.getchildren()        

            for child in children:

Combinați ultimele două linii

                if child.get("name") is not None:
                    data[getXSVal(child)+"s"].append(get_elements(child))
                elif tag in INDICATORS and getXSVal(child) in INDICATORS:
                    data["elements"].append(get_elements(child))
                else:
                    data.update(get_elements(child))

            if len(data["elements"]) == 0:
                del data["elements"]
            if len(data["attributes"]) == 0:
                del data["attributes"]

Chiar vrei să faci asta? Mi se pare că va îngreuna scrierea codului care folosește datele.

            return data

Aceste funcții lungi ca funcții interioare miros urât. Sugerează că poate ar trebui să fie într-o altă clasă sau ceva de genul acesta.

        schema = {}
        root = schema_data.getroot()
        children = root.getchildren()
        for child in children:
            c_type = getXSVal(child)
            if child.get("name") is not None and not c_type in schema:
                schema[c_type] = []

Dacă numele este None, nu va cauza o eroare în linia următoare?

            schema[c_type].append(get_elements(child))

În schimb, utilizați schema.setdefault(c_type,[]).append(get_elements(child)) va avea grijă să adauge lista la prima adăugare.

        return schema

    def get_Types(self, t_name):

Conversia Python este lowercase_with_underscores pentru numele metodelor

        types = []
        for t in self.schema[t_name]:
            types.append(t["name"])
        return types

Eu aș folosi return [t["name"] for t in self.schema[t_name]]

    def get_simpleTypes(self):
        return self.get_Types("simpleType")

    def get_complexTypes(self):
        return self.get_Types("complexType")


if __name__ == '__main__':
    fschema = open("schema.txt")

Vă sugerez să folosiți with pentru a vă asigura că este închisă

    schema = schema(fschema)

    print schema.get_simpleTypes()
    print schema.get_complexTypes()

Problema mea generală cu abordarea dvs. este că transformați schema xml într-o grămadă de dicționare nestructurate. Rezultatul nu va fi mult mai ușor de lucrat decât obiectele XML originale. Va fi o adevărată pacoste să scrii cod pentru a lucra cu reprezentarea schemei pe care ai produs-o. Fără a cunoaște scopul general al motivului pentru care analizați schema, nu vă pot sugera cum să o îmbunătățiți.

Cu privire la noul tău vesrion

    try:
        self.schema = self.create_schema(etree.parse(schemafile))
    except:
        print "Error creating Schema: Invalid schema file used"

NU! Nu prindeți excepția. Lăsați programul să moară pur și simplu. Niciodată, niciodată, niciodată, niciodată, niciodată, nu imprimați pe ecran în cazul în care apare o eroare.

Iată versiunea mea a ceea ce ai făcut.

from lxml import etree
from copy import copy
SCHEMA_SPACE = "{http://www.w3.org/2001/XMLSchema}"

class Schema:

    def __init__(self, schemafile):
        self.root = etree.parse(schemafile)

    def findall(self, path):
        return self.root.findall( path.replace("xs:", SCHEMA_SPACE) )

    def find(self, path):
        return self.root.find( path.replace("xs:", SCHEMA_SPACE) )

    def names_of(self, nodes):
        return [node.get("name") for node in nodes]

    def get_Types(self, t_name):
        return self.names_of( self.findall(t_name) ) 

    def get_simpleTypes(self):
        return self.get_Types("xs:simpleType")

    def get_complexTypes(self):
        return self.get_Types("xs:complexType")

    def get_elements_of_attribute(self, attribute):
        return self.names_of(self.findall(".//xs:element/xs:complexType/xs:" + attribute + "/../.."))

    def get_element_attributes(self, name): 

        node = self.find(".//xs:element[@name='" + name + "']")
        if node is None:
            node = self.find(".//xs:complexType[@name='" + name + "']")

        if node is None:
            return None
        else:
            return node.attrib


if __name__ == '__main__':
    with open("schema.txt") as f:

        schema = Schema(f)

        print schema.get_simpleTypes()
        print schema.get_complexTypes()
        print schema.get_elements_of_attribute("all")

        print schema.get_element_attributes("source")
        print schema.get_element_attributes("contact_id")

Copierea tuturor datelor în dicționarele python nu ajută. Este mult mai ușor să extrageți aceste informații direct din XML.

Comentarii

  • Mulțumesc pentru toate sugestiile dumneavoastră. Lucruri cu care sunt de acord și pe care le-am schimbat: nume de clase cu majuscule camel, excepția de pornire, toate sugestiile de consolidare a codului, probabil clase pentru obiectele simpleType și simpleContent. Lucruri pe care ar trebui să le explic mai bine: Aceasta este structura de date de bază, am scris o grămadă de metode de ajutor pentru a accesa rapid toate proprietățile conținute în cadrul acesteia pentru a analiza documentele XML, a produce fișiere plate din acestea, a valida datele din baza de date în conformitate cu schema și alte câteva lucruri. Distincția dicționar/somație se face în funcție de faptul că obiectul este o proprietate a elementului rădăcină sau este un element nou în sine-  > Por Mike J.
  • Sper că asta explică majoritatea condițiilor, de asemenea, de ce frecvent „name” este ignorat. Funcția internă a clasei get_element trebuia să fie recursivă și să verifice toate condițiile, dar aveți dreptate, aș putea să desprind o parte din logică. Ați menționat codul repetat, dar nu am putut găsi un alt loc în care să folosesc acea logică condițională în acest segment de cod, îmi scapă ceva evident? Am schimbat „get_Type” în „get_type”, folosit inițial pentru că standardele xsd pentru „simpleType” și „complexType” sunt scrise astfel. Având în vedere acest lucru, aveți alte sugestii despre cum aș putea gestiona mai bine logica de analiză?-  > Por Mike J.
  • @MikeJ, referitor la duplicarea codului: la o a doua privire, pur și simplu nu pot să citesc. Pentru a oferi sugestii mai bune, ar trebui să văd cum folosiți această structură de date pe care o construiți.-  > Por Winston Ewert.
  • Am adăugat mai multe funcții de ajutor pe care le-am scris și mai multe exemple de apeluri de funcții pentru a arăta, sper, o idee mai bună despre cum folosesc această structură de date.  > Por Mike J.
  • @MikeJ, vezi editarea. Practic, structura ta de date nu te ajută să îndeplinești niciuna dintre aceste sarcini. De fapt, este ușor de extras din documentul xml original.-  > Por Winston Ewert.
Michael Kay

Numele tipului sau al spațiului de nume ‘X’ nu a putut fi găsit (lipsește o directivă using sau o referință de asamblare?)

JohnDoDoDo

Am o problemă ciudată pe care nu o pot localiza.

Comentarii

    Am un proiect SP 2010 care conține două subproiecte de tip bibliotecă de clase (

  • > Por și .
  • ).

Proiectul se construiește bine, fără nici un fel de erori, dar odată ce îl implementez pe server primesc erori în fereastra „Error List” a VisualStudio: