Lxml: добавить пространство имен для ввода файла
Я разбираю 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>
Важная проблема заключается в том, что существующие данные, которые считываются из файла, должны храниться, поэтому я не могу просто создать новый корневой элемент (я думаю?).
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
Изменение отображения пространства имен в узле невозможно в lxml. См. этот открытый билет, который имеет эту функцию как элемент списка пожеланий.
Он возник из этого потока на 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> ... <listOfUnitDefinitions> ... </listOfUnitDefinitions> ... <listOfCompartments> ... </listOfCompartments> ... <listOfSpecies> ... < species metaid = "s1" id = "s1" name= "GenA" отделение = "default" initialAmount = "0" & gt;
... < аннотация > ... < celldesigner: extension > ... </celldesigner: extension > ... </annotation> ... </species> ... < species metaid = "s2" id = "s2" name= "s2" отделение = "default" initialAmount = "0" & gt;
... < аннотация > ... < celldesigner: extension > ... </celldesigner: extension > ... </annotation> ... </species> ... </listOfSpecies> ... <listOfReactions> ... </listOfReactions> ... </model> ... </sbml> "" → → → от lxml import etree → > из 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. msgstr "". < & аннотации GT;... </аннотации > <listOfUnitDefinitions> ... </listOfUnitDefinitions> < & listOfCompartments GT;... </listOfCompartments> <listOfSpecies> < species metaid = "s1" id = "s1" name= "GenA" отделение = "default" initialAmount = "0" & gt; < & аннотации GT; < celldesigner: расширение > ... </celldesigner: расширение > </аннотации > < &/вида GT; < species metaid = "s2" id = "s2" name= "s2" отделение = "default" initialAmount = "0" & gt; < & аннотации GT; < celldesigner: расширение > ... </celldesigner: расширение > </аннотации > < &/вида GT; < &/listOfSpecies GT; <listOfReactions> ... </listOfReactions> </модель > < KJW: испытание / > < KJW: испытание / > </SBML> Код >
Ответ 2
Вместо прямого обращения с необработанным XML вы также можете посмотреть LibSBML, библиотеку для управления документами SBML с привязками языка, среди прочего, питона. Там вы будете использовать его следующим образом:
>>> 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>'
>>>
Ответ 3
Если вы временно добавите атрибут с именами в корневой каталог node, который выполняет трюк.
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']
Ответ 4
Я знаю, что это старый вопрос, но он по-прежнему действителен, и, как и в случае с lxml 3.5.0, вероятно, лучшее решение этой проблемы:
cleanup_namespaces()
принимает новый аргумент top_nsmap
, который перемещает определения предоставленного префикса-пространства имен в верхнюю часть дерева.
Итак, теперь карту пространства имен можно перенести с помощью простого вызова:
nsmap = {'kjw': 'http://this.is.some/custom_namespace'}
etree.cleanup_namespaces(root, top_nsmap=nsmap)
Ответ 5
Вы можете заменить корневой элемент, чтобы добавить 'kjw' в свой nsmap. Тогда объявление xmlns будет только в корневом элементе.