2012-07-05 8 views
12

Ich analysiere eine XML-Datei, die von einem externen program generiert wurde. Ich möchte dann benutzerdefinierte Anmerkungen zu dieser Datei hinzufügen, die meinen eigenen Namespace verwenden. Meine Eingabe sieht wie folgt:lxml: Namespace zur Eingabedatei hinzufügen

<sbml xmlns="http://www.sbml.org/sbml/level2/version4" xmlns:celldesigner="http://www.sbml.org/2001/ns/celldesigner" level="2" version="4"> 
    <model metaid="untitled" id="untitled"> 
    <annotation>...</annotation> 
    <listOfUnitDefinitions>...</listOfUnitDefinitions> 
    <listOfCompartments>...</listOfCompartments> 
    <listOfSpecies> 
     <species metaid="s1" id="s1" name="GenA" compartment="default" initialAmount="0"> 
     <annotation> 
      <celldesigner:extension>...</celldesigner:extension> 
     </annotation> 
     </species> 
     <species metaid="s2" id="s2" name="s2" compartment="default" initialAmount="0"> 
     <annotation> 
      <celldesigner:extension>...</celldesigner:extension> 
     </annotation> 
     </species> 
    </listOfSpecies> 
    <listOfReactions>...</listOfReactions> 
    </model> 
</sbml> 

Das Problem ist, dass lxml nur Namespaces deklariert, wenn sie verwendet werden, was bedeutet, dass die Erklärung viele Male wiederholt wird, wie so (vereinfacht):

<sbml xmlns="namespace" xmlns:celldesigner="morenamespace" level="2" version="4"> 
    <listOfSpecies> 
    <species> 
     <kjw:test xmlns:kjw="http://this.is.some/custom_namespace"/> 
     <celldesigner:data>Some important data which must be kept</celldesigner:data> 
    </species> 
    <species> 
     <kjw:test xmlns:kjw="http://this.is.some/custom_namespace"/> 
    </species> 
    .... 
    </listOfSpecies> 
</sbml> 

es ist möglich, Lxml zu zwingen, diese Deklaration nur einmal in einem Elternelement zu schreiben, wie sbml oder listOfSpecies? Oder gibt es einen guten Grund, dies nicht zu tun? Das Ergebnis möchte ich wäre:

<sbml xmlns="namespace" xmlns:celldesigner="morenamespace" level="2" version="4" xmlns:kjw="http://this.is.some/custom_namespace"> 
    <listOfSpecies> 
    <species> 
     <kjw:test/> 
     <celldesigner:data>Some important data which must be kept</celldesigner:data> 
    </species> 
    <species> 
     <kjw:test/> 
    </species> 
    .... 
    </listOfSpecies> 
</sbml> 

Das wichtige Problem ist, dass die vorhandenen Daten, die aus einer Datei gelesen wird, gehalten werden muss, so kann ich nicht nur ein neues Root-Element machen (ich glaube?).

EDIT: Code unten angehängt.

def annotateSbml(sbml_input): 
    from lxml import etree 

    checkSbml(sbml_input) # Makes sure the input is valid sbml/xml. 

    ns = "http://this.is.some/custom_namespace" 
    etree.register_namespace('kjw', ns) 

    sbml_doc = etree.ElementTree() 
    root = sbml_doc.parse(sbml_input, etree.XMLParser(remove_blank_text=True)) 
    nsmap = root.nsmap 
    nsmap['sbml'] = nsmap[None] # Makes code more readable, but seems ugly. Any alternatives to this? 
    nsmap['kjw'] = ns 
    ns = '{' + ns + '}' 
    sbmlns = '{' + nsmap['sbml'] + '}' 

    for species in root.findall('sbml:model/sbml:listOfSpecies/sbml:species', nsmap): 
    species.append(etree.Element(ns + 'test')) 

    sbml_doc.write("test.sbml.xml", pretty_print=True, xml_declaration=True) 

    return 
+1

Zeigen Sie Ihren Code. – Marcin

+0

@Marcin: fertig. Irgendwelche Tipps? – kai

+0

@mzjin meine Eingabe enthält alles außer den '' Tags. Das Ziel besteht darin, solche Markierungen (oder ähnliche, z. B. "kjw: score" oder "kjw: length") für jede Art in dieser Liste einzufügen. Macht das Sinn, oder sollte ich die ganze Datei posten (meine ursprüngliche Frage war lang genug, wie es ist)? – kai

Antwort

8

Das Ändern der Namespace-Zuordnung eines Knotens ist in Lxml nicht möglich. Siehe this open ticket, die diese Funktion als Wunschliste hat.

Es stammt von this thread auf der lxml-Mailing-Liste, wo eine workaround replacing the root node als Alternative gegeben wird. Es gibt jedoch einige Probleme beim Ersetzen des Stammknotens: siehe das obige Ticket.

ich die vorgeschlagene Wurzel Ersatz Abhilfe Code hier der Vollständigkeit halber setzen werde:

