2012-07-05 2 views
12

Я разбираю XML-файл, созданный внешним program. Затем я хотел бы добавить пользовательские аннотации к этому файлу, используя собственное пространство имен. Мой вход выглядит, как показано ниже:lxml: добавить пространство имен для ввода файла

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

Проблема в том, что LXML только декларирует пространство имен, когда они используются, что означает, что декларация повторяется много раз, как это (упрощенный):

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

Является ли это можно заставить lxml написать это объявление только один раз в родительском элементе, например sbml или listOfSpecies? Или есть веская причина не делать этого? В результате я хочу бы:

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

важная проблема состоит в том, что существующие данные, которые считываются из файла должны быть сохранены, так что я не могу просто сделать новый корневой элемент (я думаю?).

EDIT: код прилагается ниже.

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

Показать код. – Marcin

+0

@ Марцин: сделано. Какие-нибудь советы? – kai

+0

@mzjin Мой вход содержит все, кроме тегов ''. Цель состоит в том, чтобы вставить эти теги (или подобные, например, «kjw: score» или «kjw: length») каждому виду в этом списке. Имеет ли это смысл, или я должен опубликовать весь файл (предположил, что мой первоначальный вопрос был достаточно длинным, как есть)? – kai

ответ

8

Изменение отображения пространства имен узла невозможно в lxml. См. this open ticket, который имеет эту функцию как элемент списка пожеланий.

Источник информации от this thread в рассылке lxml, где в качестве альтернативы предоставляется workaround replacing the root node. Есть некоторые проблемы с заменой корневого узла, хотя: см. Билет выше.

Я положу предложенный корень замена коды обходного пути для полноты:

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

Для дальнейшего использования это требует небольшого изменения (по крайней мере на Python 3.2), в противном случае дает TypeError из '** root.nsmap', когда он попадает в пространство имен« Нет: », поскольку« Нет »не является строкой. Использование 'nsmap = root.nsmap;' 'nsmap ['kjw'] = NS;' 'new_root = etree.Element (root.tag, nsmap = nsmap);' работает. – kai

+0

nice catch, обновленный – jterrace

+0

вам также нужно скопировать атрибут, текст и (маловероятно, но только для полноты) хвост. 'nsmap = dict (kjw = NS, nsmap = nsmap))' неверно; это должно быть просто 'nsmap = nsmap' – jfs

0

Вы можете заменить корневой элемент, чтобы добавить 'kjw' в свой nsmap. Тогда объявление xmlns будет только в корневом элементе.

3

Вместо того, чтобы дело непосредственно с сырым XML вы также можете посмотреть в стороне LibSBML, библиотеку для работы с SBML документов с языковыми привязками для , среди прочего, python. Там вы будете использовать его как это:

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

Если вы временно добавить атрибут пространства имён в корневой узел, который делает трюк.

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

Я знаю, что это старый вопрос, но он по-прежнему действует и на LXML 3.5.0, то, вероятно, лучшее решение этой проблемы:

cleanup_namespaces() принимает новый аргумент top_nsmap, который перемещает определения предоставленного префикса-пространства имен в верхней части дерева.

Так что теперь отображение пространства имен может быть перемещено с простым вызовом к этому:

nsmap = {'kjw': 'http://this.is.some/custom_namespace'} 
etree.cleanup_namespaces(root, top_nsmap=nsmap) 
Смежные вопросы