Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Iterate over content / child elements #261

Closed
hcw70 opened this issue Sep 16, 2020 · 5 comments
Closed

Iterate over content / child elements #261

hcw70 opened this issue Sep 16, 2020 · 5 comments
Labels
question Further information is requested

Comments

@hcw70
Copy link

hcw70 commented Sep 16, 2020

In an xsdata representation created from a XSD schema from an XML document like the following

<?xml version="1.0" encoding="UTF-8"?>
<MessageDB xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                    xsi:schemaLocation="http://deuta.de/config/DeutaMsgDatabase MessageDB.xsd"
                    xmlns="http://deuta.de/config/DeutaMsgDatabase">
                    <module name="root">
                        <datatypes>
                            <packettype name="pckType1">
                                <var name="Var0" type="UNSIGNED32" bitsize="1"/>
                            </packettype>
                        </datatypes>
                    </module>
</MessageDB>

is there a uniform way to iterate over the nested elements, maybe something like

for child in obj.child_elems():
    print (child)

?

In my current TMessageDb class created by xsdata, there is an attribute "modules" of kind list, but
the child elements are stored in different attributes depending on the class, so here TDataTypes would have an list element "packettype".

Regards

@tefra
Copy link
Owner

tefra commented Sep 16, 2020

No there is no such helper, but it's not very difficult to write one, here something similar that I wrote in order to fetch all the obj children that matched a base class.

def children(self, condition: Condition = None) -> Iterator["ElementBase"]:
"""Iterate over all the ElementBase children of this element that match
the given condition if any."""
for f in fields(self):
value = getattr(self, f.name)
if (
isinstance(value, list)
and len(value) > 0
and isinstance(value[0], ElementBase)
):
yield from (val for val in value if not condition or condition(val))
elif isinstance(value, ElementBase):
if not condition or condition(value):
yield value

@tefra tefra added the question Further information is requested label Sep 16, 2020
@hcw70
Copy link
Author

hcw70 commented Sep 17, 2020

Agreed, after digging into dataclass deeper, i came to a similair solution, thanks for providing the info!

However, after further investigation, it looks like the order of the elements yielded by that is not in document order
(if the xsd has a sequence of choices of elements) but packet together based on the element type.

Since the order in our XML docs are very relevant in most cases, this is a no-go for us. Should i raise a new issue for this?

@tefra
Copy link
Owner

tefra commented Sep 17, 2020

Oh I see what you need.

Cool that's part of the serializer logic I could move it to a public helper, in pr #247

def next_value(cls, meta: XmlMeta, obj: Any) -> Iterator[Tuple[XmlVar, Any]]:
"""
Return the fields and their values in the correct order according to
their definition and sequential metadata.
Sequential fields need to be rendered together in parallel order
eg: <a1/><a2/><a1/><a/2></a1>
"""
index = 0
attrs = meta.vars
stop = len(attrs)
while index < stop:
var = attrs[index]
if not var.sequential:
yield var, getattr(obj, var.name)
index += 1
continue
end = next(
(i for i in range(index + 1, stop) if not attrs[i].sequential), stop
)
sequence = attrs[index:end]
index = end
j = 0
rolling = True
while rolling:
rolling = False
for var in sequence:
values = getattr(obj, var.name)
if j < len(values):
rolling = True
yield var, values[j]
j += 1

@tefra
Copy link
Owner

tefra commented Sep 19, 2020

Hey man take a look at XmlSerializer.next_value

In [1]: from xsdata.formats.dataclass.serializers import XmlSerializer

In [2]: from xsdata.formats.dataclass.context import XmlContext

In [3]: from tests.fixtures.books.fixtures import books

In [4]: books.book[0]
Out[4]: BookForm(author='Hightower, Kim', title='The First Book', genre='Fiction', price=44.95, pub_date='2000-10-01', review='An amazing story of nothing.', id='bk001', lang='en')

In [7]: context = XmlContext()

In [8]: meta = context.fetch(books.__class__)

In [9]: for var, value in XmlSerializer.next_value(books, meta):
   ...:     print(value)
   ...: 
[BookForm(author='Hightower, Kim', title='The First Book', genre='Fiction', price=44.95, pub_date='2000-10-01', review='An amazing story of nothing.', id='bk001', lang='en'), BookForm(author='Nagata, Suanne', title='Becoming Somebody', genre='Biography', price=33.95, pub_date='2001-01-10', review='A masterpiece of the fine art of gossiping.', id='bk002', lang='en')]

In the generator you can do a check for list instance and create a recursive method, the var holds teh attribute metadata used for binding purposes you can ignore it. The values will be in the order the fields are defined with support for sequential elements as well.

@tefra tefra closed this as completed Sep 19, 2020
@hcw70
Copy link
Author

hcw70 commented Sep 21, 2020

Thank you. i will have a look!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants