Repository: tefra/xsdata
Pull request #482 (2021-05-05 21:06:43)

Performance tweaks
Quick wins after profiling with the netex collection and the 150mb sample from #476 

**1. The xml parser comes with an event emitter**

The emitter was designed for the the internal Schema and Defintions parsers.
Split the emitter to a new UserXmlParser for whoever needs it instead of imposing it for all.

**2. Refactor xml var finders to avoid iterations and extra caching**

All class xml vars are stored in a sequence, during lookup we loop through all vars in order to identify the best matching option, although the result is cached and the current solution is quite elegant is not fully optimized. Split the vars into dedicated properties that won't need additional caching at all and will allow direct qname lookup for attributes/elements

Bonuses
 - This opens the way for **truly** sequential fields, 
 - This fixed a few mismatches for classes that include multiple xs:anyAttribute derived fields.


**Before:**
first parse - xml context warmup: 51.35779690742493
parse - lxml EventHandler: 51.03626036643982
parse - xml EventHandler (native python): 46.76586675643921

**After:**
first parse - xml context warmup: 42.239562034606934
parse - lxml EventHandler: 41.22812223434448
parse - xml EventHandler (native python): 37.97033977508545





Patch:
diff --git a/docs/api/parsing.rst b/docs/api/parsing.rst
index 8fae93e6..c4c282a7 100644
--- a/docs/api/parsing.rst
+++ b/docs/api/parsing.rst
@@ -10,6 +10,7 @@ Parsing
     :nosignatures:
 
     XmlParser
+    UserXmlParser
     JsonParser
 
 .. currentmodule:: xsdata.formats.dataclass.parsers.config
diff --git a/xsdata/codegen/parsers/schema.py b/xsdata/codegen/parsers/schema.py
index aa05b86b..dc9f42f7 100644
--- a/xsdata/codegen/parsers/schema.py
+++ b/xsdata/codegen/parsers/schema.py
@@ -11,7 +11,7 @@ from urllib.parse import urljoin
 from xsdata.formats.bindings import T
 from xsdata.formats.dataclass.parsers.mixins import XmlNode
 from xsdata.formats.dataclass.parsers.nodes import Parsed
-from xsdata.formats.dataclass.parsers.xml import XmlParser
+from xsdata.formats.dataclass.parsers.xml import UserXmlParser
 from xsdata.models import xsd
 from xsdata.models.enums import FormType
 from xsdata.models.enums import Mode
@@ -19,7 +19,7 @@ from xsdata.models.enums import Namespace
 
 
 @dataclass
-class SchemaParser(XmlParser):
+class SchemaParser(UserXmlParser):
     """
     A simple parser to convert an xsd schema to an easy to handle data
     structure based on dataclasses.
diff --git a/xsdata/formats/dataclass/models/builders.py b/xsdata/formats/dataclass/models/builders.py
index 76780ba4..57b945a2 100644
--- a/xsdata/formats/dataclass/models/builders.py
+++ b/xsdata/formats/dataclass/models/builders.py
@@ -65,12 +65,38 @@ class XmlMetaBuilder:
             clazz, namespace, element_name_generator, attribute_name_generator
         )
 
+        attributes = {}
+        elements = defaultdict(list)
+        choices = []
+        any_attributes = []
+        wildcards = []
+        text = None
+
+        for var in class_vars:
+            if var.is_attribute:
+                attributes[var.qname] = var
+            elif var.is_element:
+                elements[var.qname].append(var)
+            elif var.is_elements:
+                choices.append(var)
+            elif var.is_attributes:
+                any_attributes.append(var)
+            elif var.is_wildcard:
+                wildcards.append(var)
+            else:  # var.is_text
+                text = var
+
         return XmlMeta(
             clazz=clazz,
             qname=build_qname(namespace, local_name),
             source_qname=build_qname(source_namespace, local_name),
             nillable=nillable,
-            vars=tuple(class_vars),
+            text=text,
+            attributes=attributes,
+            elements=elements,
+            choices=choices,
+            any_attributes=any_attributes,
+            wildcards=wildcards,
         )
 
     @classmethod
@@ -91,8 +117,9 @@ class XmlMetaBuilder:
             attribute_name_generator=attribute_name_generator,
         )
 
-        for var in fields(clazz):
+        for index, var in enumerate(fields(clazz)):
             yield builder.build(
+                index,
                 var.name,
                 type_hints[var.name],
                 var.metadata,
@@ -156,6 +183,7 @@ class XmlVarBuilder:
 
     def build(
         self,
+        index: int,
         name: str,
         type_hint: Any,
         metadata: Mapping[str, Any],
@@ -188,19 +216,18 @@ class XmlVarBuilder:
         any_type = self.is_any_type(types, xml_type)
         namespaces = self.resolve_namespaces(xml_type, namespace)
         default_namespace = self.default_namespace(namespaces)
-        choice_vars = self.build_choices(name, choices, globalns)
         qname = build_qname(default_namespace, local_name)
 
-        (
-            element,
-            elements,
-            attribute,
-            attributes,
-            wildcard,
-            text,
-        ) = self.get_xml_attributes(xml_type)
+        elements = {}
+        wildcards = []
+        for choice in self.build_choices(name, choices, globalns):
+            if choice.is_element:
+                elements[choice.qname] = choice
+            else:  # choice.is_wildcard:
+                wildcards.append(choice)
 
-        return XmlVar(
+        var = XmlVar(
+            index=index,
             name=name,
             qname=qname,
             init=init,
@@ -214,23 +241,33 @@ class XmlVarBuilder:
             list_element=element_list,
             default=default_value,
             types=types,
-            choices=tuple(choice_vars),
-            namespaces=namespaces,
-            element=element,
             elements=elements,
-            attribute=attribute,
-            attributes=attributes,
-            wildcard=wildcard,
-            text=text,
+            wildcards=wildcards,
+            namespaces=namespaces,
         )
 
+        if xml_type == XmlType.ELEMENT:
+            var.is_element = True
+        elif xml_type == XmlType.ELEMENTS:
+            var.is_elements = True
+        elif xml_type == XmlType.ATTRIBUTE:
+            var.is_attribute = True
+        elif xml_type == XmlType.ATTRIBUTES:
+            var.is_attributes = True
+        elif xml_type == XmlType.WILDCARD:
+            var.is_wildcard = True
+        else:
+            var.is_text = True
+
+        return var
+
     def build_choices(
         self, name: str, choices: List[Dict], globalns: Any
     ) -> Iterator[XmlVar]:
         """Build the binding metadata for a compound dataclass field."""
         existing_types: Set[type] = set()
 
-        for choice in choices:
+        for index, choice in enumerate(choices):
             default_value = choice.get("default_factory", choice.get("default"))
 
             metadata = choice.copy()
@@ -242,8 +279,10 @@ class XmlVarBuilder:
             else:
                 metadata["type"] = XmlType.ELEMENT
 
-            var = self.build(name, type_hint, metadata, True, default_value, globalns)
-            var.list_element = False
+            var = self.build(
+                index, name, type_hint, metadata, True, default_value, globalns
+            )
+            var.list_element = True
 
             if var.any_type or any(True for tp in var.types if tp in existing_types):
                 var.derived = True
@@ -352,25 +391,6 @@ class XmlVarBuilder:
 
         return False
 
-    @classmethod
-    def get_xml_attributes(cls, xml_type: str) -> Tuple[bool, ...]:
-        element = elements = attribute = attributes = wildcard = text = False
-
-        if xml_type == XmlType.ELEMENT:
-            element = True
-        elif xml_type == XmlType.ELEMENTS:
-            elements = True
-        elif xml_type == XmlType.ATTRIBUTE:
-            attribute = True
-        elif xml_type == XmlType.ATTRIBUTES:
-            attributes = True
-        elif xml_type == XmlType.WILDCARD:
-            wildcard = True
-        else:
-            text = True
-
-        return element, elements, attribute, attributes, wildcard, text
-
     @classmethod
     def analyze_types(
         cls, type_hint: Any, globalns: Any
diff --git a/xsdata/formats/dataclass/models/elements.py b/xsdata/formats/dataclass/models/elements.py
index 2c51687c..2dd43cd5 100644
--- a/xsdata/formats/dataclass/models/elements.py
+++ b/xsdata/formats/dataclass/models/elements.py
@@ -1,14 +1,19 @@
+import operator
 from dataclasses import dataclass
 from dataclasses import field
 from dataclasses import is_dataclass
 from typing import Any
 from typing import Dict
+from typing import Iterator
+from typing import List
 from typing import Optional
 from typing import Sequence
 from typing import Tuple
 from typing import Type
 
 from xsdata.models.enums import NamespaceType
+from xsdata.utils import collections
+from xsdata.utils.constants import EMPTY_SEQUENCE
 from xsdata.utils.namespaces import split_qname
 
 NoneType = type(None)
@@ -33,15 +38,18 @@ class XmlVar:
     :param sequential: Render values in sequential mode
     :param list_element: Field is a list of elements
     :param default: Field default value or factory
-    :param text: Field is derived from xs:simpleType
-    :param element: Field is derived from xs:element
-    :param elements: Field is derived from xs:choice
-    :param wildcard: Field is derived from xs:anyType
-    :param attribute: Field is derived from xs:attribute
-    :param attributes: Field is derived from xs:attributes
+    :param is_text: Field is derived from xs:simpleType
+    :param is_element: Field is derived from xs:element
+    :param is_elements: Field is derived from xs:choice
+    :param is_wildcard: Field is derived from xs:anyType
+    :param is_attribute: Field is derived from xs:attribute
+    :param is_attributes: Field is derived from xs:attributes
+    :param index: Field ordering
     :param types: List of all the supported data types
-    :param choices: List of repeatable choice elements
     :param namespaces: List of the supported namespaces
+    :param elements: Mapping of qname-repeatable elements
+    :param wildcards: List of repeatable wildcards
+    :ivar namespace_matches: Matching cache for the repeatable wildcards
     """
 
     name: str
@@ -57,15 +65,18 @@ class XmlVar:
     sequential: bool = False
     list_element: bool = False
     default: Any = None
-    text: bool = False
-    element: bool = False
-    elements: bool = False
-    wildcard: bool = False
-    attribute: bool = False
-    attributes: bool = False
+    is_text: bool = False
+    is_element: bool = False
+    is_elements: bool = False
+    is_wildcard: bool = False
+    is_attribute: bool = False
+    is_attributes: bool = False
+    index: int = field(default_factory=int)
     types: Tuple[Type, ...] = field(default_factory=tuple)
-    choices: Tuple["XmlVar", ...] = field(default_factory=tuple)
     namespaces: Tuple[str, ...] = field(default_factory=tuple)
+    elements: Dict[str, "XmlVar"] = field(default_factory=dict)
+    wildcards: List["XmlVar"] = field(default_factory=list)
+    namespace_matches: Optional[Dict[str, bool]] = field(init=False, default=None)
 
     @property
     def lname(self) -> str:
@@ -82,31 +93,10 @@ class XmlVar:
     def is_clazz_union(self) -> bool:
         return self.dataclass and len(self.types) > 1
 
-    def matches(self, qname: str) -> bool:
-        """
-        Match the field qualified local name to the given qname.
-
-        Return True automatically if the local name is a wildcard.
-        """
-        if self.elements:
-            return self.matches_choice(qname)
-
-        if self.wildcard:
-            return self.matches_wildcard(qname)
-
-        return qname in (self.qname, "*")
-
-    def matches_choice(self, qname: str) -> bool:
-        """Return whether a choice element matches the given qualified name."""
-        return self.find_choice(qname) is not None
-
     def find_choice(self, qname: str) -> Optional["XmlVar"]:
         """Match and return a choice field by its qualified name."""
