2012-07-05 10 views
12

Estoy analizando un archivo xml generado por un program externo. Luego me gustaría agregar anotaciones personalizadas a este archivo, usando mi propio espacio de nombres. Mi entrada se ve de la siguiente manera:lxml: agregar espacio de nombres al archivo de entrada

<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> 

La cuestión es que lxml sólo declara espacios de nombres cuando se utilizan, lo que significa que la declaración se repite muchas veces, al igual que (simplificado):

<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 Es posible forzar a lxml a escribir esta declaración solo una vez en un elemento principal, como sbml o listOfSpecies? ¿O hay una buena razón para no hacerlo? El resultado que quiero sería:

<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> 

El problema importante es que los datos existentes que se lee de un archivo se debe mantener, por lo que no se puede simplemente hacer un nuevo elemento raíz (creo?).

EDITAR: Código adjunto a continuación.

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

Muestre su código. – Marcin

+0

@Marcin: hecho. ¿Algun consejo? – kai

+0

@mzjin mi entrada contiene todo excepto las etiquetas ''. El objetivo es insertar dichas etiquetas (o similares, por ejemplo, 'kjw: score' o' kjw: length') para cada especie en esta lista. ¿Tiene esto sentido, o debería publicar el archivo completo (supongo que mi pregunta original fue lo suficientemente larga como es)? – kai

Respuesta

8

No es posible modificar el mapeo del espacio de nombres de un nodo en lxml. Vea this open ticket que tiene esta característica como un elemento de la lista de deseos.

Se originó a partir de this thread en la lista de correo lxml, donde se da un workaround replacing the root node como alternativa. Sin embargo, existen algunos problemas para reemplazar el nodo raíz: ver el ticket anterior.

voy a poner el código de solución reemplazo de la raíz sugerido aquí para completar:

>>> 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

Para referencia futura esto requiere una pequeña alteración (en Python 3.2 al menos), de lo contrario da un TypeError de '** root.nsmap' cuando golpea el' NOMBRE: 'namespace'' ya que 'None' no es una cadena. Usando 'nsmap = root.nsmap;' 'nsmap ['kjw'] = NS;' 'new_root = etree.Element (root.tag, nsmap = nsmap);' funciona. – kai

+0

nice catch, actualizado – jterrace

+0

también necesita copiar attrib, texto, y (improbable, pero solo por completitud) tail. 'nsmap = dict (kjw = NS, nsmap = nsmap))' es incorrecto; debería ser solo 'nsmap = nsmap' – jfs

0

Puede reemplazar el elemento raíz para agregar 'kjw' a su nsmap. Entonces la declaración xmlns estaría solo en el elemento raíz.

3

En lugar de tratar directamente con el XML sin procesar también se puede mirar hacia LibSBML, una biblioteca para la manipulación de documentos SBML con enlaces de lenguaje para , entre otros, pitón. Hay que usaría así:

 
>>> 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

Si se agrega temporalmente un atributo de espacio de nombres al nodo raíz, que hace el truco.

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

Sé que esto es cuestión de edad, sino que sigue siendo válida ya partir de lxml 3.5.0, es probable que haya una mejor solución a este problema:

cleanup_namespaces() acepta un nuevo argumento top_nsmap que se mueve definiciones del mapeo de espacio de nombres de prefijo proporcionado a la parte superior del árbol.

Así que ahora el mapa espacio de nombres puede ser movido hacia arriba con una llamada simple a este:

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