Вопрос по lxml, namespaces, xml, python – lxml: добавить пространство имен во входной файл

12

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

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

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

РЕДАКТИРОВАТЬ: Код прилагается ниже.

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
Покажите свой код. Marcin
@mzjin мой вход содержит все, кроме<kjw:test/> теги. Цель состоит в том, чтобы вставить такие теги (или аналогичные, например,kjw:score или жеkjw:length) к каждому виду в этом списке. Имеет ли это смысл, или я должен опубликовать весь файл (полагал, что мой первоначальный вопрос был достаточно длинным)? kai
@mzjin Извините, это слишком упрощенно. Да, он действительно содержит модельные теги. Я использовалsbml:model теги вместе сnsmap['sbml'] = nsmap[None] таким образом, синтаксический анализатор правильно заменяет пространство имен в модели корневым пространством имен, которое, по-видимому, не выглядит иначе. kai
@ Марчин: готово. Какие-нибудь советы? kai

Ваш Ответ

4   ответа
0

добавив «kjw» к его nsmap. Тогда объявление xmlns будет только в корневом элементе.

2

что это старый вопрос, но он все еще действителен, и начиная с lxml 3.5.0, возможно, есть лучшее решение этой проблемы:

cleanup_namespaces() accepts a new argument top_nsmap that moves definitions of the provided prefix-namespace mapping to the top of the tree.

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

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

этот открытый билет который имеет эту функцию как элемент списка желаний.

Он возник изэта тема в списке рассылки lxml, гдеОбходной путь замены корневого узла дается в качестве альтернативы. Есть некоторые проблемы с заменой корневого узла, хотя: см. Билет выше.

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

>>> 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>
хороший улов, обновленный
вам также нужно скопировать атрибут, текст и (маловероятно, но только для полноты) хвост.nsmap=dict(kjw=NS, nsmap=nsmap)) неправильно; это должно быть простоnsmap=nsmap
Для дальнейшего использования это требует небольшого изменения (по крайней мере на Python 3.2), в противном случае выдается TypeError из**root.nsmap когда он попадает вNone:'namespace' какNone это не строка С помощьюnsmap = root.nsmap; nsmap['kjw'] = NS; new_root = etree.Element(root.tag, nsmap = nsmap); работает. kai
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>'
>>>

Похожие вопросы