-        for choice in self.choices:
-            if choice.matches(qname):
-                return choice
-
-        return None
+        match = self.elements.get(qname)
+        return match or find_by_namespace(self.wildcards, qname)
 
     def find_value_choice(self, value: Any) -> Optional["XmlVar"]:
         """Match and return a choice field that matches the given value
@@ -128,16 +118,16 @@ class XmlVar:
     ) -> Optional["XmlVar"]:
         """Match and return a choice field that matches the given type."""
 
-        for choice in self.choices:
+        for element in self.elements.values():
 
-            if choice.any_type or tokens != choice.tokens:
+            if element.any_type or tokens != element.tokens:
                 continue
 
             if tp is NoneType:
-                if choice.nillable:
-                    return choice
-            elif self.match_type(tp, choice.types, check_subclass):
-                return choice
+                if element.nillable:
+                    return element
+            elif self.match_type(tp, element.types, check_subclass):
+                return element
 
         return None
 
@@ -149,9 +139,20 @@ class XmlVar:
 
         return False
 
-    def matches_wildcard(self, qname: str) -> bool:
+    def match_namespace(self, qname: str) -> bool:
         """Match the given qname to the wildcard allowed namespaces."""
 
+        if self.namespace_matches is None:
+            self.namespace_matches = {}
+
+        matches = self.namespace_matches.get(qname)
+        if matches is None:
+            matches = self._match_namespace(qname)
+            self.namespace_matches[qname] = matches
+
+        return matches
+
+    def _match_namespace(self, qname: str) -> bool:
         if qname == "*":
             return True
 
@@ -159,43 +160,19 @@ class XmlVar:
         if not self.namespaces and namespace is None:
             return True
 
-        return any(self.match_namespace(ns, namespace) for ns in self.namespaces)
-
-    @staticmethod
-    def match_namespace(source: Optional[str], cmp: Optional[str]) -> bool:
-        if not source and cmp is None:
-            return True
-        if source == cmp:
-            return True
-        if source == NamespaceType.ANY_NS:
-            return True
-        if source and source[0] == "!" and source[1:] != cmp:
-            return True
+        for check in self.namespaces:
+            if (
+                (not check and namespace is None)
+                or check == namespace
+                or check == NamespaceType.ANY_NS
+                or (check and check[0] == "!" and check[1:] != namespace)
+            ):
+                return True
 
         return False
 
 
-class FindMode:
-    """Find switches to be used to find a specific var."""
-
-    ALL = 0
-    ATTRIBUTE = 1
-    ATTRIBUTES = 2
-    TEXT = 3
-    WILDCARD = 4
-    MIXED_CONTENT = 5
-    ELEMENT = 6
-
-
-find_predicates = {
-    FindMode.ALL: lambda x: True,
-    FindMode.ATTRIBUTE: lambda x: x.attribute,
-    FindMode.ATTRIBUTES: lambda x: x.attributes,
-    FindMode.TEXT: lambda x: x.text,
-    FindMode.WILDCARD: lambda x: x.wildcard,
-    FindMode.MIXED_CONTENT: lambda x: x.mixed,
-    FindMode.ELEMENT: lambda x: x.element or x.elements,
-}
+get_index = operator.attrgetter("index")
 
 
 @dataclass
@@ -207,40 +184,91 @@ class XmlMeta:
     :param qname: The namespace qualified name.
     :param source_qname: The source namespace qualified name.
     :param nillable: Specifies whether an explicit empty value can be assigned.
-    :param vars: The list of field metadata
+    :param mixed_content: Has a wildcard with mixed flag enabled
+    :param text: Text var
+    :param choices: List of compound vars
+    :param elements: Mapping of qname-element vars
+    :param wildcards: List of wildcard vars
+    :param attributes: Mapping of qname-attribute vars
+    :param any_attributes: List of wildcard attributes vars
     """
 
     clazz: Type
     qname: str
     source_qname: str
     nillable: bool
-    vars: Tuple[XmlVar, ...] = field(default_factory=tuple)
-    cache: Dict = field(default_factory=dict, init=False)
+    mixed_content: bool = field(default=False)
+    text: Optional[XmlVar] = field(default=None)
+    choices: List[XmlVar] = field(default_factory=list)
+    elements: Dict[str, List[XmlVar]] = field(default_factory=dict)
+    wildcards: List[XmlVar] = field(default_factory=list)
+    attributes: Dict[str, XmlVar] = field(default_factory=dict)
+    any_attributes: List[XmlVar] = field(default_factory=list)
+
+    def __post_init__(self):
+        self.mixed_content = any(wildcard.mixed for wildcard in self.wildcards)
+
+    def get_element_vars(self) -> List[XmlVar]:
+        result = self.wildcards + self.choices
+
+        for elements in self.elements.values():
+            result.extend(elements)
+
+        if self.text:
+            result.append(self.text)
+
+        return sorted(result, key=get_index)
+
+    def get_attribute_vars(self) -> List[XmlVar]:
+        result = collections.concat(self.any_attributes, self.attributes.values())
+        return sorted(result, key=get_index)
+
+    def get_all_vars(self) -> List[XmlVar]:
+        result = self.wildcards + self.choices + self.any_attributes
+        result.extend(self.attributes.values())
+        for elements in self.elements.values():
+            result.extend(elements)
+
+        if self.text:
+            result.append(self.text)
+
+        return sorted(result, key=get_index)
 
     @property
     def namespace(self) -> Optional[str]:
         return split_qname(self.qname)[0]
 
-    def has_var(self, qname: str = "*", mode: int = FindMode.ALL) -> bool:
-        return self.find_var(qname, mode) is not None
+    def find_attribute(self, qname: str) -> Optional[XmlVar]:
+        return self.attributes.get(qname)
 
-    def find_var(self, qname: str = "*", mode: int = FindMode.ALL) -> Optional[XmlVar]:
-        """Find and cache a field by it's qualified name and the specified
-        mode."""
-        key = (qname, mode)
-        index = self.cache.get(key)
-        if index is None:
-            self.cache[key] = index = self._find_var(qname, mode)
+    def find_elements(self, qname: str) -> Sequence[XmlVar]:
+        return self.elements.get(qname, EMPTY_SEQUENCE)
 
-        return None if index < 0 else self.vars[index]
+    def find_choice(self, qname: str) -> Optional[XmlVar]:
+        for choice in self.choices:
+            match = choice.find_choice(qname)
+            if match:
+                return match
+
+        return None
+
+    def find_any_attributes(self, qname: str) -> Optional[XmlVar]:
+        return find_by_namespace(self.any_attributes, qname)
+
+    def find_wildcard(self, qname: str) -> Optional[XmlVar]:
+        return find_by_namespace(self.wildcards, qname)
 
-    def _find_var(self, qname: str, mode: int) -> int:
-        predicate = find_predicates[mode]
-        for index, var in enumerate(self.vars):
-            if predicate(var) and var.matches(qname):
-                return index
+    def find_children(self, qname: str) -> Iterator[XmlVar]:
 
-        return -1
+        yield from self.find_elements(qname)
+
+        chd = self.find_choice(qname)
+        if chd:
+            yield chd
+
+        chd = self.find_wildcard(qname)
+        if chd:
+            yield chd
 
 
 class XmlType:
@@ -252,3 +280,11 @@ class XmlType:
     WILDCARD = "Wildcard"
     ATTRIBUTE = "Attribute"
     ATTRIBUTES = "Attributes"
+
+
+def find_by_namespace(xml_vars: List[XmlVar], qname: str) -> Optional[XmlVar]:
+    for xml_var in xml_vars:
+        if xml_var.match_namespace(qname):
+            return xml_var
+
+    return None
diff --git a/xsdata/formats/dataclass/parsers/__init__.py b/xsdata/formats/dataclass/parsers/__init__.py
index f9dca8d8..5bd28954 100644
--- a/xsdata/formats/dataclass/parsers/__init__.py
+++ b/xsdata/formats/dataclass/parsers/__init__.py
@@ -1,5 +1,6 @@
 from xsdata.formats.dataclass.parsers.json import JsonParser
 from xsdata.formats.dataclass.parsers.tree import TreeParser
+from xsdata.formats.dataclass.parsers.xml import UserXmlParser
 from xsdata.formats.dataclass.parsers.xml import XmlParser
 
-__all__ = ["JsonParser", "XmlParser", "TreeParser"]
+__all__ = ["JsonParser", "XmlParser", "UserXmlParser", "TreeParser"]
diff --git a/xsdata/formats/dataclass/parsers/json.py b/xsdata/formats/dataclass/parsers/json.py
index 9e3638bd..269fb227 100644
--- a/xsdata/formats/dataclass/parsers/json.py
+++ b/xsdata/formats/dataclass/parsers/json.py
@@ -98,7 +98,7 @@ class JsonParser(AbstractParser):
     def bind_value(self, var: XmlVar, value: Any) -> Any:
         """Bind value according to the class var."""
 
-        if var.attributes:
+        if var.is_attributes:
             return dict(value)
 
         if var.is_clazz_union:
@@ -110,10 +110,10 @@ class JsonParser(AbstractParser):
         if var.clazz:
             return self.bind_dataclass(value, var.clazz)
 
-        if var.elements:
+        if var.is_elements:
             return self.bind_choice(value, var)
 
-        if var.wildcard or var.any_type:
+        if var.is_wildcard or var.any_type:
             return self.bind_wildcard(value)
 
         return self.parse_value(value, var.types, var.default, var.tokens, var.format)
@@ -121,7 +121,7 @@ class JsonParser(AbstractParser):
     def bind_dataclass(self, data: Dict, clazz: Type[T]) -> T:
         """Recursively build the given model from the input dict data."""
         params = {}
-        for var in self.context.build(clazz).vars:
+        for var in self.context.build(clazz).get_all_vars():
             value = data.get(var.lname)
 
             if value is None or not var.init:
@@ -203,7 +203,7 @@ class JsonParser(AbstractParser):
 
         # Sometimes exact type match doesn't work, eg Decimals, try all of them
         is_list = isinstance(value, list)
-        for choice in var.choices:
+        for choice in var.elements.values():
             if choice.dataclass or choice.tokens != is_list:
                 continue
 
@@ -235,10 +235,10 @@ class JsonParser(AbstractParser):
     def bind_choice_dataclass(self, value: Dict, var: XmlVar) -> Any:
         """Bind data to the best matching choice model."""
         keys = set(value.keys())
-        for choice in var.choices:
+        for choice in var.elements.values():
             if choice.clazz:
                 meta = self.context.build(choice.clazz)
-                attrs = {var.lname for var in meta.vars}
+                attrs = {var.lname for var in meta.get_all_vars()}
                 if attrs == keys:
                     return self.bind_value(choice, value)
 
diff --git a/xsdata/formats/dataclass/parsers/nodes.py b/xsdata/formats/dataclass/parsers/nodes.py
index 0483cfcf..570a354a 100644
--- a/xsdata/formats/dataclass/parsers/nodes.py
+++ b/xsdata/formats/dataclass/parsers/nodes.py
@@ -5,7 +5,6 @@ from dataclasses import field
 from dataclasses import is_dataclass
 from typing import Any
 from typing import Dict
-from typing import Iterator
 from typing import List
 from typing import Optional
 from typing import Set
@@ -17,7 +16,6 @@ from xsdata.exceptions import ParserError
 from xsdata.exceptions import XmlContextError
 from xsdata.formats.bindings import T
 from xsdata.formats.dataclass.context import XmlContext
-from xsdata.formats.dataclass.models.elements import FindMode
 from xsdata.formats.dataclass.models.elements import XmlMeta
 from xsdata.formats.dataclass.models.elements import XmlVar
 from xsdata.formats.dataclass.models.generics import AnyElement
@@ -35,8 +33,6 @@ from xsdata.utils.namespaces import target_uri
 Parsed = Tuple[Optional[str], Any]
 NoneStr = Optional[str]
 
-FIND_MODES = (FindMode.ELEMENT, FindMode.WILDCARD)
-
 
 @dataclass
 class ElementNode(XmlNode):
@@ -65,11 +61,10 @@ class ElementNode(XmlNode):
 
     def bind(self, qname: str, text: NoneStr, tail: NoneStr, objects: List) -> bool:
         params: Dict = {}
-        wild_node = False
         text_node = False
         ParserUtils.bind_attrs(params, self.meta, self.attrs, self.ns_map)
 
-        wild_var = self.meta.find_var(mode=FindMode.WILDCARD)
+        wild_var = self.meta.find_wildcard("*")
         if wild_var and wild_var.mixed:
             ParserUtils.bind_mixed_objects(params, wild_var, self.position, objects)
         else:
@@ -87,7 +82,7 @@ class ElementNode(XmlNode):
 
         objects.append((qname, obj))
 
-        if not wild_var and self.mixed and not wild_node:
+        if not wild_var and self.mixed:
             tail = ParserUtils.normalize_content(tail)
             if tail:
                 objects.append((None, tail))
@@ -95,7 +90,12 @@ class ElementNode(XmlNode):
         return True
 
     def child(self, qname: str, attrs: Dict, ns_map: Dict, position: int) -> XmlNode:
-        for unique, var in self.fetch_vars(qname):
+        for var in self.meta.find_children(qname):
+            unique = None
+
+            if var.is_element and not var.list_element:
+                unique = id(var)
+
             if not unique or unique not in self.assigned:
                 node = self.build_node(var, attrs, ns_map, position)
 
@@ -134,11 +134,11 @@ class ElementNode(XmlNode):
                 xsi_type,
             )
 
-        if not var.any_type and not var.wildcard:
+        if not var.any_type and not var.is_wildcard:
             return PrimitiveNode(var, ns_map)
 
         datatype = DataType.from_qname(xsi_type) if xsi_type else None
-        derived = var.derived or var.wildcard
+        derived = var.derived or var.is_wildcard
         if datatype:
             return StandardNode(datatype, ns_map, derived, var.nillable)
 
@@ -157,22 +157,6 @@ class ElementNode(XmlNode):
 
         return WildcardNode(var=var, attrs=attrs, ns_map=ns_map, position=position)
 