>>> DOC = """<sbml xmlns="http://www.sbml.org/sbml/level2/version4" xmlns:celldesigner="http://www.sbml.org/2001/ns/celldesigner" level="2" version="4"> 
... <model metaid="untitled" id="untitled"> 
...  <annotation>...</annotation> 
...  <listOfUnitDefinitions>...</listOfUnitDefinitions> 
...  <listOfCompartments>...</listOfCompartments> 
...  <listOfSpecies> 
...  <species metaid="s1" id="s1" name="GenA" compartment="default" initialAmount="0"> 
...   <annotation> 
...   <celldesigner:extension>...</celldesigner:extension> 
...   </annotation> 
...  </species> 
...  <species metaid="s2" id="s2" name="s2" compartment="default" initialAmount="0"> 
...   <annotation> 
...   <celldesigner:extension>...</celldesigner:extension> 
...   </annotation> 
...  </species> 
...  </listOfSpecies> 
...  <listOfReactions>...</listOfReactions> 
... </model> 
... </sbml>""" 
>>> 
>>> from lxml import etree 
>>> from StringIO import StringIO 
>>> NS = "http://this.is.some/custom_namespace" 
>>> tree = etree.ElementTree(element=None, file=StringIO(DOC)) 
>>> root = tree.getroot() 
>>> nsmap = root.nsmap 
>>> nsmap['kjw'] = NS 
>>> new_root = etree.Element(root.tag, nsmap=nsmap) 
>>> new_root[:] = root[:] 
>>> new_root.append(etree.Element('{%s}%s' % (NS, 'test'))) 
>>> new_root.append(etree.Element('{%s}%s' % (NS, 'test'))) 

>>> print etree.tostring(new_root, pretty_print=True) 
<sbml xmlns:celldesigner="http://www.sbml.org/2001/ns/celldesigner" xmlns:kjw="http://this.is.some/custom_namespace" xmlns="http://www.sbml.org/sbml/level2/version4"><model metaid="untitled" id="untitled"> 
    <annotation>...</annotation> 
    <listOfUnitDefinitions>...</listOfUnitDefinitions> 
    <listOfCompartments>...</listOfCompartments> 
    <listOfSpecies> 
     <species metaid="s1" id="s1" name="GenA" compartment="default" initialAmount="0"> 
     <annotation> 
      <celldesigner:extension>...</celldesigner:extension> 
     </annotation> 
     </species> 
     <species metaid="s2" id="s2" name="s2" compartment="default" initialAmount="0"> 
     <annotation> 
      <celldesigner:extension>...</celldesigner:extension> 
     </annotation> 
     </species> 
    </listOfSpecies> 
    <listOfReactions>...</listOfReactions> 
    </model> 
<kjw:test/><kjw:test/></sbml> 
+1

Für zukünftige Referenz erfordert dies eine kleine Änderung (mindestens Python 3.2), andernfalls gibt ein TypeError von "** root.nsmap", wenn es den 'None: 'Namespace' trifft, da' None' keine Zeichenfolge ist. Mit 'nsmap = root.nsmap; '' nsmap [' kjw '] = NS; '' new_root = etree.Element (root.tag, nsmap = nsmap); 'funktioniert. – kai

+0

netter Fang, aktualisiert – jterrace

+0

Sie müssen auch Attrib, Text und (unwahrscheinlich, aber nur für Vollständigkeit) Schwanz zu kopieren. 'nsmap = dict (kjw = NS, nsmap = nsmap)) ist falsch; es sollte nur 'nsmap = nsmap' sein – jfs

0

Sie können das Stammelement ersetzen, um "kjw" zu seinem nsmap hinzuzufügen. Dann wäre die xmlns-Deklaration nur im root-Element.

3

Anstatt direkt mit dem rohen XML beschäftigen Sie auch in Richtung LibSBML, eine Bibliothek zur Manipulation von SBML Dokumenten mit Sprachbindungen aussehen könnte unter anderem Python. Dort würden Sie es wie folgt verwenden:

 
>>> from libsbml import * 
>>> doc = readSBML('Dropbox/SBML Models/BorisEJB.xml') 
>>> species = doc.getModel().getSpecies('MAPK') 
>>> species.appendAnnotation('<kjw:test xmlns:kjw="http://this.is.some/custom_namespace"/>') 
0 
>>> species.toSBML() 
'<species id="MAPK" compartment="compartment" initialConcentration="280" boundaryCondition="false">\n <annotation>\n 
<kjw:test xmlns:kjw="http://this.is.some/custom_namespace"/>\n </annotation>\n</species>' 
>>> 

1

Wenn Sie vorübergehend ein Namespaced-Attribut zum Stammknoten hinzufügen, tut das den Trick

ns = '{http://this.is.some/custom_namespace}' 

# add 'kjw:foobar' attribute to root node 
root.set(ns+'foobar', 'foobar') 

# add kjw namespace elements (or attributes) elsewhere 
... get child element species ... 
species.append(etree.Element(ns + 'test')) 

# remove temporary namespaced attribute from root node 
del root.attrib[ns+'foobar'] 
1

Ich weiß, diese alte Frage, aber es immer noch gültig und ab lxml 3.5.0, gibt es wahrscheinlich eine bessere Lösung für dieses Problem:

cleanup_namespaces() übernimmt ein neues Argument top_nsmap die Definitionen bewegt der bereitgestellten Präfix-Namespace-Zuordnung an den Anfang der Struktur.

So, jetzt die Namensraum Karte kann sich mit einfachen Aufruf dazu bewegt werden:

nsmap = {'kjw': 'http://this.is.some/custom_namespace'} 
etree.cleanup_namespaces(root, top_nsmap=nsmap)