I think it's really easy to use iterparse
to build a custom data extractor that completely removes the need for using objectify.
For the sake of this example, I've used a .NET reference XML file that looks a bit like this:
<doc>
<assembly>
<name>System.IO</name>
</assembly>
<members>
<member name="T:System.IO.BinaryReader">
<summary>Reads primitive data types as binary values in a specific encoding.</summary>
<filterpriority>2</filterpriority>
</member>
<member name="M:System.IO.BinaryReader.#ctor(System.IO.Stream)">
<summary>Initializes a new instance of the <see cref="T:System.IO.BinaryReader" /> class based on the specified stream and using UTF-8 encoding.</summary>
<param name="input">The input stream. </param>
<exception cref="T:System.ArgumentException">The stream does not support reading, is null, or is already closed. </exception>
</member>
<member name="M:System.IO.BinaryReader.#ctor(System.IO.Stream,System.Text.Encoding)">
<summary>Initializes a new instance of the <see cref="T:System.IO.BinaryReader" /> class based on the specified stream and character encoding.</summary>
<param name="input">The input stream. </param>
<param name="encoding">The character encoding to use. </param>
<exception cref="T:System.ArgumentException">The stream does not support reading, is null, or is already closed. </exception>
<exception cref="T:System.ArgumentNullException">
<paramref name="encoding" /> is null. </exception>
</member>
<!-- ... many more members like this -->
</members>
</doc>
Assuming you would want to extract all members with their names, summaries and attributes as a list of dicts like this:
{
'summary': 'Reads primitive data types as binary values in a specific encoding.',
'name': 'T:System.IO.BinaryReader'
}
{
'summary': 'Initializes a new instance of the ',
'@input': 'The input stream. ',
'name': 'M:System.IO.BinaryReader.#ctor(System.IO.Stream)'
}
{
'summary': 'Initializes a new instance of the class based on the specified stream and using UTF-8 encoding.',
'@input': 'The input stream. ',
'@encoding': 'The character encoding to use. ',
'name': 'M:System.IO.BinaryReader.#ctor(System.IO.Stream,System.Text.Encoding)'
}
you could do it like this:
- use
lxml.iterparse
with start
and end
events
- when a
<member>
element starts, prepare a new dict (item
)
- when we're inside a
<member>
element, add anything we're interested in to the dict
- when the
<member>
element ends, finalize the dict and yield it
- setting
item
to None
functions as the "inside/outside of <member>
"-flag
In code:
import lxml
from lxml import etree
def text_content(elt):
return ' '.join([t.strip() for t in elt.itertext()])
def extract_data(xmlfile):
item = None
for event, elt in etree.iterparse(xmlfile, events=['start', 'end']):
if elt.tag == 'member':
if event == 'start':
item = {}
else:
item['name'] = elt.attrib['name']
yield item
item = None
if item == None:
continue
if event == 'end':
if elt.tag in ('summary', 'returns'):
item[elt.tag] = text_content(elt)
continue
if elt.tag == 'param':
item['@' + elt.attrib['name']] = text_content(elt)
continue
testfile = r'C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETCore\v4.5.1\System.IO.xml'
for item in extract_data(testfile):
print(item)
This way you get the fastest and most memory-efficient parsing and fine control over what data you look at. Using objectify
would be more wasteful than that even without the intermediate tostring()
/fromstring()
.