-    def fetch_vars(self, qname: str) -> Iterator[Tuple[Any, XmlVar]]:
-        for mode in FIND_MODES:
-            var = self.meta.find_var(qname, mode)
-
-            if not var:
-                continue
-
-            if var.elements:
-                unique = None
-                choice = var.find_choice(qname)
-                assert choice is not None
-                yield unique, choice
-            else:
-                unique = None if var.list_element or var.wildcard else id(var)
-                yield unique, var
-
     def build_element_node(
         self,
         clazz: Type,
@@ -197,7 +181,7 @@ class ElementNode(XmlNode):
             position=position,
             derived=derived,
             substituted=xsi_type is not None,
-            mixed=self.meta.has_var(mode=FindMode.MIXED_CONTENT),
+            mixed=self.meta.mixed_content,
         )
 
 
@@ -229,7 +213,7 @@ class WildcardNode(XmlNode):
         text = "" if text is None and not self.var.nillable else text
         tail = ParserUtils.normalize_content(tail)
 
-        if tail or attributes or children or self.var.wildcard or derived:
+        if tail or attributes or children or self.var.is_wildcard or derived:
             obj = AnyElement(
                 qname=qname,
                 text=text,
diff --git a/xsdata/formats/dataclass/parsers/tree.py b/xsdata/formats/dataclass/parsers/tree.py
index 135ca155..61c587e6 100644
--- a/xsdata/formats/dataclass/parsers/tree.py
+++ b/xsdata/formats/dataclass/parsers/tree.py
@@ -38,7 +38,7 @@ class TreeParser(NodeParser):
             child = item.child(qname, attrs, ns_map, len(objects))
         except IndexError:
             child = WildcardNode(
-                var=XmlVar(name=qname, qname=qname, wildcard=True),
+                var=XmlVar(name=qname, qname=qname, is_wildcard=True),
                 attrs=attrs,
                 ns_map=ns_map,
                 position=0,
diff --git a/xsdata/formats/dataclass/parsers/utils.py b/xsdata/formats/dataclass/parsers/utils.py
index de55779c..2b52fdd1 100644
--- a/xsdata/formats/dataclass/parsers/utils.py
+++ b/xsdata/formats/dataclass/parsers/utils.py
@@ -9,13 +9,11 @@ from typing import Type
 
 from xsdata.formats.converter import converter
 from xsdata.formats.converter import QNameConverter
-from xsdata.formats.dataclass.models.elements import FindMode
 from xsdata.formats.dataclass.models.elements import XmlMeta
 from xsdata.formats.dataclass.models.elements import XmlVar
 from xsdata.formats.dataclass.models.generics import AnyElement
 from xsdata.formats.dataclass.models.generics import DerivedElement
 from xsdata.logger import logger
-from xsdata.models.enums import DataType
 from xsdata.models.enums import QNames
 from xsdata.utils import text
 from xsdata.utils.namespaces import build_qname
@@ -32,17 +30,6 @@ class ParserUtils:
         namespace, name = QNameConverter.resolve(xsi_type, ns_map)
         return build_qname(namespace, name)
 
-    @classmethod
-    def data_type(cls, attrs: Dict, ns_map: Dict) -> DataType:
-        """Convert the xsi:type attribute to a DataType, defaults to
-        DataType.STRING."""
-        xsi_type = cls.xsi_type(attrs, ns_map)
-        datatype = DataType.STRING
-        if xsi_type:
-            datatype = DataType.from_qname(xsi_type) or datatype
-
-        return datatype
-
     @classmethod
     def is_nillable(cls, attrs: Dict) -> bool:
         """Return whether the element attrs has xsi:nil="false"."""
@@ -83,16 +70,19 @@ class ParserUtils:
 
         while len(objects) > position:
             qname, value = objects.pop(position)
+            if not cls.bind_object(params, meta, qname, value):
+                logger.warning("Unassigned parsed object %s", qname)
 
-            arg = meta.find_var(qname, FindMode.ELEMENT)
-            if arg and cls.bind_var(params, arg, value):
-                continue
+    @classmethod
+    def bind_object(cls, params: Dict, meta: XmlMeta, qname: str, value: Any) -> bool:
+        for var in meta.find_children(qname):
+            if var.is_wildcard:
+                return cls.bind_wild_var(params, var, qname, value)
 
-            arg = meta.find_var(qname, FindMode.WILDCARD)
-            if arg and cls.bind_wild_var(params, arg, qname, value):
-                continue
+            if cls.bind_var(params, var, value):
+                return True
 
-            logger.warning("Unassigned parsed object %s", qname)
+        return False
 
     @classmethod
     def bind_mixed_objects(cls, params: Dict, var: XmlVar, pos: int, objects: List):
@@ -216,7 +206,7 @@ class ParserUtils:
         """
 
         if txt is not None:
-            var = metadata.find_var(mode=FindMode.TEXT)
+            var = metadata.text
             if var and var.init:
                 params[var.name] = cls.parse_value(
                     txt, var.types, var.default, ns_map, var.tokens, var.format
@@ -234,19 +224,35 @@ class ParserUtils:
         if not attrs:
             return
 
-        wildcard = metadata.find_var(mode=FindMode.ATTRIBUTES)
-        if wildcard:
-            params[wildcard.name] = {}
-
         for qname, value in attrs.items():
-            var = metadata.find_var(qname, FindMode.ATTRIBUTE)
+            var = metadata.find_attribute(qname)
             if var and var.name not in params:
-                if var.init:
-                    params[var.name] = cls.parse_value(
-                        value, var.types, var.default, ns_map, var.tokens, var.format
-                    )
-            elif wildcard:
-                params[wildcard.name][qname] = cls.parse_any_attribute(value, ns_map)
+                cls.bind_attr(params, var, value, ns_map)
+            else:
+                var = metadata.find_any_attributes(qname)
+                if var:
+                    cls.bind_any_attr(params, var, qname, value, ns_map)
+
+    @classmethod
+    def bind_attr(cls, params: Dict, var: XmlVar, value: Any, ns_map: Dict):
+        if var.init:
+            params[var.name] = cls.parse_value(
+                value,
+                var.types,
+                var.default,
+                ns_map,
+                var.tokens,
+                var.format,
+            )
+
+    @classmethod
+    def bind_any_attr(
+        cls, params: Dict, var: XmlVar, qname: str, value: Any, ns_map: Dict
+    ):
+        if var.name not in params:
+            params[var.name] = {}
+
+        params[var.name][qname] = cls.parse_any_attribute(value, ns_map)
 
     @classmethod
     def prepare_generic_value(cls, qname: str, value: Any) -> Any:
diff --git a/xsdata/formats/dataclass/parsers/xml.py b/xsdata/formats/dataclass/parsers/xml.py
index e65cedd4..cf0f0c55 100644
--- a/xsdata/formats/dataclass/parsers/xml.py
+++ b/xsdata/formats/dataclass/parsers/xml.py
@@ -19,7 +19,22 @@ from xsdata.utils.text import snake_case
 @dataclass
 class XmlParser(NodeParser):
     """
-    Xml parser for dataclasses.
+    Default Xml parser for dataclasses.
+
+    :param config: Parser configuration
+    :param context: Model context provider
+    :param handler: Override default XmlHandler
+    :ivar ms_map: The prefix-URI map generated during parsing
+    """
+
+    handler: Type[XmlHandler] = field(default=default_handler())
+
+
+@dataclass
+class UserXmlParser(NodeParser):
+    """
+    User Xml parser for dataclasses with hooks for emitting events to alter the
+    behavior when an elements starts or ends.
 
     :param config: Parser configuration
     :param context: Model context provider
@@ -40,20 +55,6 @@ class XmlParser(NodeParser):
         attrs: Dict,
         ns_map: Dict,
     ):
-        """
-        Start element notification receiver.
-
-        Build and queue the XmlNode for the starting element and
-        emits and propagate the event to subclasses.
-
-        :param clazz: Root class type, if it's missing look for any
-            suitable models from the current context.
-        :param queue: The active XmlNode queue
-        :param objects: The list of all intermediate parsed objects
-        :param qname: Qualified name
-        :param attrs: Attribute key-value map
-        :param ns_map: Namespace prefix-URI map
-        """
         super().start(clazz, queue, objects, qname, attrs, ns_map)
         self.emit_event(EventType.START, qname, attrs=attrs)
 
@@ -65,19 +66,6 @@ class XmlParser(NodeParser):
         text: Optional[str],
         tail: Optional[str],
     ) -> Any:
-        """
-        End element notification receiver.
-
-        Pop the last XmlNode from the queue and use it to build and
-        return the resulting object tree with its text and tail
-        content. Propagate the event to subclasses.
-
-        :param queue: Xml nodes queue
-        :param objects: List of parsed objects
-        :param qname: Qualified name
-        :param text: Text content
-        :param tail: Tail content
-        """
         obj = super().end(queue, objects, qname, text, tail)
         if obj:
             self.emit_event(EventType.END, qname, obj=obj)
diff --git a/xsdata/formats/dataclass/serializers/json.py b/xsdata/formats/dataclass/serializers/json.py
index e229c408..0e53176e 100644
--- a/xsdata/formats/dataclass/serializers/json.py
+++ b/xsdata/formats/dataclass/serializers/json.py
@@ -67,7 +67,7 @@ class JsonSerializer(AbstractSerializer):
             return self.dict_factory(
                 [
                     (var.lname, self.convert(getattr(obj, var.name), var))
-                    for var in self.context.build(obj.__class__).vars
+                    for var in self.context.build(obj.__class__).get_all_vars()
                 ]
             )
 
diff --git a/xsdata/formats/dataclass/serializers/xml.py b/xsdata/formats/dataclass/serializers/xml.py
index 87b0aed9..2929852a 100644
--- a/xsdata/formats/dataclass/serializers/xml.py
+++ b/xsdata/formats/dataclass/serializers/xml.py
@@ -120,7 +120,7 @@ class XmlSerializer(AbstractSerializer):
         """Produce an events stream from a dataclass for the given var with
         with xsi abstract type check for non wildcards."""
 
-        if var.wildcard:
+        if var.is_wildcard:
             yield from self.write_dataclass(value, namespace)
         else:
             xsi_type = self.xsi_type(var, value, namespace)
@@ -138,11 +138,11 @@ class XmlSerializer(AbstractSerializer):
         """
         if var.mixed:
             yield from self.write_mixed_content(value, var, namespace)
-        elif var.text:
+        elif var.is_text:
             yield from self.write_data(value, var, namespace)
         elif var.tokens:
             yield from self.write_tokens(value, var, namespace)
-        elif var.elements:
+        elif var.is_elements:
             yield from self.write_elements(value, var, namespace)
         elif var.list_element and isinstance(value, list):
             yield from self.write_list(value, var, namespace)
@@ -185,7 +185,7 @@ class XmlSerializer(AbstractSerializer):
             yield from self.write_derived_element(value, var, namespace)
         elif is_dataclass(value):
             yield from self.write_xsi_type(value, var, namespace)
-        elif var.element:
+        elif var.is_element:
             yield from self.write_element(value, var, namespace)
         else:
             yield from self.write_data(value, var, namespace)
@@ -312,13 +312,10 @@ class XmlSerializer(AbstractSerializer):
         eg: <a1/><a2/><a1/><a/2></a1>
         """
         index = 0
-        attrs = meta.vars
+        attrs = meta.get_element_vars()
         stop = len(attrs)
         while index < stop:
             var = attrs[index]
-            if var.attribute or var.attributes:
-                index += 1
-                continue
 
             if not var.sequential:
                 yield var, getattr(obj, var.name)
@@ -351,14 +348,14 @@ class XmlSerializer(AbstractSerializer):
         Ignores None values and empty lists. Optionally include the xsi
         properties type and nil.
         """
-        for var in meta.vars:
-            if var.attribute:
+        for var in meta.get_attribute_vars():
+            if var.is_attribute:
                 value = getattr(obj, var.name)
                 if value is None or isinstance(value, list) and not value:
                     continue
 
                 yield var.qname, cls.encode_value(value, var)
-            elif var.attributes:
+            else:
                 yield from getattr(obj, var.name, EMPTY_MAP).items()
 
         if xsi_type:


Test patch:
diff --git a/tests/formats/dataclass/models/test_builders.py b/tests/formats/dataclass/models/test_builders.py
index a40d9e63..dfb29587 100644
--- a/tests/formats/dataclass/models/test_builders.py
+++ b/tests/formats/dataclass/models/test_builders.py
@@ -33,7 +33,7 @@ from xsdata.utils.namespaces import build_qname
 class XmlMetaBuilderTests(TestCase):
     @mock.patch.object(XmlMetaBuilder, "build_vars")
     def test_build(self, mock_build_vars):
-        var = XmlVar(element=True, name="foo", qname="{foo}bar", types=(int,))
+        var = XmlVar(is_element=True, name="foo", qname="{foo}bar", types=(int,))
         mock_build_vars.return_value = [var]
 
         result = XmlMetaBuilder.build(ItemsType, None, return_input, return_input)
@@ -42,7 +42,7 @@ class XmlMetaBuilderTests(TestCase):
             qname="ItemsType",
             source_qname="ItemsType",
             nillable=False,
-            vars=(var,),
+            elements={var.qname: [var]},
         )
 
         self.assertEqual(expected, result)
@@ -110,15 +110,52 @@ class XmlMetaBuilderTests(TestCase):
         self.assertIsInstance(result, Iterator)
 
         expected = [
-            XmlVar(element=True, name="author", qname="Author", types=(str,)),
-            XmlVar(element=True, name="title", qname="Title", types=(str,)),
-            XmlVar(element=True, name="genre", qname="Genre", types=(str,)),
-            XmlVar(element=True, name="price", qname="Price", types=(float,)),
-            XmlVar(element=True, name="pub_date", qname="PubDate", types=(XmlDate,)),
-            XmlVar(element=True, name="review", qname="Review", types=(str,)),
-            XmlVar(attribute=True, name="id", qname="ID", types=(str,)),
             XmlVar(
-                attribute=True,
+                is_element=True,
+                index=0,
+                name="author",
+                qname="Author",
+                types=(str,),
+            ),
+            XmlVar(
+                is_element=True,
+                index=1,
+                name="title",
+                qname="Title",
+                types=(str,),
+            ),
+            XmlVar(
+                is_element=True,
+                index=2,
+                name="genre",
+                qname="Genre",
+                types=(str,),
+            ),
+            XmlVar(
+                is_element=True,
+                index=3,
+                name="price",
+                qname="Price",
+                types=(float,),
+            ),
+            XmlVar(
+                is_element=True,
+                index=4,
+                name="pub_date",
+                qname="PubDate",
+                types=(XmlDate,),
+            ),
+            XmlVar(
+                is_element=True,
+                index=5,
+                name="review",
+                qname="Review",
+                types=(str,),
+            ),
+            XmlVar(is_attribute=True, index=6, name="id", qname="ID", types=(str,)),
+            XmlVar(
+                is_attribute=True,
+                index=7,
                 name="lang",
                 qname="LANG",
                 types=(str,),
@@ -184,6 +221,7 @@ class XmlVarBuilderTests(TestCase):
         builder = XmlVarBuilder(XmlType.ELEMENT, "bar", return_input, return_input)
 
         actual = builder.build(
+            66,
             "compound",
             type_hints["compound"],
             class_field.metadata,
@@ -192,79 +230,96 @@ class XmlVarBuilderTests(TestCase):
             globalns,
         )
         expected = XmlVar(
-            elements=True,
+            index=66,
+            is_elements=True,
             name="compound",
             qname="compound",
             list_element=True,
             any_type=True,
             default=list,
-            choices=(
-                XmlVar(
-                    element=True,
+            elements={
+                "{foo}node": XmlVar(
+                    index=0,
+                    is_element=True,
                     name="compound",
                     qname="{foo}node",
                     dataclass=True,
+                    list_element=True,
                     types=(CompoundFieldExample,),
                     namespaces=("foo",),
                     derived=False,
                 ),
-                XmlVar(
-                    element=True,
+                "{bar}x": XmlVar(
+                    index=1,
+                    is_element=True,
                     name="compound",
                     qname="{bar}x",
                     tokens=True,
+                    list_element=True,
                     types=(str,),
                     namespaces=("bar",),
                     derived=False,
                     default=return_true,
                     format="Nope",
                 ),
-                XmlVar(
-                    element=True,
+                "{bar}y": XmlVar(
+                    index=2,
+                    is_element=True,
                     name="compound",
                     qname="{bar}y",
                     nillable=True,
+                    list_element=True,
                     types=(int,),
                     namespaces=("bar",),
                     derived=False,
                 ),
-                XmlVar(
-                    element=True,
+                "{bar}z": XmlVar(
+                    index=3,
+                    is_element=True,
                     name="compound",
                     qname="{bar}z",
                     nillable=False,
+                    list_element=True,
                     types=(int,),
                     namespaces=("bar",),
                     derived=True,
                 ),
-                XmlVar(
-                    element=True,
+                "{bar}o": XmlVar(
+                    index=4,
+                    is_element=True,
                     name="compound",
                     qname="{bar}o",
                     nillable=False,
+                    list_element=True,
                     types=(object,),
                     namespaces=("bar",),
                     derived=True,
                     any_type=True,
                 ),
-                XmlVar(
-                    element=True,
+                "{bar}p": XmlVar(
+                    index=5,
+                    is_element=True,
                     name="compound",
                     qname="{bar}p",
                     types=(float,),
+                    list_element=True,
                     namespaces=("bar",),
                     default=1.1,
                 ),
+            },
+            wildcards=[
                 XmlVar(
-                    wildcard=True,
+                    index=6,
+                    is_wildcard=True,
                     name="compound",
                     qname="{http://www.w3.org/1999/xhtml}any",
                     types=(object,),
                     namespaces=("http://www.w3.org/1999/xhtml",),
                     derived=True,
                     any_type=False,
-                ),
-            ),
+                    list_element=True,
+                )
+            ],
             types=(object,),
         )
         self.assertEqual(expected, actual)
@@ -272,7 +327,7 @@ class XmlVarBuilderTests(TestCase):
     def test_build_validates_result(self):
         with self.assertRaises(XmlContextError) as cm:
             self.builder.build(
-                "foo", List[int], {"type": "Attributes"}, True, None, None
+                1, "foo", List[int], {"type": "Attributes"}, True, None, None
             )
 
         self.assertEqual(
diff --git a/tests/formats/dataclass/parsers/test_json.py b/tests/formats/dataclass/parsers/test_json.py
index c11860d3..324c1939 100644
--- a/tests/formats/dataclass/parsers/test_json.py
+++ b/tests/formats/dataclass/parsers/test_json.py
@@ -126,7 +126,7 @@ class JsonParserTests(TestCase):
         self.assertEqual("Key `book` value is not iterable", str(cm.exception))
 
     def test_bind_value_with_attributes_var(self):
-        var = XmlVar(attributes=True, name="a", qname="a")
+        var = XmlVar(is_attributes=True, name="a", qname="a")
         value = {"a": 1}
         actual = self.parser.bind_value(var, value)
         self.assertEqual(value, actual)
@@ -138,7 +138,7 @@ class JsonParserTests(TestCase):
         c = make_dataclass("c", [("x", int), ("y", str), ("z", str)])
         d = make_dataclass("d", [("x", int)])
         var = XmlVar(
-            element=True,
+            is_element=True,
             name="union",
             qname="union",
             types=(a, b, c, int),
@@ -153,7 +153,7 @@ class JsonParserTests(TestCase):
     def test_bind_type_union(self):
         a = make_dataclass("a", [("x", int), ("y", str)])
         var = XmlVar(
-            element=True,
+            is_element=True,
             name="union",
             qname="union",
             types=(a, int, float),
@@ -165,22 +165,28 @@ class JsonParserTests(TestCase):
 
     def test_bind_choice_simple(self):
         var = XmlVar(
-            elements=True,
+            is_elements=True,
             qname="compound",
             name="compound",
-            choices=[
-                XmlVar(element=True, qname="int", name="int", types=(int,)),
-                XmlVar(
-                    element=True,
+            elements={
+                "int": XmlVar(is_element=True, qname="int", name="int", types=(int,)),
+                "tokens": XmlVar(
+                    is_element=True,
                     qname="tokens",
                     name="tokens",
                     types=(int,),
                     tokens=True,
                 ),
-                XmlVar(element=True, qname="generic", name="generic", dataclass=True),
-                XmlVar(element=True, qname="float", name="float", types=(float,)),
-                XmlVar(element=True, qname="qname", name="qname", types=(QName,)),
-            ],
+                "generic": XmlVar(
+                    is_element=True, qname="generic", name="generic", dataclass=True
+                ),
+                "float": XmlVar(
+                    is_element=True, qname="float", name="float", types=(float,)
+                ),
+                "qname": XmlVar(
+                    is_element=True, qname="qname", name="qname", types=(QName,)
+                ),
+            },
         )
         self.assertEqual(1.0, self.parser.bind_choice("1.0", var))
         self.assertEqual(1, self.parser.bind_choice(1, var))
@@ -203,14 +209,18 @@ class JsonParserTests(TestCase):
         b = make_dataclass("b", [("x", int), ("y", str)])
 
         var = XmlVar(
-            elements=True,
+            is_elements=True,
             qname="compound",
             name="compound",
-            choices=[
-                XmlVar(element=True, qname="c", name="c", types=(int,)),
-                XmlVar(element=True, qname="a", name="a", types=(a,), dataclass=True),
-                XmlVar(element=True, qname="b", name="b", types=(b,), dataclass=True),
-            ],
+            elements={
+                "a": XmlVar(
+                    is_element=True, qname="a", name="a", types=(a,), dataclass=True
+                ),
+                "b": XmlVar(
+                    is_element=True, qname="b", name="b", types=(b,), dataclass=True
+                ),
+                "c": XmlVar(is_element=True, qname="c", name="c", types=(int,)),
+            },
         )
 
         self.assertEqual(a(1), self.parser.bind_choice({"x": 1}, var))
@@ -226,13 +236,13 @@ class JsonParserTests(TestCase):
 
     def test_bind_choice_generic_with_derived(self):
         var = XmlVar(
-            elements=True,
+            is_elements=True,
             qname="compound",
             name="compound",
-            choices=[
-                XmlVar(element=True, name="a", qname="a", types=(int,)),
-                XmlVar(element=True, name="b", qname="b", types=(float,)),
-            ],
+            elements={
+                "a": XmlVar(is_element=True, name="a", qname="a", types=(int,)),
+                "b": XmlVar(is_element=True, name="b", qname="b", types=(float,)),
+            },
         )
         data = {"qname": "a", "value": 1, "substituted": True}
 
@@ -243,13 +253,13 @@ class JsonParserTests(TestCase):
 
     def test_bind_choice_generic_with_wildcard(self):
         var = XmlVar(
-            elements=True,
+            is_elements=True,
             qname="compound",
             name="compound",
-            choices=[
-                XmlVar(element=True, name="a", qname="a", types=(int,)),
-                XmlVar(element=True, name="b", qname="b", types=(float,)),
-            ],
+            elements={
+                "a": XmlVar(is_element=True, name="a", qname="a", types=(int,)),
+                "b": XmlVar(is_element=True, name="b", qname="b", types=(float,)),
+            },
         )
 
         self.assertEqual(
@@ -258,7 +268,7 @@ class JsonParserTests(TestCase):
         )
 
     def test_bind_choice_generic_with_unknown_qname(self):
-        var = XmlVar(elements=True, qname="compound", name="compound")
+        var = XmlVar(is_elements=True, qname="compound", name="compound")
 
         with self.assertRaises(ParserError) as cm:
             self.parser.bind_choice({"qname": "foo", "text": 1}, var)
@@ -270,7 +280,7 @@ class JsonParserTests(TestCase):
 
     def test_bind_wildcard_with_any_element(self):
         var = XmlVar(
-            wildcard=True,
+            is_wildcard=True,
             name="any_element",
             qname="any_element",
             types=(object,),
diff --git a/tests/formats/dataclass/parsers/test_nodes.py b/tests/formats/dataclass/parsers/test_nodes.py
index ed32aff7..7ff056c6 100644
--- a/tests/formats/dataclass/parsers/test_nodes.py
+++ b/tests/formats/dataclass/parsers/test_nodes.py
@@ -3,7 +3,6 @@ from dataclasses import field
 from dataclasses import make_dataclass
 from dataclasses import replace
 from typing import Any
-from typing import Generator
 from typing import List
 from typing import Union
 from unittest import mock
@@ -146,7 +145,7 @@ class ElementNodeTests(TestCase):
         self.assertEqual("foo", objects[-1][0])
         self.assertEqual(DerivedElement("foo", a()), objects[-1][1])
 
-    @mock.patch.object(XmlMeta, "find_var")
+    @mock.patch.object(XmlMeta, "find_wildcard")
     @mock.patch.object(ParserUtils, "bind_objects")
     @mock.patch.object(ParserUtils, "bind_wild_content")
     @mock.patch.object(ParserUtils, "bind_content")
@@ -157,13 +156,13 @@ class ElementNodeTests(TestCase):
         mock_bind_content,
         mock_bind_wild_content,
         mock_bind_objects,
-        mock_find_var,
+        find_wildcard,
     ):
         mock_bind_attrs.side_effect = add_attr
         mock_bind_content.return_value = False
         mock_bind_wild_content.side_effect = add_text
         mock_bind_objects.side_effect = add_child
-        mock_find_var.return_value = XmlVar(wildcard=True, qname="b", name="b")
+        find_wildcard.return_value = XmlVar(is_wildcard=True, qname="b", name="b")
 
         node = ElementNode(
             position=0,
@@ -239,6 +238,7 @@ class ElementNodeTests(TestCase):
             attrs={"a": "b"},
             ns_map={"ns0": "xsdata"},
         )
+        wildcard = node.meta.find_wildcard("*")
         objects = [1, 2, 3]
 
         self.assertTrue(node.bind("foo", "text", "tail", objects))
@@ -249,38 +249,18 @@ class ElementNodeTests(TestCase):
         mock_bind_attrs.assert_called_once_with(
             mock.ANY, node.meta, node.attrs, node.ns_map
         )
+
         mock_bind_wild_content.assert_called_once_with(
-            mock.ANY, node.meta.vars[2], "text", "tail", node.attrs, node.ns_map
-        )
-        mock_bind_mixed_objects.assert_called_once_with(
-            mock.ANY, node.meta.vars[2], 0, objects
+            mock.ANY, wildcard, "text", "tail", node.attrs, node.ns_map
         )
+        mock_bind_mixed_objects.assert_called_once_with(mock.ANY, wildcard, 0, objects)
 
-    def test_fetch_vars(self):
-        elem = XmlVar(element=True, name="a", qname="a", types=(Foo,), dataclass=True)
-        wild = XmlVar(wildcard=True, name="a", qname="a", types=(Foo,), dataclass=True)
-        self.meta.vars = self.meta.vars + (wild, elem)
-
-        matching_vars = self.node.fetch_vars("a")
-        self.assertIsInstance(matching_vars, Generator)
-        self.assertEqual([(id(elem), elem), (None, wild)], list(matching_vars))
-
-    def test_fetch_vars_with_elements_var(self):
-        elem = XmlVar(element=True, name="a", qname="a", types=(Foo,), dataclass=True)
-        elems = XmlVar(elements=True, name="compound", qname="compound", choices=[elem])
-        self.meta.vars = self.meta.vars + (elems,)
-
-        matching_vars = self.node.fetch_vars("a")
-        self.assertIsInstance(matching_vars, Generator)
-        self.assertEqual((None, elem), next(matching_vars))
-
-    @mock.patch.object(ElementNode, "fetch_vars")
-    def test_child(self, mock_match_vars):
-        var = XmlVar(element=True, name="a", qname="a", types=(Foo,), dataclass=True)
+    def test_child(self):
+        var = XmlVar(is_element=True, name="a", qname="a", types=(Foo,), dataclass=True)
         attrs = {"a": "b"}
         ns_map = {"ns0": "xsdata"}
         position = 1
-        mock_match_vars.return_value = [(id(var), var)]
+        self.meta.elements[var.qname] = [var]
 
         actual = self.node.child("a", attrs, ns_map, position)
         self.assertIsInstance(actual, ElementNode)
@@ -288,10 +268,13 @@ class ElementNodeTests(TestCase):
         self.assertEqual(ns_map, actual.ns_map)
         self.assertEqual(position, actual.position)
 
-    def test_child_unique_vars(self):
-        single = XmlVar(element=True, name="a", qname="a", types=(Foo,), dataclass=True)
-        wildcard = XmlVar(wildcard=True, name="a", qname="a", types=(object,))
-        self.meta.vars = self.meta.vars + (single, wildcard)
+    def test_child_with_unique_element(self):
+        single = XmlVar(
+            is_element=True, name="a", qname="a", types=(Foo,), dataclass=True
+        )
+        wildcard = XmlVar(is_wildcard=True, name="a", qname="a", types=(object,))
+        self.meta.elements[single.qname] = [single]
+        self.meta.wildcards.append(wildcard)
 
         attrs = {"a": "b"}
         ns_map = {"ns0": "xsdata"}
@@ -308,10 +291,11 @@ class ElementNodeTests(TestCase):
     @mock.patch.object(ElementNode, "build_node")
     def test_child_when_failed_to_build_next_node(self, mock_build_node):
         mock_build_node.return_value = None
-        self.meta.vars = self.meta.vars + (
-            XmlVar(element=True, name="a", qname="a"),
-            XmlVar(wildcard=True, name="a", qname="a"),
-        )
+        element = XmlVar(is_element=True, name="a", qname="a")
+        wildcard = XmlVar(is_wildcard=True, name="a", qname="a")
+
+        self.meta.elements[element.qname] = [element]
+        self.meta.wildcards.append(wildcard)
 
         with self.assertRaises(ParserError) as cm:
             self.node.child("a", {}, {}, 0)
@@ -325,7 +309,11 @@ class ElementNodeTests(TestCase):
 
     def test_build_node_with_dataclass_union_var(self):
         var = XmlVar(
-            element=True, name="a", qname="a", types=(Foo, FooMixed), dataclass=True
+            is_element=True,
+            name="a",
+            qname="a",
+            types=(Foo, FooMixed),
+            dataclass=True,
         )
         attrs = {"a": "b"}
         ns_map = {"ns0": "xsdata"}
@@ -343,7 +331,7 @@ class ElementNodeTests(TestCase):
     @mock.patch.object(XmlContext, "fetch")
     def test_build_node_with_dataclass_var(self, mock_ctx_fetch, mock_xsi_type):
         var = XmlVar(
-            element=True,
+            is_element=True,
             name="a",
             qname="a",
             types=(Foo,),
@@ -369,7 +357,7 @@ class ElementNodeTests(TestCase):
 
     @mock.patch.object(XmlContext, "fetch")
     def test_build_node_with_dataclass_var_validates_nillable(self, mock_ctx_fetch):
-        var = XmlVar(element=True, name="a", qname="a", types=(Foo,), dataclass=True)
+        var = XmlVar(is_element=True, name="a", qname="a", types=(Foo,), dataclass=True)
         ns_map = {}
         nillable_meta = replace(self.meta, nillable=True)
         mock_ctx_fetch.side_effect = [self.meta, self.meta, nillable_meta]
@@ -384,7 +372,13 @@ class ElementNodeTests(TestCase):
         self.assertIsNone(self.node.build_node(var, attrs, ns_map, 10))
 
     def test_build_node_with_any_type_var_with_matching_xsi_type(self):
-        var = XmlVar(element=True, name="a", qname="a", types=(object,), any_type=True)
+        var = XmlVar(
+            is_element=True,
+            name="a",
+            qname="a",
+            types=(object,),
+            any_type=True,
+        )
         attrs = {QNames.XSI_TYPE: "bk:books"}
         ns_map = {"bk": "urn:books"}
         actual = self.node.build_node(var, attrs, ns_map, 10)
@@ -397,7 +391,13 @@ class ElementNodeTests(TestCase):
         self.assertFalse(actual.mixed)
 
     def test_build_node_with_any_type_var_with_datatype(self):
-        var = XmlVar(element=True, name="a", qname="a", types=(object,), any_type=True)
+        var = XmlVar(
+            is_element=True,
+            name="a",
+            qname="a",
+            types=(object,),
+            any_type=True,
+        )
         attrs = {QNames.XSI_TYPE: "xs:hexBinary"}
         ns_map = {Namespace.XS.prefix: Namespace.XS.uri}
         actual = self.node.build_node(var, attrs, ns_map, 10)
@@ -408,7 +408,13 @@ class ElementNodeTests(TestCase):
         self.assertEqual(var.derived, actual.derived)
 
     def test_build_node_with_any_type_var_with_no_matching_xsi_type(self):
-        var = XmlVar(element=True, name="a", qname="a", types=(object,), any_type=True)
+        var = XmlVar(
+            is_element=True,
+            name="a",
+            qname="a",
+            types=(object,),
+            any_type=True,
+        )
         attrs = {QNames.XSI_TYPE: "noMatch"}
         actual = self.node.build_node(var, attrs, {}, 10)
 
@@ -419,7 +425,13 @@ class ElementNodeTests(TestCase):
         self.assertEqual({}, actual.ns_map)
 
     def test_build_node_with_any_type_var_with_no_xsi_type(self):
-        var = XmlVar(element=True, name="a", qname="a", types=(object,), any_type=True)
+        var = XmlVar(
+            is_element=True,
+            name="a",
+            qname="a",
+            types=(object,),
+            any_type=True,
+        )
         attrs = {}
         actual = self.node.build_node(var, attrs, {}, 10)
 
@@ -430,7 +442,7 @@ class ElementNodeTests(TestCase):
         self.assertEqual({}, actual.ns_map)
 
     def test_build_node_with_wildcard_var(self):
-        var = XmlVar(wildcard=True, name="a", qname="a", dataclass=False)
+        var = XmlVar(is_wildcard=True, name="a", qname="a", dataclass=False)
 
         actual = self.node.build_node(var, {}, {}, 10)
 
@@ -439,7 +451,7 @@ class ElementNodeTests(TestCase):
         self.assertEqual(var, actual.var)
 
     def test_build_node_with_primitive_var(self):
-        var = XmlVar(text=True, name="a", qname="a", types=(int,), default=100)
+        var = XmlVar(is_text=True, name="a", qname="a", types=(int,), default=100)
         attrs = {"a": "b"}
         ns_map = {"ns0": "xsdata"}
         actual = self.node.build_node(var, attrs, ns_map, 10)
@@ -464,7 +476,7 @@ class WildcardNodeTests(TestCase):
             children=[1, 2, 3],
         )
 
-        var = XmlVar(text=True, name="foo", qname="a")
+        var = XmlVar(is_text=True, name="foo", qname="a")
         node = WildcardNode(position=0, var=var, attrs=attrs, ns_map=ns_map)
         objects = [("a", 1), ("b", 2), ("c", 3)]
 
@@ -490,7 +502,7 @@ class WildcardNodeTests(TestCase):
     def test_child(self):
         attrs = {"id": "1"}
         ns_map = {"ns0": "xsdata"}
-        var = XmlVar(text=True, name="foo", qname="foo")
+        var = XmlVar(is_text=True, name="foo", qname="foo")
         node = WildcardNode(position=0, var=var, attrs={}, ns_map={})
         actual = node.child("foo", attrs, ns_map, 10)
 
@@ -506,7 +518,7 @@ class UnionNodeTests(TestCase):
         attrs = {"id": "1"}
         ns_map = {"ns0": "xsdata"}
         ctx = XmlContext()
-        var = XmlVar(text=True, name="foo", qname="foo")
+        var = XmlVar(is_text=True, name="foo", qname="foo")
         node = UnionNode(position=0, var=var, context=ctx, attrs={}, ns_map={})
         self.assertEqual(node, node.child("foo", attrs, ns_map, 10))
 
@@ -516,7 +528,7 @@ class UnionNodeTests(TestCase):
 
     def test_bind_appends_end_event_when_level_not_zero(self):
         ctx = XmlContext()
-        var = XmlVar(text=True, name="foo", qname="foo")
+        var = XmlVar(is_text=True, name="foo", qname="foo")
         node = UnionNode(position=0, var=var, context=ctx, attrs={}, ns_map={})
         node.level = 1
         objects = []
@@ -543,7 +555,7 @@ class UnionNodeTests(TestCase):
 
         ctx = XmlContext()
         meta = ctx.build(Root)
-        var = meta.vars[0]
+        var = meta.find_elements("item")[0]
         attrs = {"a": "1", "b": 2}
         ns_map = {}
         node = UnionNode(position=0, var=var, context=ctx, attrs=attrs, ns_map=ns_map)
@@ -581,9 +593,9 @@ class UnionNodeTests(TestCase):
 
         ctx = XmlContext()
         meta = ctx.build(Root)
-        meta.vars[0]
+        var = meta.find_elements("item")[0]
 
-        node = UnionNode(position=0, var=meta.vars[0], context=ctx, attrs={}, ns_map={})
+        node = UnionNode(position=0, var=var, context=ctx, attrs={}, ns_map={})
 
         with self.assertRaises(ParserError) as cm:
             node.bind("item", None, None, [])
@@ -595,7 +607,7 @@ class PrimitiveNodeTests(TestCase):
     @mock.patch.object(ParserUtils, "parse_value")
     def test_bind(self, mock_parse_value):
         mock_parse_value.return_value = 13
-        var = XmlVar(text=True, name="foo", qname="foo", types=(int,), format="Nope")
+        var = XmlVar(is_text=True, name="foo", qname="foo", types=(int,), format="Nope")
         ns_map = {"foo": "bar"}
         node = PrimitiveNode(var, ns_map)
         objects = []
@@ -608,7 +620,7 @@ class PrimitiveNodeTests(TestCase):
         )
 
     def test_bind_derived_mode(self):
-        var = XmlVar(text=True, name="foo", qname="foo", types=(int,), derived=True)
+        var = XmlVar(is_text=True, name="foo", qname="foo", types=(int,), derived=True)
         ns_map = {"foo": "bar"}
         node = PrimitiveNode(var, ns_map)
         objects = []
@@ -617,7 +629,9 @@ class PrimitiveNodeTests(TestCase):
         self.assertEqual(DerivedElement("foo", 13), objects[-1][1])
 
     def test_bind_nillable_content(self):
-        var = XmlVar(text=True, name="foo", qname="foo", types=(str,), nillable=False)
+        var = XmlVar(
+            is_text=True, name="foo", qname="foo", types=(str,), nillable=False
+        )
         ns_map = {"foo": "bar"}
         node = PrimitiveNode(var, ns_map)
         objects = []
@@ -630,7 +644,7 @@ class PrimitiveNodeTests(TestCase):
         self.assertIsNone(objects[-1][1])
 
     def test_child(self):
-        var = XmlVar(text=True, name="foo", qname="foo")
+        var = XmlVar(is_text=True, name="foo", qname="foo")
         node = PrimitiveNode(var, {})
 
         with self.assertRaises(XmlContextError):
@@ -815,7 +829,7 @@ class NodeParserTests(TestCase):
         parser = NodeParser()
         objects = [("q", "result")]
         queue = []
-        var = XmlVar(text=True, name="foo", qname="foo")
+        var = XmlVar(is_text=True, name="foo", qname="foo")
         queue.append(PrimitiveNode(var, ns_map={}))
 
         result = parser.end(queue, objects, "author", "foobar", None)
diff --git a/tests/formats/dataclass/parsers/test_utils.py b/tests/formats/dataclass/parsers/test_utils.py
index 7d2ccd8a..670460ee 100644
--- a/tests/formats/dataclass/parsers/test_utils.py
+++ b/tests/formats/dataclass/parsers/test_utils.py
@@ -1,4 +1,3 @@
-from dataclasses import dataclass
 from dataclasses import field
 from dataclasses import make_dataclass
 from dataclasses import replace
@@ -11,14 +10,12 @@ from tests.fixtures.defxmlschema.chapter12 import ProductType
 from tests.fixtures.defxmlschema.chapter12 import SizeType
 from xsdata.formats.converter import ConverterFactory
 from xsdata.formats.dataclass.context import XmlContext
-from xsdata.formats.dataclass.models.elements import FindMode
 from xsdata.formats.dataclass.models.elements import XmlMeta
 from xsdata.formats.dataclass.models.elements import XmlType
 from xsdata.formats.dataclass.models.elements import XmlVar
 from xsdata.formats.dataclass.models.generics import AnyElement
 from xsdata.formats.dataclass.models.generics import DerivedElement
 from xsdata.formats.dataclass.parsers.utils import ParserUtils
-from xsdata.models.enums import DataType
 from xsdata.models.enums import Namespace
 from xsdata.models.enums import QNames
 
@@ -38,18 +35,6 @@ class ParserUtilsTests(TestCase):
         attrs = {QNames.XSI_TYPE: "bar:foo"}
         self.assertEqual("{xsdata}foo", ParserUtils.xsi_type(attrs, ns_map))
 
-    def test_data_type(self):
-        ns_map = {"bar": "xsdata"}
-        attrs = {}
-        self.assertEqual(DataType.STRING, ParserUtils.data_type(attrs, ns_map))
-
-        ns_map = {"xs": Namespace.XS.uri}
-        attrs = {QNames.XSI_TYPE: "xs:foo"}
-        self.assertEqual(DataType.STRING, ParserUtils.data_type(attrs, ns_map))
-
-        attrs = {QNames.XSI_TYPE: "xs:float"}
-        self.assertEqual(DataType.FLOAT, ParserUtils.data_type(attrs, ns_map))
-
     @mock.patch.object(ConverterFactory, "deserialize", return_value=2)
     def test_parse_value(self, mock_deserialize):
         self.assertEqual(1, ParserUtils.parse_value(None, [int], 1))
@@ -92,50 +77,62 @@ class ParserUtilsTests(TestCase):
             " 1 2 3", [str], ns_map=None, format="Nope"
         )
 
-    def test_bind_objects(self):
-        @dataclass
-        class A:
-            x: int
-            y: int = field(init=False)
-            w: object = field(metadata=dict(type=XmlType.WILDCARD))
+    @mock.patch("xsdata.formats.dataclass.parsers.utils.logger.warning")
+    def test_bind_objects(self, mock_warning):
+        x = make_dataclass("x", [("a", int), ("c", float)])
+        objects = [("a", 1), ("b", 2), ("c", 3.0)]
 
+        params = {}
         ctx = XmlContext()
-        meta = ctx.build(A)
-        x = meta.find_var("x")
-        y = meta.find_var("y")
-        w = meta.find_var("wild")
-        wild_element = AnyElement(qname="foo")
+        meta = ctx.build(x)
 
-        objects = [
-            ("foo", 0),
-            (x.qname, 1),
-            (y.qname, 2),
-            (w.qname, None),
-            (w.qname, wild_element),
-        ]
+        ParserUtils.bind_objects(params, meta, 0, objects)
+        self.assertEqual({"a": 1, "c": 3.0}, params)
+
+        mock_warning.assert_called_once_with("Unassigned parsed object %s", "b")
 
+    def test_bind_object_with_matching_wildcard(self):
+        x = make_dataclass(
+            "x",
+            [("a", int), ("any", object, field(metadata={"type": XmlType.WILDCARD}))],
+        )
+        ctx = XmlContext()
+        meta = ctx.build(x)
         params = {}
-        ParserUtils.bind_objects(params, meta, 1, objects)
-        expected = {
-            "x": 1,
-            "w": AnyElement(
-                children=[AnyElement(qname="w", text=None), AnyElement(qname="foo")]
-            ),
-        }
 
-        self.assertEqual(expected, params)
+        result = ParserUtils.bind_object(params, meta, "whatever", "foo")
 
-    @mock.patch("xsdata.formats.dataclass.parsers.utils.logger.warning")
-    def test_bind_objects_with_unassigned_object(self, mock_warning):
-        a = make_dataclass("a", [("x", int), ("z", float)])
-        meta = XmlContext().build(a)
+        self.assertTrue(result)
+        self.assertEqual({"any": AnyElement(qname="whatever", text="foo")}, params)
+
+    def test_bind_object_with_matching_element(self):
+        x = make_dataclass(
+            "x", [("a", int), ("a1", int, field(metadata={"name": "a"}))]
+        )
+        ctx = XmlContext()
+        meta = ctx.build(x)
+        params = {"a": 1}
+
+        result = ParserUtils.bind_object(params, meta, "a", 2)
+
+        self.assertTrue(result)
+        self.assertEqual({"a": 1, "a1": 2}, params)
+
+    def test_bind_object_failure(self):
+        x = make_dataclass(
+            "x",
+            [
+                ("a", int),
+            ],
+        )
+        ctx = XmlContext()
+        meta = ctx.build(x)
         params = {}
-        objects = [("x", 1), ("y", 2)]
 
-        ParserUtils.bind_objects(params, meta, 0, objects)
+        result = ParserUtils.bind_object(params, meta, "b", 1)
 
-        self.assertEqual({"x": 1}, params)
-        mock_warning.assert_called_once_with("Unassigned parsed object %s", "y")
+        self.assertFalse(result)
+        self.assertEqual({}, params)
 
     def test_bind_mixed_objects(self):
         generic = AnyElement(qname="foo")
@@ -149,7 +146,7 @@ class ParserUtilsTests(TestCase):
             (None, "foo"),
         ]
 
-        var = XmlVar(wildcard=True, name="foo", qname="{any}foo")
+        var = XmlVar(is_wildcard=True, name="foo", qname="{any}foo")
         params = {}
         ParserUtils.bind_mixed_objects(params, var, 1, objects)
 
@@ -168,7 +165,7 @@ class ParserUtilsTests(TestCase):
         mock_parse_value.return_value = "2020-03-02"
         mock_parse_any_attribute.return_value = "foobar"
         metadata = self.ctx.build(ProductType)
-        eff_date = metadata.find_var("effDate")
+        eff_date = metadata.find_attribute("effDate")
 
         params = {}
         ns_map = {}
@@ -213,43 +210,49 @@ class ParserUtilsTests(TestCase):
     def test_bind_attrs_doesnt_overwrite_values(self):
         metadata = self.ctx.build(ProductType)
         params = dict(eff_date="foo")
-        attrs = {"effDate": "2020-03-01"}
+        attrs = {"effDate": "2020-03-01", "bar": "foo"}
         ns_map = {}
 
         ParserUtils.bind_attrs(params, metadata, attrs, ns_map)
 
-        expected = {"eff_date": "foo", "other_attributes": {"effDate": "2020-03-01"}}
+        expected = {
+            "eff_date": "foo",
+            "other_attributes": {"effDate": "2020-03-01", "bar": "foo"},
+        }
         self.assertEqual(expected, params)
 
     def test_bind_attrs_ignore_init_false_vars(self):
         metadata = self.ctx.build(ProductType)
-        eff_date = metadata.find_var("effDate")
-
-        meta_vars = list(metadata.vars)
-        meta_vars.remove(eff_date)
-        meta_vars.append(replace(eff_date, init=False, text=True))
-        metadata.vars = tuple(meta_vars)
+        eff_date = metadata.find_attribute("effDate")
+        eff_date = replace(eff_date, init=False)
 
+        metadata.attributes[eff_date.qname] = eff_date
         params = {}
         attrs = {"effDate": "2020-03-01"}
-        ns_map = {}
 
-        ParserUtils.bind_attrs(params, metadata, attrs, ns_map)
-        self.assertEqual({"other_attributes": {}}, params)
+        ParserUtils.bind_attrs(params, metadata, attrs, {})
+        self.assertEqual({}, params)
 
-    @mock.patch.object(XmlMeta, "find_var")
-    def test_bind_attrs_skip_empty_attrs(self, mock_find_var):
+    @mock.patch.object(XmlMeta, "find_attribute")
+    def test_bind_attrs_skip_empty_attrs(self, mock_find_attribute):
         metadata = self.ctx.build(ProductType)
 
         params = {}
         ParserUtils.bind_attrs(params, metadata, {}, {})
         self.assertEqual(0, len(params))
-        self.assertEqual(0, mock_find_var.call_count)
+        self.assertEqual(0, mock_find_attribute.call_count)
+
+    def test_bind_unknown_attrs(self):
+        metadata = self.ctx.build(SizeType)
+        params = {}
+        ParserUtils.bind_attrs(params, metadata, {"a": "b"}, {})
+
+        self.assertEqual({}, params)
 
     @mock.patch.object(ParserUtils, "parse_value", return_value="yes!")
     def test_bind_content(self, mock_parse_value):
         metadata = self.ctx.build(SizeType)
-        var = metadata.find_var(mode=FindMode.TEXT)
+        var = metadata.text
         params = {}
         ns_map = {"a": "b"}
 
@@ -269,7 +272,7 @@ class ParserUtilsTests(TestCase):
         self.assertEqual({}, params)
 
     def test_bind_var(self):
-        var = XmlVar(name="a", qname="a")
+        var = XmlVar(name="a", qname="a", is_element=True)
         params = {}
 
         status = ParserUtils.bind_var(params, var, 1)
@@ -280,8 +283,14 @@ class ParserUtilsTests(TestCase):
         self.assertFalse(status)
         self.assertEqual({"a": 1}, params)
 
+        var.init = False
+        params.clear()
+        status = ParserUtils.bind_var(params, var, 1)
+        self.assertTrue(status)
+        self.assertEqual({}, params)
+
     def test_bind_var_with_list_var(self):
-        var = XmlVar(name="a", qname="a", list_element=True)
+        var = XmlVar(name="a", qname="a", list_element=True, is_element=True)
         params = {}
 
         status = ParserUtils.bind_var(params, var, 1)
@@ -294,7 +303,7 @@ class ParserUtilsTests(TestCase):
 
     def test_bind_wild_var(self):
         params = {}
-        var = XmlVar(name="a", qname="a")
+        var = XmlVar(name="a", qname="a", is_wildcard=True)
         qname = "b"
         one = AnyElement(qname=qname, text="one")
         two = AnyElement(qname=qname, text="two")
@@ -312,7 +321,7 @@ class ParserUtilsTests(TestCase):
 
     def test_bind_wild_var_with_list_var(self):
         params = {}
-        var = XmlVar(name="a", qname="a", list_element=True)
+        var = XmlVar(name="a", qname="a", list_element=True, is_element=True)
         qname = "b"
         one = AnyElement(qname=qname, text="one")
         two = AnyElement(qname=qname, text="two")
@@ -325,7 +334,7 @@ class ParserUtilsTests(TestCase):
         self.assertEqual(dict(a=[one, two, three]), params)
 
     def test_bind_wild_content(self):
-        var = XmlVar(name="a", qname="a")
+        var = XmlVar(name="a", qname="a", is_wildcard=True)
         params = {}
         attrs = {}
         ns_map = {}
@@ -347,7 +356,13 @@ class ParserUtilsTests(TestCase):
         self.assertEqual(dict(a=expected), params)
 
     def test_bind_wildcard_when_var_is_list(self):
-        var = XmlVar(name="a", qname="a", default=list, list_element=True)
+        var = XmlVar(
+            name="a",
+            qname="a",
+            default=list,
+            list_element=True,
+            is_wildcard=True,
+        )
         params = {}
         attrs = {"a", "b"}
         ns_map = {"ns0", "a"}
diff --git a/tests/formats/dataclass/parsers/test_xml.py b/tests/formats/dataclass/parsers/test_xml.py
index d00ed56f..3a5553c1 100644
--- a/tests/formats/dataclass/parsers/test_xml.py
+++ b/tests/formats/dataclass/parsers/test_xml.py
@@ -5,17 +5,17 @@ from tests.fixtures.books import Books
 from xsdata.formats.dataclass.models.elements import XmlVar
 from xsdata.formats.dataclass.parsers.nodes import PrimitiveNode
 from xsdata.formats.dataclass.parsers.nodes import SkipNode
-from xsdata.formats.dataclass.parsers.xml import XmlParser
+from xsdata.formats.dataclass.parsers.xml import UserXmlParser
 from xsdata.models.enums import EventType
 
 
-class XmlParserTests(TestCase):
+class UserXmlParserTests(TestCase):
     def setUp(self):
         super().setUp()
-        self.parser = XmlParser()
+        self.parser = UserXmlParser()
         self.parser.objects = [(x, x) for x in "abcde"]
 
-    @mock.patch.object(XmlParser, "emit_event")
+    @mock.patch.object(UserXmlParser, "emit_event")
     def test_start(self, mock_emit_event):
         attrs = {"a": "b"}
         queue = []
@@ -27,11 +27,11 @@ class XmlParserTests(TestCase):
             EventType.START, "{urn:books}books", attrs=attrs
         )
 
-    @mock.patch.object(XmlParser, "emit_event")
+    @mock.patch.object(UserXmlParser, "emit_event")
     def test_end(self, mock_emit_event):
         objects = []
         queue = []
-        var = XmlVar(text=True, name="foo", qname="foo", types=(bool,))
+        var = XmlVar(is_text=True, name="foo", qname="foo", types=(bool,))
         queue.append(PrimitiveNode(var, {}))
 
         result = self.parser.end(queue, objects, "enabled", "true", None)
@@ -40,7 +40,7 @@ class XmlParserTests(TestCase):
         self.assertEqual(("enabled", True), objects[-1])
         mock_emit_event.assert_called_once_with(EventType.END, "enabled", obj=result)
 
-    @mock.patch.object(XmlParser, "emit_event")
+    @mock.patch.object(UserXmlParser, "emit_event")
     def test_end_with_no_result(self, mock_emit_event):
         objects = []
         queue = [SkipNode()]
diff --git a/tests/formats/dataclass/serializers/test_xml.py b/tests/formats/dataclass/serializers/test_xml.py
index 5d12c08d..33c6eaa7 100644
--- a/tests/formats/dataclass/serializers/test_xml.py
+++ b/tests/formats/dataclass/serializers/test_xml.py
@@ -11,6 +11,7 @@ from xml.etree.ElementTree import QName
 from tests.fixtures.books import BookForm
 from xsdata.exceptions import SerializerError
 from xsdata.exceptions import XmlContextError
+from xsdata.formats.dataclass.models.elements import XmlType
 from xsdata.formats.dataclass.models.elements import XmlVar
 from xsdata.formats.dataclass.models.generics import AnyElement
 from xsdata.formats.dataclass.models.generics import DerivedElement
@@ -104,7 +105,7 @@ class XmlSerializerTests(TestCase):
         self.assertEqual("Object <class 'int'> is not a dataclass.", str(cm.exception))
 
     def test_write_mixed_content(self):
-        var = XmlVar(wildcard=True, qname="a", name="a", mixed=True)
+        var = XmlVar(is_wildcard=True, qname="a", name="a", mixed=True)
         book = BookForm(id="123")
         ebook = DerivedElement("ebook", BookForm(id="123"))
         value = ["text", AnyElement(qname="br"), book, ebook, "tail"]
@@ -129,7 +130,7 @@ class XmlSerializerTests(TestCase):
         self.assertEqual(expected, list(result))
 
     def test_write_data(self):
-        var = XmlVar(text=True, qname="a", name="a")
+        var = XmlVar(is_text=True, qname="a", name="a")
         expected = [(XmlWriterEvent.DATA, "123")]
 
         result = self.serializer.write_value("123", var, "xsdata")
@@ -137,7 +138,7 @@ class XmlSerializerTests(TestCase):
         self.assertEqual(expected, list(result))
 
     def test_write_tokens(self):
-        var = XmlVar(element=True, qname="a", name="a", tokens=True)
+        var = XmlVar(is_element=True, qname="a", name="a", tokens=True)
 
         result = self.serializer.write_value([], var, "xsdata")
         self.assertIsInstance(result, Generator)
@@ -163,7 +164,7 @@ class XmlSerializerTests(TestCase):
         result = self.serializer.write_value([[1, 2, 3], [4, 5, 6]], var, "xsdata")
         self.assertEqual(expected, list(result))
 
-        var = XmlVar(element=True, qname="a", name="a", tokens=True, nillable=True)
+        var = XmlVar(is_element=True, qname="a", name="a", tokens=True, nillable=True)
         expected = [
             (XmlWriterEvent.START, "a"),
             (XmlWriterEvent.ATTR, QNames.XSI_NIL, "true"),
@@ -175,7 +176,7 @@ class XmlSerializerTests(TestCase):
         self.assertEqual(expected, list(result))
 
     def test_write_any_type_with_primitive(self):
-        var = XmlVar(wildcard=True, qname="a", name="a")
+        var = XmlVar(is_wildcard=True, qname="a", name="a")
         expected = [(XmlWriterEvent.DATA, "str")]
 
         result = self.serializer.write_value("str", var, "xsdata")
@@ -183,7 +184,7 @@ class XmlSerializerTests(TestCase):
         self.assertEqual(expected, list(result))
 
     def test_write_any_type_with_primitive_element(self):
-        var = XmlVar(element=True, qname="a", name="a", types=(object,))
+        var = XmlVar(is_element=True, qname="a", name="a", types=(object,))
         expected = [
             (XmlWriterEvent.START, "a"),
             (XmlWriterEvent.DATA, "str"),
@@ -195,7 +196,7 @@ class XmlSerializerTests(TestCase):
         self.assertEqual(expected, list(result))
 
     def test_write_any_type_with_any_element(self):
-        var = XmlVar(wildcard=True, qname="a", name="a")
+        var = XmlVar(is_wildcard=True, qname="a", name="a")
         value = AnyElement(
             qname="a",
             text="b",
@@ -219,7 +220,7 @@ class XmlSerializerTests(TestCase):
         self.assertEqual(expected, list(result))
 
     def test_write_any_type_with_derived_element_primitive(self):
-        var = XmlVar(wildcard=True, qname="a", name="a")
+        var = XmlVar(is_wildcard=True, qname="a", name="a")
         value = DerivedElement(qname="a", value=1)
         expected = [
             (XmlWriterEvent.START, "a"),
@@ -233,7 +234,7 @@ class XmlSerializerTests(TestCase):
         self.assertEqual(expected, list(result))
 
     def test_write_any_type_with_derived_element_dataclass(self):
-        var = XmlVar(wildcard=True, qname="a", name="a")
+        var = XmlVar(is_wildcard=True, qname="a", name="a")
         value = DerivedElement(qname="a", value=BookForm(title="def"), substituted=True)
         expected = [
             (XmlWriterEvent.START, "a"),
@@ -251,7 +252,11 @@ class XmlSerializerTests(TestCase):
 
     def test_write_xsi_type(self):
         var = XmlVar(
-            element=True, qname="a", name="a", dataclass=True, types=(BookForm,)
+            is_element=True,
+            qname="a",
+            name="a",
+            dataclass=True,
+            types=(BookForm,),
         )
         value = BookForm(id="123")
         expected = [
@@ -267,7 +272,11 @@ class XmlSerializerTests(TestCase):
 
     def test_write_xsi_type_with_derived_class(self):
         var = XmlVar(
-            element=True, qname="a", name="a", dataclass=True, types=(BookForm,)
+            is_element=True,
+            qname="a",
+            name="a",
+            dataclass=True,
+            types=(BookForm,),
         )
         ebook = make_dataclass("eBook", [], bases=(BookForm,))
 
@@ -286,7 +295,11 @@ class XmlSerializerTests(TestCase):
 
     def test_write_xsi_type_with_illegal_derived_class(self):
         var = XmlVar(
-            element=True, qname="a", name="a", dataclass=True, types=(BookForm,)
+            is_element=True,
+            qname="a",
+            name="a",
+            dataclass=True,
+            types=(BookForm,),
         )
         ebook = make_dataclass("eBook", [])
         value = ebook()
@@ -298,7 +311,7 @@ class XmlSerializerTests(TestCase):
         self.assertEqual("eBook is not derived from BookForm", str(cm.exception))
 
     def test_write_element(self):
-        var = XmlVar(element=True, qname="a", name="a")
+        var = XmlVar(is_element=True, qname="a", name="a")
         expected = [
             (XmlWriterEvent.START, "a"),
             (XmlWriterEvent.DATA, "123"),
@@ -310,7 +323,7 @@ class XmlSerializerTests(TestCase):
         self.assertEqual(expected, list(result))
 
     def test_write_element_with_nillable_true(self):
-        var = XmlVar(element=True, qname="a", name="a", nillable=True)
+        var = XmlVar(is_element=True, qname="a", name="a", nillable=True)
         expected = [
             (XmlWriterEvent.START, "a"),
             (XmlWriterEvent.ATTR, QNames.XSI_NIL, "true"),
@@ -323,7 +336,13 @@ class XmlSerializerTests(TestCase):
         self.assertEqual(expected, list(result))
 
     def test_write_element_with_any_type_var(self):
-        var = XmlVar(element=True, qname="a", name="a", types=(object,), any_type=True)
+        var = XmlVar(
+            is_element=True,
+            qname="a",
+            name="a",
+            types=(object,),
+            any_type=True,
+        )
         expected = [
             (XmlWriterEvent.START, "a"),
             (XmlWriterEvent.ATTR, QNames.XSI_TYPE, QName(str(DataType.SHORT))),
@@ -336,7 +355,13 @@ class XmlSerializerTests(TestCase):
         self.assertEqual(expected, list(result))
 
     def test_write_element_with_any_type_var_ignore_xs_string(self):
-        var = XmlVar(element=True, qname="a", name="a", types=(object,), any_type=True)
+        var = XmlVar(
+            is_element=True,
+            qname="a",
+            name="a",
+            types=(object,),
+            any_type=True,
+        )
         expected = [
             (XmlWriterEvent.START, "a"),
             (XmlWriterEvent.DATA, ""),
@@ -357,10 +382,10 @@ class XmlSerializerTests(TestCase):
 
     def test_write_choice_with_derived_primitive_value(self):
         var = XmlVar(
-            elements=True,
+            is_elements=True,
             name="compound",
             qname="compound",
-            choices=[XmlVar(element=True, qname="a", name="a", types=(int,))],
+            elements={"a": XmlVar(is_element=True, qname="a", name="a", types=(int,))},
         )
         value = DerivedElement(qname="a", value=1)
         expected = [
@@ -375,10 +400,10 @@ class XmlSerializerTests(TestCase):
 
     def test_write_choice_with_derived_dataclass(self):
         var = XmlVar(
-            elements=True,
+            is_elements=True,
             name="compound",
             qname="compound",
-            choices=[XmlVar(element=True, qname="a", name="a", types=(A,))],
+            elements={"a": XmlVar(is_element=True, qname="a", name="a", types=(A,))},
         )
         value = DerivedElement(qname="a", value=A("foo"))
         expected = [
@@ -393,10 +418,10 @@ class XmlSerializerTests(TestCase):
 
     def test_write_choice_with_generic_object(self):
         var = XmlVar(
-            elements=True,
+            is_elements=True,
             name="compound",
             qname="compound",
-            choices=[XmlVar(element=True, qname="a", name="a", types=(int,))],
+            elements={"a": XmlVar(is_element=True, qname="a", name="a", types=(int,))},
         )
         value = AnyElement(qname="a", text="1")
         expected = [
@@ -411,13 +436,19 @@ class XmlSerializerTests(TestCase):
 
     def test_write_choice_with_raw_value(self):
         var = XmlVar(
-            elements=True,
+            is_elements=True,
             name="compound",
             qname="compound",
-            choices=[
-                XmlVar(element=True, qname="a", name="a", types=(int,)),
-                XmlVar(element=True, qname="b", name="b", types=(int,), tokens=True),
-            ],
+            elements={
+                "a": XmlVar(is_element=True, qname="a", name="a", types=(int,)),
+                "b": XmlVar(
+                    is_element=True,
+                    qname="b",
+                    name="b",
+                    types=(int,),
+                    tokens=True,
+                ),
+            },
         )
         expected = [
             (XmlWriterEvent.START, "a"),
@@ -434,10 +465,12 @@ class XmlSerializerTests(TestCase):
 
     def test_write_choice_when_no_matching_choice_exists(self):
         var = XmlVar(
-            elements=True,
+            is_elements=True,
             name="compound",
             qname="compound",
-            choices=[XmlVar(element=True, qname="a", name="a", types=(float,))],
+            elements={
+                "a": XmlVar(is_element=True, qname="a", name="a", types=(float,))
+            },
         )
 
         with self.assertRaises(SerializerError) as cm:
@@ -448,7 +481,7 @@ class XmlSerializerTests(TestCase):
         self.assertEqual(msg, str(cm.exception))
 
     def test_write_value_with_list_value(self):
-        var = XmlVar(element=True, qname="a", name="a", list_element=True)
+        var = XmlVar(is_element=True, qname="a", name="a", list_element=True)
         value = [True, False]
         expected = [
             (XmlWriterEvent.START, "a"),
@@ -466,10 +499,10 @@ class XmlSerializerTests(TestCase):
     def test_next_value(self):
         obj = A(x0=1, x1=[2, 3, 4], x2=[6, 7], x3=[9])
         meta = self.serializer.context.build(A)
-        x0 = meta.find_var("x0")
-        x1 = meta.find_var("x1")
-        x2 = meta.find_var("x2")
-        x3 = meta.find_var("x3")
+        x0 = meta.text
+        x1 = meta.find_elements("x1")[0]
+        x2 = meta.find_elements("x2")[0]
+        x3 = meta.find_elements("x3")[0]
 
         actual = self.serializer.next_value(obj, meta)
         expected = [
diff --git a/tests/formats/dataclass/test_elements.py b/tests/formats/dataclass/test_elements.py
index 3304f3ae..cfcdc741 100644
--- a/tests/formats/dataclass/test_elements.py
+++ b/tests/formats/dataclass/test_elements.py
@@ -1,13 +1,10 @@
 from dataclasses import dataclass
 from dataclasses import make_dataclass
-from dataclasses import replace
+from typing import Iterator
+from unittest import mock
 from unittest.case import TestCase
 
-from tests.fixtures.books.books import BookForm
-from xsdata.exceptions import XmlContextError
-from xsdata.formats.dataclass.context import XmlContext
-from xsdata.formats.dataclass.models.elements import FindMode
-from xsdata.formats.dataclass.models.elements import XmlType
+from xsdata.formats.dataclass.models.elements import XmlMeta
 from xsdata.formats.dataclass.models.elements import XmlVar
 
 
@@ -42,91 +39,174 @@ class XmlValTests(TestCase):
         var = XmlVar(name="foo", qname="foo", types=(int,), list_element=True)
         self.assertTrue(var.list_element)
 
-    def test_matches(self):
-        var = XmlVar(name="foo", qname="foo")
-        self.assertTrue(var.matches("*"))
-        self.assertTrue(var.matches(var.qname))
-        self.assertFalse(var.matches("bar"))
-
     def test_find_choice(self):
-        choices = [
-            XmlVar(element=True, name="a", qname="{a}a"),
-            XmlVar(element=True, name="b", qname="b"),
-        ]
-        var = XmlVar(elements=True, name="foo", qname="foo", choices=choices)
+        var = XmlVar(
+            is_elements=True,
+            name="foo",
+            qname="foo",
+            elements={
+                "{a}a": XmlVar(is_element=True, name="a", qname="{a}a"),
+                "b": XmlVar(is_element=True, name="b", qname="b"),
+            },
+        )
 
-        self.assertFalse(var.matches("a"))
         self.assertIsNone(var.find_choice("a"))
+        self.assertEqual("a", var.find_choice("{a}a").name)
+        self.assertEqual("b", var.find_choice("b").name)
+
+        var.elements.clear()
+        var.wildcards = [
+            XmlVar(
+                is_wildcard=True, name="target", qname="target", namespaces=("foo",)
+            ),
+            XmlVar(is_wildcard=True, name="other", qname="other", namespaces=("!foo",)),
+        ]
 
-        self.assertEqual(choices[0], var.find_choice("{a}a"))
-        self.assertTrue(var.matches("{a}a"))
-
-        self.assertEqual(choices[1], var.find_choice("b"))
-        self.assertTrue(var.matches("b"))
+        self.assertEqual(var.wildcards[1], var.find_choice("{a}a"))
+        self.assertEqual(var.wildcards[0], var.find_choice("{foo}a"))
 
     def test_find_value_choice(self):
         c = make_dataclass("C", fields=[])
         d = make_dataclass("D", fields=[], bases=(c,))
 
+        elements = [
+            XmlVar(is_element=True, qname="a", name="a", types=(int,)),
+            XmlVar(is_element=True, qname="b", name="b", types=(int,), tokens=True),
+            XmlVar(is_element=True, qname="c", name="c", types=(c,), dataclass=True),
+            XmlVar(is_element=True, qname="d", name="d", types=(float,), nillable=True),
+        ]
+
         var = XmlVar(
-            elements=True,
+            is_elements=True,
             name="compound",
             qname="compound",
-            choices=[
-                XmlVar(element=True, qname="a", name="a", types=(int,)),
-                XmlVar(element=True, qname="b", name="b", types=(int,), tokens=True),
-                XmlVar(element=True, qname="c", name="c", types=(c,), dataclass=True),
-                XmlVar(
-                    element=True, qname="d", name="d", types=(float,), nillable=True
-                ),
-            ],
+            elements={x.qname: x for x in elements},
         )
 
         self.assertIsNone(var.find_value_choice("foo"))
-        self.assertEqual(var.choices[0], var.find_value_choice(1))
-        self.assertEqual(var.choices[1], var.find_value_choice([1, 2]))
-        self.assertEqual(var.choices[2], var.find_value_choice(d()))
-        self.assertEqual(var.choices[2], var.find_value_choice(c()))
-        self.assertEqual(var.choices[3], var.find_value_choice(None))
+        self.assertEqual(elements[0], var.find_value_choice(1))
+        self.assertEqual(elements[1], var.find_value_choice([1, 2]))
+        self.assertEqual(elements[2], var.find_value_choice(d()))
+        self.assertEqual(elements[2], var.find_value_choice(c()))
+        self.assertEqual(elements[3], var.find_value_choice(None))
+
+    def test_match_namespace(self):
+        var = XmlVar(is_wildcard=True, name="foo", qname="foo")
+        self.assertTrue(var.match_namespace("*"))
+        self.assertTrue(var.match_namespace("a"))
 
-    def test_matches_widlcard(self):
-        var = XmlVar(wildcard=True, name="foo", qname="foo")
-        self.assertTrue(var.matches("*"))
-        self.assertTrue(var.matches("a"))
+        var = XmlVar(is_wildcard=True, name="foo", qname="foo", namespaces=("tns",))
+        self.assertFalse(var.match_namespace("a"))
+        self.assertTrue(var.match_namespace("{tns}a"))
 
-        var = XmlVar(wildcard=True, name="foo", qname="foo", namespaces=("tns",))
-        self.assertFalse(var.matches("a"))
-        self.assertTrue(var.matches("{tns}a"))
+        var = XmlVar(is_wildcard=True, name="foo", qname="foo", namespaces=("##any",))
+        self.assertTrue(var.match_namespace("a"))
+        self.assertTrue(var.match_namespace("{tns}a"))
 
-        var = XmlVar(wildcard=True, name="foo", qname="foo", namespaces=("##any",))
-        self.assertTrue(var.matches("a"))
-        self.assertTrue(var.matches("{tns}a"))
+        var = XmlVar(is_wildcard=True, name="foo", qname="foo", namespaces=("",))
+        self.assertTrue(var.match_namespace("a"))
+        self.assertFalse(var.match_namespace("{tns}a"))
 
-        var = XmlVar(wildcard=True, name="foo", qname="foo", namespaces=("",))
-        self.assertTrue(var.matches("a"))
-        self.assertFalse(var.matches("{tns}a"))
+        var = XmlVar(is_wildcard=True, name="foo", qname="foo", namespaces=("!tns",))
+        self.assertTrue(var.match_namespace("{foo}a"))
+        self.assertFalse(var.match_namespace("{tns}a"))
 
-        var = XmlVar(wildcard=True, name="foo", qname="foo", namespaces=("!tns",))
-        self.assertTrue(var.matches("{foo}a"))
-        self.assertFalse(var.matches("{tns}a"))
+        var.namespace_matches["{tns}cached"] = True
+        self.assertTrue(var.match_namespace("{tns}cached"))
 
 
 class XmlMetaTests(TestCase):
-    def test_find_var(self):
-        ctx = XmlContext()
-        meta = ctx.build(BookForm)
+    def setUp(self) -> None:
+        a = make_dataclass("a", [])
+        self.meta = XmlMeta(clazz=a, qname="a", source_qname="a", nillable=False)
+
+    def test_find_attribute(self):
+        a = XmlVar(is_attribute=True, qname="a", name="a")
+        b = XmlVar(is_attribute=True, qname="b", name="b")
+
+        self.meta.attributes[a.qname] = a
+        self.meta.attributes[b.qname] = b
+
+        self.assertEqual(a, self.meta.find_attribute("a"))
+        self.assertEqual(b, self.meta.find_attribute("b"))
+
+    def test_find_elements(self):
+        a_1 = XmlVar(is_attribute=True, qname="a", name="a_1")
+        a_2 = XmlVar(is_attribute=True, qname="a", name="a_2")
+
+        self.meta.elements[a_1.qname] = [a_1, a_2]
+
+        self.assertEqual([a_1, a_2], self.meta.find_elements("a"))
+        self.assertEqual([], self.meta.find_elements("b"))
 
-        self.assertTrue(meta.find_var("author").element)
-        self.assertIsNone(meta.find_var("author", FindMode.ATTRIBUTE))
-        self.assertIsNone(meta.find_var("nope"))
+    @mock.patch.object(XmlVar, "find_choice")
+    def test_find_choice(self, mock_find_choice):
+        a_1 = XmlVar(is_attribute=True, qname="a", name="a_1")
+        a_2 = XmlVar(is_attribute=True, qname="a", name="a_2")
 
-    def test_find_var_uses_cache(self):
-        ctx = XmlContext()
-        meta = ctx.build(BookForm)
+        mock_find_choice.side_effect = [None, None, None, a_1, a_2]
+        choice_1 = XmlVar(is_attribute=True, qname="compound_1", name="compound_1")
+        choice_2 = XmlVar(is_attribute=True, qname="compound_2", name="compound_2")
+        self.meta.choices = [choice_1, choice_2]
+
+        self.assertIsNone(self.meta.find_choice("a"))
+        self.assertEqual(a_1, self.meta.find_choice("a"))
+        self.assertEqual(a_2, self.meta.find_choice("a"))
+
+        mock_find_choice.assert_has_calls([mock.call("a") for _ in range(5)])
+
+    @mock.patch.object(XmlVar, "match_namespace")
+    def test_find_any_attributes(self, mock_match_namespace):
+        mock_match_namespace.side_effect = [False, False, False, True]
+
+        attributes = [
+            XmlVar(is_attributes=True, qname="any", name="any"),
+            XmlVar(is_attributes=True, qname="other", name="any"),
+        ]
+        self.meta.any_attributes = attributes
+
+        self.assertIsNone(self.meta.find_any_attributes("a"))
+        self.assertEqual(attributes[1], self.meta.find_any_attributes("a"))
+
+        mock_match_namespace.assert_has_calls([mock.call("a") for _ in range(4)])
+
+    @mock.patch.object(XmlVar, "match_namespace")
+    def test_find_wildcard(self, mock_match_namespace):
+        mock_match_namespace.side_effect = [False, False, False, True]
+
+        wildcards = [
+            XmlVar(is_wildcard=True, qname="any", name="any"),
+            XmlVar(is_wildcard=True, qname="other", name="any"),
+        ]
+        self.meta.wildcards = wildcards
+
+        self.assertIsNone(self.meta.find_wildcard("a"))
+        self.assertEqual(wildcards[1], self.meta.find_wildcard("a"))
+
+        mock_match_namespace.assert_has_calls([mock.call("a") for _ in range(4)])
+
+    def test_find_children(self):
+        element1 = XmlVar(is_element=True, qname="a", name="a")
+        element2 = XmlVar(is_element=True, qname="a", name="a1")
+
+        option1 = XmlVar(is_element=True, qname="a", name="a3")
+        option2 = XmlVar(is_element=True, qname="b", name="b")
+
+        choice = XmlVar(
+            is_elements=True,
+            qname="c1",
+            name="c1",
+            elements={
+                "a": option1,
+                "b": option2,
+            },
+        )
+        wildcard = XmlVar(is_wildcard=True, qname="any", name="any")
 
-        self.assertEqual("author", meta.find_var("author").name)
-        self.assertEqual(1, len(meta.cache))
-        key = tuple(meta.cache.keys())[0]
+        self.meta.elements["a"] = [element1, element2]
+        self.meta.choices.append(choice)
+        self.meta.wildcards.append(wildcard)
 
-        meta.cache[key] = 1
-        self.assertEqual("title", meta.find_var("author").name)
+        result = self.meta.find_children("a")
+        self.assertIsInstance(result, Iterator)
+        self.assertEqual([element1, element2, option1, wildcard], list(result))