Repository: medley56/space_packet_parser

Issue:
Refactor `xtcedef.py` into sub modules
The xtcedef.py module is about 2500 lines of code and the accompanying test module is about 1800. These are too big and make navigating the codebase annoying. 

Refactor xtcedef and its test module to split up into different groups. Perhaps:
- common classes (e.g. ABCs)
- exceptions
- encodings
- parameter_types
- parameters

Could put the top level XtceDefinition class either in __init__.py or in its own submodule.

Discussion:

Solution patch:
diff --git a/docs/source/changelog.md b/docs/source/changelog.md
index a757334..1d892b1 100644
--- a/docs/source/changelog.md
+++ b/docs/source/changelog.md
@@ -6,6 +6,8 @@ list and release milestones.
 Release notes for the `space_packet_parser` library
 
 ### v5.0.0 (unreleased)
+- BREAKING: Separated out logical pieces into separate modules rather than everything
+  living within the xtcedef module. This means user imports may be different now.
 - BREAKING: Replace `bitstring` objects with native Python bytes objects
   - Remove dependency on the `bitstring` library
   - Much faster parsing speed
diff --git a/docs/source/users.md b/docs/source/users.md
index bdbbcbb..2ae7776 100644
--- a/docs/source/users.md
+++ b/docs/source/users.md
@@ -11,11 +11,11 @@ Usage with XTCE packet definition:
 
 ```python
 from pathlib import Path
-from space_packet_parser import xtcedef, parser
+from space_packet_parser import definitions, parser
 
 packet_file = Path('my_packets.pkts')
 xtce_document = Path('my_xtce_document.xml')
-packet_definition = xtcedef.XtcePacketDefinition(xtce_document)
+packet_definition = definitions.XtcePacketDefinition(xtce_document)
 
 # You can introspect the packet definition to learn about what was parsed
 pt = packet_definition.named_parameter_types["MY_PARAM_Type"]  # Look up a type (includes unit and encoding info)
@@ -38,11 +38,11 @@ Usage with CSV packet definition:
 
 ```python
 from pathlib import Path
-from space_packet_parser import csvdef, parser
+from space_packet_parser import definitions, parser
 
 packet_file = Path('my_packets.pkts')
 csv_document = Path('my_csv_file.csv')
-packet_definition = csvdef.CsvPacketDefinition(csv_document)
+packet_definition = definitions.CsvPacketDefinition(csv_document)
 my_parser = parser.PacketParser(packet_definition)
 
 with packet_file.open("rb") as binary_data:
diff --git a/examples/parsing_and_plotting_idex_waveforms_from_socket.py b/examples/parsing_and_plotting_idex_waveforms_from_socket.py
index 5a84411..76c2072 100644
--- a/examples/parsing_and_plotting_idex_waveforms_from_socket.py
+++ b/examples/parsing_and_plotting_idex_waveforms_from_socket.py
@@ -19,7 +19,7 @@
 # Installed
 import matplotlib.pyplot as plt
 # Local
-from space_packet_parser import xtcedef
+from space_packet_parser import definitions
 from space_packet_parser import parser
 
 
@@ -99,8 +99,8 @@ def plot_full_event(data: dict):
     """Parse IDEX data"""
     idex_test_data_dir = Path("../tests/test_data/idex")
     idex_xtce = idex_test_data_dir / 'idex_combined_science_definition.xml'
-    idex_definition = xtcedef.XtcePacketDefinition(xtce_document=idex_xtce)
-    assert isinstance(idex_definition, xtcedef.XtcePacketDefinition)
+    idex_definition = definitions.XtcePacketDefinition(xtce_document=idex_xtce)
+    assert isinstance(idex_definition, definitions.XtcePacketDefinition)
     idex_parser = parser.PacketParser(idex_definition)
     idex_packet_file = idex_test_data_dir / 'sciData_2023_052_14_45_05'
 
diff --git a/space_packet_parser/calibrators.py b/space_packet_parser/calibrators.py
new file mode 100644
index 0000000..59436bb
--- /dev/null
+++ b/space_packet_parser/calibrators.py
@@ -0,0 +1,361 @@
+
+"""Calibrator definitions"""
+from abc import ABCMeta, abstractmethod
+from collections import namedtuple
+from typing import List, Union
+
+import lxml.etree as ElementTree
+
+from space_packet_parser.exceptions import CalibrationError
+from space_packet_parser import comparisons
+
+
+class Calibrator(comparisons.AttrComparable, metaclass=ABCMeta):
+    """Abstract base class for XTCE calibrators"""
+
+    @classmethod
+    @abstractmethod
+    def from_calibrator_xml_element(cls, element: ElementTree.Element, ns: dict) -> 'Calibrator':
+        """Abstract classmethod to create a default_calibrator object from an XML element.
+
+        Parameters
+        ----------
+        element : ElementTree.Element
+            XML element
+        ns : dict
+            XML namespace dict
+
+        Returns
+        -------
+        cls
+        """
+        return NotImplemented
+
+    @abstractmethod
+    def calibrate(self, uncalibrated_value: Union[int, float]) -> Union[int, float]:
+        """Takes an integer-encoded or float-encoded value and returns a calibrated version.
+
+        Parameters
+        ----------
+        uncalibrated_value : Union[int, float]
+            The uncalibrated, raw encoded value
+
+        Returns
+        -------
+        : Union[int, float]
+            Calibrated value
+        """
+        raise NotImplementedError
+
+
+SplinePoint = namedtuple('SplinePoint', ['raw', 'calibrated'])
+
+
+class SplineCalibrator(Calibrator):
+    """<xtce:SplineCalibrator>"""
+    _order_mapping = {'zero': 0, 'first': 1, 'second': 2, 'third': 3}
+
+    def __init__(self, points: list, order: int = 0, extrapolate: bool = False):
+        """Constructor
+
+        Parameters
+        ----------
+        points : list
+            List of SplinePoint objects. These points are sorted by their raw values on instantiation.
+        order : int
+            Spline order. Only zero and first order splines are supported.
+        extrapolate : bool
+            Whether or not to allow extrapolation outside the bounds of the spline points. If False, raises an
+            error when calibrate is called for a query point outside the bounds of the spline points.
+        """
+        if order > 1:
+            raise NotImplementedError("Spline calibrators of order > 1 are not implemented. Consider contributing "
+                                      "if you need this functionality. It does not appear to be commonly used but "
+                                      "it probably would not be too hard to implement.")
+        self.order = order
+        self.points = sorted(points, key=lambda point: point.raw)  # Sort points before storing
+        self.extrapolate = extrapolate
+
+    @classmethod
+    def from_calibrator_xml_element(cls, element: ElementTree.Element, ns: dict) -> 'SplineCalibrator':
+        """Create a spline default_calibrator object from an <xtce:SplineCalibrator> XML element."""
+        point_elements = element.findall('xtce:SplinePoint', ns)
+        spline_points = [
+            SplinePoint(raw=float(p.attrib['raw']), calibrated=float(p.attrib['calibrated']))
+            for p in point_elements
+        ]
+        order = int(cls._order_mapping[element.attrib['order']]) if 'order' in element.attrib else 0
+        extrapolate = element.attrib['extrapolate'].lower() == 'true' if 'extrapolate' in element.attrib else False
+        return cls(order=order, points=spline_points, extrapolate=extrapolate)
+
+    def calibrate(self, uncalibrated_value: float) -> float:
+        """Take an integer-encoded value and returns a calibrated version according to the spline points.
+
+        Parameters
+        ----------
+        uncalibrated_value : float
+            Query point.
+
+        Returns
+        -------
+        : float
+            Calibrated value
+        """
+        if self.order == 0:
+            return self._zero_order_spline_interp(uncalibrated_value)
+        if self.order == 1:
+            return self._first_order_spline_interp(uncalibrated_value)
+        raise NotImplementedError(f"SplineCalibrator is not implemented for spline order {self.order}.")
+
+    def _zero_order_spline_interp(self, query_point: float) -> float:
+        """Abstraction for zero order spline interpolation. If extrapolation is set to a truthy value, we use
+        the nearest point to extrapolate outside the range of the given spline points. Within the range of spline
+        points, we use nearest lower point interpolation.
+
+        Parameters
+        ----------
+        query_point : float
+            Query point.
+
+        Returns
+        -------
+        : float
+            Calibrated value.
+        """
+        x = [float(p.raw) for p in self.points]
+        y = [float(p.calibrated) for p in self.points]
+        if min(x) <= query_point <= max(x):
+            first_greater = [p.raw > query_point for p in self.points].index(True)
+            return y[first_greater - 1]
+        if query_point > max(x) and self.extrapolate:
+            return y[-1]
+        if query_point < min(x) and self.extrapolate:
+            return y[0]
+        raise CalibrationError(f"Extrapolation is set to a falsy value ({self.extrapolate}) but query value "
+                               f"{query_point} falls outside the range of spline points {self.points}")
+
+    def _first_order_spline_interp(self, query_point: float) -> float:
+        """Abstraction for first order spline interpolation. If extrapolation is set to a truthy value, we use the
+        end points to make a linear function and use it to extrapolate.
+
+        Parameters
+        ----------
+        query_point : float
+            Query point.
+
+        Returns
+        -------
+        float
+            Calibrated value.
+        """
+
+        def linear_func(xq: float, x0: float, x1: float, y0: float, y1: float) -> float:
+            """Evaluate a linear function through points (x0, y0), (x1, y1) at point xq
+
+            Parameters
+            ----------
+            xq : float
+            x0 : float
+            x1 : float
+            y0 : float
+            y1 : float
+
+            Returns
+            -------
+            yq : float
+                Interpolated point
+            """
+            slope = (y1 - y0) / (x1 - x0)
+            return (slope * (xq - x0)) + y0
+
+        x = [p.raw for p in self.points]
+        y = [p.calibrated for p in self.points]
+        if min(x) <= query_point <= max(x):
+            first_greater = [p.raw > query_point for p in self.points].index(True)
+            return linear_func(query_point,
+                               x[first_greater - 1], x[first_greater],
+                               y[first_greater - 1], y[first_greater])
+        if query_point > max(x) and self.extrapolate:
+            return linear_func(query_point, x[-2], x[-1], y[-2], y[-1])
+        if query_point < min(x) and self.extrapolate:
+            return linear_func(query_point, x[0], x[1], y[0], y[1])
+        raise CalibrationError(f"Extrapolation is set to a falsy value ({self.extrapolate}) but query value "
+                               f"{query_point} falls outside the range of spline points {self.points}")
+
+
+PolynomialCoefficient = namedtuple('PolynomialCoefficient', ['coefficient', 'exponent'])
+
+
+class PolynomialCalibrator(Calibrator):
+    """<xtce:PolynomialCalibrator>"""
+
+    def __init__(self, coefficients: list):
+        """Constructor
+
+        Parameters
+        ----------
+        coefficients : list
+            List of PolynomialCoefficient objects that define the polynomial.
+        """
+        self.coefficients = coefficients  # Coefficients should be a list of PolynomialCoefficients
+
+    @classmethod
+    def from_calibrator_xml_element(cls, element: ElementTree.Element, ns: dict) -> 'PolynomialCalibrator':
+        """Create a polynomial default_calibrator object from an <xtce:PolynomialCalibrator> XML element.
+
+        Parameters
+        ----------
+        element : ElementTree.Element
+            <xtce:PolynomialCalibrator> XML element
+        ns : dict
+            Namespace dict
+
+        Returns
+        -------
+
+        """
+        terms = element.findall('xtce:Term', ns)
+        coefficients = [
+            PolynomialCoefficient(coefficient=float(term.attrib['coefficient']), exponent=int(term.attrib['exponent']))
+            for term in terms
+        ]
+        return cls(coefficients=coefficients)
+
+    def calibrate(self, uncalibrated_value: float) -> float:
+        """Evaluate the polynomial defined by object coefficients at the specified uncalibrated point.
+
+        Parameters
+        ----------
+        uncalibrated_value : float
+            Query point.
+
+        Returns
+        -------
+        float
+            Calibrated value
+        """
+        return sum(a * (uncalibrated_value ** n) for a, n in self.coefficients)
+
+
+class MathOperationCalibrator(Calibrator):
+    """<xtce:MathOperationCalibrator>"""
+    err_msg = "The MathOperationCalibrator element is not supported in this package but pull requests are welcome!"
+
+    def __init__(self):
+        """Constructor
+
+        Not implemented.
+        """
+        raise NotImplementedError(self.err_msg)
+
+    @classmethod
+    def from_calibrator_xml_element(cls, element: ElementTree.Element, ns: dict) -> 'MathOperationCalibrator':
+        """Create a math operation default_calibrator from an <xtce:MathOperationCalibrator> XML element."""
+        raise NotImplementedError(cls.err_msg)
+
+    def calibrate(self, uncalibrated_value: int):
+        """Stub
+
+        Parameters
+        ----------
+        uncalibrated_value
+
+        Returns
+        -------
+
+        """
+        raise NotImplementedError(self.err_msg)
+
+
+class ContextCalibrator(comparisons.AttrComparable):
+    """<xtce:ContextCalibrator>"""
+
+    def __init__(self, match_criteria: list, calibrator: Calibrator):
+        """Constructor
+
+        Parameters
+        ----------
+        match_criteria : Union[MatchCriteria, list]
+            Object representing the logical operations to be performed to determine whether to use this
+            default_calibrator. This can be a Comparison, a ComparsonList (a list of Comparison objects),
+            a BooleanExpression (not supported), or a CustomAlgorithm (not supported)
+        calibrator : Calibrator
+            Calibrator to use if match criteria evaluates to True
+        """
+        self.match_criteria = match_criteria
+        self.calibrator = calibrator
+
+    @staticmethod
+    def get_context_match_criteria(element: ElementTree.Element, ns: dict) -> List[comparisons.MatchCriteria]:
+        """Parse contextual requirements from a Comparison, ComparisonList, or BooleanExpression
+
+        Parameters
+        ----------
+        element : ElementTree.Element
+            <xtce:ContextCalibrator> XML element from which to parse the ContextCalibrator object.
+        ns : dict
+            Namespace dict for XML parsing
+
+        Returns
+        -------
+        : List[MatchCriteria]
+            List of Comparisons that can be evaluated to determine whether this calibrator should be used.
+        """
+        context_match_element = element.find('xtce:ContextMatch', ns)
+        if context_match_element.find('xtce:ComparisonList', ns) is not None:
+            return [comparisons.Comparison.from_match_criteria_xml_element(el, ns)
+                    for el in context_match_element.findall('xtce:ComparisonList/xtce:Comparison', ns)]
+        if context_match_element.find('xtce:Comparison', ns) is not None:
+            return [comparisons.Comparison.from_match_criteria_xml_element(
+                context_match_element.find('xtce:Comparison', ns), ns)]
+        if context_match_element.find('xtce:BooleanExpression', ns) is not None:
+            return [comparisons.BooleanExpression.from_match_criteria_xml_element(
+                context_match_element.find('xtce:BooleanExpression', ns), ns)]
+        raise NotImplementedError("ContextCalibrator doesn't contain Comparison, ComparisonList, or BooleanExpression. "
+                                  "This probably means the match criteria is an unsupported type "
+                                  "(CustomAlgorithm).")
+
+    @classmethod
+    def from_context_calibrator_xml_element(cls, element: ElementTree.Element, ns: dict) -> 'ContextCalibrator':
+        """Create a ContextCalibrator object from an <xtce:ContextCalibrator> XML element
+
+        Parameters
+        ----------
+        element : ElementTree.Element
+            <xtce:ContextCalibrator> XML element from which to parse the ContextCalibrator object.
+        ns : dict
+            Namespace dict for XML parsing
+
+        Returns
+        -------
+        : cls
+        """
+        match_criteria = cls.get_context_match_criteria(element, ns)
+
+        if element.find('xtce:Calibrator/xtce:SplineCalibrator', ns) is not None:
+            calibrator = SplineCalibrator.from_calibrator_xml_element(
+                element.find('xtce:Calibrator/xtce:SplineCalibrator', ns), ns)
+        elif element.find('xtce:Calibrator/xtce:PolynomialCalibrator', ns):
+            calibrator = PolynomialCalibrator.from_calibrator_xml_element(
+                element.find('xtce:Calibrator/xtce:PolynomialCalibrator', ns), ns)
+        else:
+            raise NotImplementedError(
+                "Unsupported default_calibrator type. space_packet_parser only supports Polynomial and Spline"
+                "calibrators for ContextCalibrators.")
+
+        return cls(match_criteria=match_criteria, calibrator=calibrator)
+
+    def calibrate(self, parsed_value: Union[int, float]) -> Union[int, float]:
+        """Wrapper method for the internal `Calibrator.calibrate`
+
+        Parameters
+        ----------
+        parsed_value : Union[int, float]
+            Uncalibrated value.
+
+        Returns
+        -------
+        : Union[int, float]
+            Calibrated value
+        """
+        return self.calibrator.calibrate(parsed_value)
diff --git a/space_packet_parser/comparisons.py b/space_packet_parser/comparisons.py
new file mode 100644
index 0000000..c11c1b0
--- /dev/null
+++ b/space_packet_parser/comparisons.py
@@ -0,0 +1,543 @@
+"""Matching logical objects"""
+from abc import ABCMeta, abstractmethod
+from collections import namedtuple
+import inspect
+from typing import Any, Optional, Union
+import warnings
+
+import lxml.etree as ElementTree
+
+from space_packet_parser.exceptions import ComparisonError
+
+
+# Common comparable mixin
+class AttrComparable(metaclass=ABCMeta):
+    """Generic class that provides a notion of equality based on all non-callable, non-dunder attributes"""
+
+    def __eq__(self, other):
+        if not isinstance(other, self.__class__):
+            raise NotImplementedError(f"No method to compare {type(other)} with {self.__class__}")
+
+        compare = inspect.getmembers(self, lambda a: not inspect.isroutine(a))
+        compare = [attr[0] for attr in compare
+                   if not (attr[0].startswith('__') or attr[0].startswith(f'_{self.__class__.__name__}__'))]
+        for attr in compare:
+            if getattr(self, attr) != getattr(other, attr):
+                print(f'Mismatch was in {attr}. {getattr(self, attr)} != {getattr(other, attr)}')
+                return False
+        return True
+
+
+class MatchCriteria(AttrComparable, metaclass=ABCMeta):
+    """<xtce:MatchCriteriaType>
+    This class stores criteria for performing logical operations based on parameter values
+    Classes that inherit from this ABC include those that represent <xtce:Comparison>, <xtce:ComparisonList>,
+    <xtce:BooleanExpression> (not supported), and <xtce:CustomAlgorithm> (not supported)
+    """
+
+    # Valid operator representations in XML. Note: the XTCE spec only allows for &gt; style representations of < and >
+    #   Python's XML parser doesn't appear to support &eq; &ne; &le; or &ge;
+    # We have implemented support for bash-style comparisons just in case.
+    _valid_operators = {
+        "==": "__eq__", "eq": "__eq__",  # equal to
+        "!=": "__ne__", "neq": "__ne__",  # not equal to
+        "&lt;": "__lt__", "lt": "__lt__", "<": "__lt__",  # less than
+        "&gt;": "__gt__", "gt": "__gt__", ">": "__gt__",  # greater than
+        "&lt;=": "__le__", "leq": "__le__", "<=": "__le__",  # less than or equal to
+        "&gt;=": "__ge__", "geq": "__ge__", ">=": "__ge__",  # greater than or equal to
+    }
+
+    @classmethod
+    @abstractmethod
+    def from_match_criteria_xml_element(cls, element: ElementTree.Element, ns: dict):
+        """Abstract classmethod to create a match criteria object from an XML element.
+
+        Parameters
+        ----------
+        element : ElementTree.Element
+            XML element
+        ns : dict
+            XML namespace dict
+
+        Returns
+        -------
+        : cls
+        """
+        raise NotImplementedError()
+
+    @abstractmethod
+    def evaluate(self, parsed_data: dict, current_parsed_value: Optional[Union[int, float]] = None) -> bool:
+        """Evaluate match criteria down to a boolean.
+
+        Parameters
+        ----------
+        parsed_data : dict
+            Dictionary of parsed parameter data so far. Used to evaluate truthyness of the match criteria.
+        current_parsed_value : any, Optional
+            Uncalibrated value that is currently being matched (e.g. as a candidate for calibration).
+            Used to resolve comparisons that reference their own raw value as a condition.
+
+        Returns
+        -------
+        : bool
+            Truthyness of this match criteria based on parsed_data values.
+        """
+        raise NotImplementedError()
+
+
+class Comparison(MatchCriteria):
+    """<xtce:Comparison>"""
+
+    def __init__(self, required_value: any, referenced_parameter: str,
+                 operator: str = "==", use_calibrated_value: bool = True):
+        """Constructor
+
+        Parameters
+        ----------
+        operator : str
+            String representation of the comparison operation. e.g. "<=" or "leq"
+        required_value : any
+            Value with which to compare the referenced parameter using the operator. This value is dynamically
+            coerced to the referenced parameter type during evaluation.
+        referenced_parameter : str
+            Name of the parameter to compare with the value.
+        use_calibrated_value : bool
+            Whether or not to calibrate the value before performing the comparison.
+        """
+        self.required_value = required_value
+        self.referenced_parameter = referenced_parameter
+        self.operator = operator
+        self.use_calibrated_value = use_calibrated_value
+        self._validate()
+
+    def __repr__(self):
+        return f"<{self.__class__.__name__} {self.referenced_parameter}{self.operator}{self.required_value}>"
+
+    def _validate(self):
+        """Validate state as logically consistent.
+
+        Returns
+        -------
+        None
+        """
+        if self.operator not in self._valid_operators:
+            raise ValueError(f"Unrecognized operator syntax {self.operator}. "
+                             f"Must be one of "
+                             f"{set(self._valid_operators.keys())}")
+
+    @classmethod
+    def from_match_criteria_xml_element(cls, element: ElementTree.Element, ns: dict) -> 'Comparison':
+        """Create
+
+        Parameters
+        ----------
+        element : ElementTree.Element
+            XML element
+        ns : dict
+            XML namespace dict
+
+        Returns
+        -------
+        : Comparison
+        """
+        use_calibrated_value = True  # Default
+        if 'useCalibratedValue' in element.attrib:
+            use_calibrated_value = element.attrib['useCalibratedValue'].lower() == 'true'
+
+        value = element.attrib['value']
+
+        parameter_name = element.attrib['parameterRef']
+        operator = '=='
+        if 'comparisonOperator' in element.attrib:
+            operator = element.attrib['comparisonOperator']
+
+        return cls(value, parameter_name, operator=operator, use_calibrated_value=use_calibrated_value)
+
+    def evaluate(self, parsed_data: dict, current_parsed_value: Optional[Union[int, float]] = None) -> bool:
+        """Evaluate comparison down to a boolean. If the parameter to compare is not present in the parsed_data dict,
+        we assume that we are comparing against the current raw value in current_parsed_value.
+
+        Parameters
+        ----------
+        parsed_data : dict
+            Dictionary of parsed parameter data so far. Used to evaluate truthyness of the match criteria.
+        current_parsed_value : Union[int, float]
+            Optional. Uncalibrated value that is currently a candidate for calibration and so has not yet been added
+            to the parsed_data dict. Used to resolve calibrator conditions that reference their own
+            raw value as a comparate.
+
+        Returns
+        -------
+        : bool
+            Truthyness of this match criteria based on parsed_data values.
+        """
+        if self.referenced_parameter in parsed_data:
+            if self.use_calibrated_value:
+                parsed_value = parsed_data[self.referenced_parameter].derived_value
+                if not parsed_value:
+                    raise ComparisonError(f"Comparison {self} was instructed to useCalibratedValue (the default)"
+                                          f"but {self.referenced_parameter} does not appear to have a derived value.")
+            else:
+                parsed_value = parsed_data[self.referenced_parameter].raw_value
+        elif current_parsed_value is not None:
+            # Assume then that the comparison is a reference to its own uncalibrated value
+            parsed_value = current_parsed_value
+            if self.use_calibrated_value:
+                warnings.warn("Performing a comparison against a current value (e.g. a Comparison within a "
+                              "context calibrator contains a reference to its own uncalibrated value but use_"
+                              "calibrated_value is set to true. This is nonsensical. Using the uncalibrated value...")
+        else:
+            raise ValueError("Attempting to resolve a Comparison expression but the referenced parameter does not "
+                             "appear in the parsed data so far and no current raw value was passed "
+                             "to compare with.")
+
+        operator = self._valid_operators[self.operator]
+        t_comparate = type(parsed_value)
+        try:
+            required_value = t_comparate(self.required_value)
+        except ValueError as err:
+            raise ComparisonError(f"Unable to coerce {self.required_value} of type {type(self.required_value)} to "
+                                  f"type {t_comparate} for comparison evaluation.") from err
+        if required_value is None or parsed_value is None:
+            raise ValueError(f"Error in Comparison. Cannot compare {required_value} with {parsed_value}. "
+                             "Neither should be None.")
+
+        # x.__le__(y) style call
+        return getattr(parsed_value, operator)(required_value)
+
+
+class Condition(MatchCriteria):
+    """<xtce:Condition>
+    Note: This xtce model doesn't actually inherit from MatchCriteria in the UML model
+    but it's functionally close enough that we inherit the class here.
+    """
+
+    def __init__(self,
+                 left_param: str,
+                 operator: str,
+                 right_param: Optional[str] = None,
+                 right_value: Optional[Any] = None,
+                 left_use_calibrated_value: bool = True,
+                 right_use_calibrated_value: bool = True):
+        """Constructor
+
+        Parameters
+        ----------
+        left_param : str
+            Parameter name on the LH side of the comparison
+        operator : str
+            Member of MatchCriteria._valid_operators.
+        right_param : Optional[str]
+            Parameter name on the RH side of the comparison.
+        right_value: Optional[Any]
+            Used in case of comparison with a fixed xtce:Value on the RH side.
+        left_use_calibrated_value : bool, Optional
+            Default is True. If False, comparison is made against the uncalibrated value.
+        right_use_calibrated_value: bool, Optional
+            Default is True. If False, comparison is made against the uncalibrated value.
+        """
+        self.left_param = left_param
+        self.right_param = right_param
+        self.right_value = right_value
+        self.operator = operator
+        self.right_use_calibrated_value = right_use_calibrated_value
+        self.left_use_calibrated_value = left_use_calibrated_value
+        self._validate()
+
+    def _validate(self):
+        """Check that the instantiated object actually makes logical sense.
+
+        Returns
+        -------
+        None
+        """
+        if self.operator not in self._valid_operators:
+            raise ValueError(f"Unrecognized operator syntax {self.operator}. "
+                             f"Must be one of "
+                             f"{set(self._valid_operators.keys())}")
+        if self.right_param and self.right_value:
+            raise ComparisonError(f"Received both a right_value and a right_param reference to Condition {self}.")
+        if self.right_value and self.right_use_calibrated_value:
+            raise ComparisonError(f"Unable to use calibrated form of a fixed value in Condition {self}.")
+
+    @staticmethod
+    def _parse_parameter_instance_ref(element: ElementTree.Element):
+        """Parse an xtce:ParameterInstanceRef element
+
+        Parameters
+        ----------
+        element: ElementTree.Element
+            xtce:ParameterInstanceRef element
+
+        Returns
+        -------
+        parameter_name: str
+            Name of referenced parameter
+        use_calibrated_value: bool
+            Whether to use the calibrated form of the referenced parameter
+        """
+        parameter_name = element.attrib['parameterRef']
+        use_calibrated_value = True  # Default
+        if 'useCalibratedValue' in element.attrib:
+            use_calibrated_value = element.attrib['useCalibratedValue'].lower() == 'true'
+        return parameter_name, use_calibrated_value
+
+    @classmethod
+    def from_match_criteria_xml_element(cls, element: ElementTree.Element, ns: dict):
+        """Classmethod to create a Condition object from an XML element.
+
+        Parameters
+        ----------
+        element : ElementTree.Element
+            XML element
+        ns : dict
+            XML namespace dict
+
+        Returns
+        -------
+        : cls
+        """
+        operator = element.find('xtce:ComparisonOperator', ns).text
+        params = element.findall('xtce:ParameterInstanceRef', ns)
+        if len(params) == 1:
+            left_param, use_calibrated_value = cls._parse_parameter_instance_ref(params[0])
+            right_value = element.find('xtce:Value', ns).text
+            return cls(left_param, operator, right_value=right_value,
+                       left_use_calibrated_value=use_calibrated_value,
+                       right_use_calibrated_value=False)
+        if len(params) == 2:
+            left_param, left_use_calibrated_value = cls._parse_parameter_instance_ref(params[0])
+            right_param, right_use_calibrated_value = cls._parse_parameter_instance_ref(params[1])
+            return cls(left_param, operator, right_param=right_param,
+                       left_use_calibrated_value=left_use_calibrated_value,
+                       right_use_calibrated_value=right_use_calibrated_value)
+        raise ValueError(f'Failed to parse a Condition element {element}. '
+                         'See 3.4.3.4.2 of XTCE Green Book CCSDS 660.1-G-2')
+
+    def evaluate(self, parsed_data: dict, current_parsed_value: Optional[Union[int, float]] = None) -> bool:
+        """Evaluate match criteria down to a boolean.
+
+        Parameters
+        ----------
+        parsed_data : dict
+            Dictionary of parsed parameter data so far. Used to evaluate truthyness of the match criteria.
+        current_parsed_value : Optional[Union[int, float]]
+            Current value being parsed. NOTE: This is currently ignored. See the TODO item below.
+
+        Returns
+        -------
+        : bool
+            Truthyness of this match criteria based on parsed_data values.
+        """
+
+        def _get_parsed_value(parameter_name: str, use_calibrated: bool):
+            """Retrieves the previously parsed value from the passed in parsed_data"""
+            try:
+                return parsed_data[parameter_name].derived_value if use_calibrated \
+                    else parsed_data[parameter_name].raw_value
+            except KeyError as e:
+                raise ComparisonError(f"Attempting to perform a Condition evaluation on {self.left_param} but "
+                                      "the referenced parameter does not appear in the hitherto parsed data passed to "
+                                      "the evaluate method. If you intended a comparison against the raw value of the "
+                                      "parameter currently being parsed, unfortunately that is not currently supported."
+                                      ) from e
+
+        # TODO: Consider allowing one of the parameters to be the parameter currently being evaluated.
+        #    This isn't explicitly provided for in the XTCE spec but it seems reasonable to be able to
+        #    perform conditionals against the current raw value of a parameter, e.g. while determining if it
+        #    should be calibrated. Note that only one of the parameters can be used this way and it must reference
+        #    an uncalibrated value so the logic and error handling must be done carefully.
+        left_value = _get_parsed_value(self.left_param, self.left_use_calibrated_value)
+        # Convert XML operator representation to a python-compatible operator (e.g. '&gt;' to '__gt__')
+        operator = self._valid_operators[self.operator]
+
+        if self.right_param is not None:
+            right_value = _get_parsed_value(self.right_param, self.right_use_calibrated_value)
+        elif self.right_value is not None:
+            t_left_param = type(left_value)  # Coerce right value xml representation to correct type
+            right_value = t_left_param(self.right_value)
+        else:
+            raise ValueError(f"Error when evaluating condition {self}. Neither right_param nor right_value is set.")
+        if left_value is None or right_value is None:
+            raise ComparisonError(f"Error comparing {left_value} and {right_value}. Neither should be None.")
+
+        # x.__le__(y) style call
+        return getattr(left_value, operator)(right_value)
+
+
+Anded = namedtuple('Anded', ['conditions', 'ors'])
+Ored = namedtuple('Ored', ['conditions', 'ands'])
+
+
+class BooleanExpression(MatchCriteria):
+    """<xtce:BooleanExpression>"""
+
+    def __init__(self, expression: Union[Condition, Anded, Ored]):
+        self.expression = expression
+
+    @classmethod
+    def from_match_criteria_xml_element(cls, element: ElementTree.Element, ns: dict) -> 'BooleanExpression':
+        """Abstract classmethod to create a match criteria object from an XML element.
+
+        Parameters
+        ----------
+        element : ElementTree.Element
+           XML element
+        ns : dict
+           XML namespace dict
+
+        Returns
+        -------
+        : BooleanExpression
+        """
+
+        def _parse_anded(anded_el: ElementTree.Element) -> Anded:
+            """Create an Anded object from an xtce:ANDedConditions element
+
+            Parameters
+            ----------
+            anded_el: ElementTree.Element
+                xtce:ANDedConditions element
+
+            Returns
+            -------
+            : Anded
+            """
+            conditions = [Condition.from_match_criteria_xml_element(el, ns)
+                          for el in anded_el.findall('xtce:Condition', ns)]
+            anded_ors = [_parse_ored(anded_or) for anded_or in anded_el.findall('xtce:ORedConditions', ns)]
+            return Anded(conditions, anded_ors)
+
+        def _parse_ored(ored_el: ElementTree.Element) -> Ored:
+            """Create an Ored object from an xtce:ARedConditions element
+
+            Parameters
+            ----------
+            ored_el: ElementTree.Element
+                xtce:ORedConditions element
+
+            Returns
+            -------
+            : Ored
+            """
+            conditions = [Condition.from_match_criteria_xml_element(el, ns)
+                          for el in ored_el.findall('xtce:Condition', ns)]
+            ored_ands = [_parse_anded(ored_and) for ored_and in ored_el.findall('xtce:ANDedConditions', ns)]
+            return Ored(conditions, ored_ands)
+
+        if element.find('xtce:Condition', ns) is not None:
+            condition = Condition.from_match_criteria_xml_element(element.find('xtce:Condition', ns), ns)
+            return cls(expression=condition)
+        if element.find('xtce:ANDedConditions', ns) is not None:
+            return cls(expression=_parse_anded(element.find('xtce:ANDedConditions', ns)))
+        if element.find('xtce:ORedConditions', ns) is not None:
+            return cls(expression=_parse_ored(element.find('xtce:ORedConditions', ns)))
+        raise ValueError(f"Failed to parse {element}")
+
+    def evaluate(self, parsed_data: dict, current_parsed_value: Optional[Union[int, float]] = None) -> bool:
+        """Evaluate the criteria in the BooleanExpression down to a single boolean.
+
+        Parameters
+        ----------
+        parsed_data : dict
+            Dictionary of parsed parameter data so far. Used to evaluate truthyness of the match criteria.
+        current_parsed_value : Optional[Union[int, float]]
+            Current value being parsed.
+
+        Returns
+        -------
+        : bool
+            Truthyness of this match criteria based on parsed_data values.
+        """
+
+        def _or(ored: Ored):
+            for condition in ored.conditions:
+                if condition.evaluate(parsed_data) is True:
+                    return True
+            for anded in ored.ands:
+                if _and(anded):
+                    return True
+            return False
+
+        def _and(anded: Anded):
+            for condition in anded.conditions:
+                if condition.evaluate(parsed_data) is False:
+                    return False
+            for ored in anded.ors:
+                if not _or(ored):
+                    return False
+            return True
+
+        if isinstance(self.expression, Condition):
+            return self.expression.evaluate(parsed_data)
+        if isinstance(self.expression, Anded):
+            return _and(self.expression)
+        if isinstance(self.expression, Ored):
+            return _or(self.expression)
+
+        raise ValueError(f"Error evaluating an unknown expression {self.expression}.")
+
+
+class DiscreteLookup(AttrComparable):
+    """<xtce:DiscreteLookup>"""
+
+    def __init__(self, match_criteria: list, lookup_value: Union[int, float]):
+        """Constructor
+
+        Parameters
+        ----------
+        match_criteria : list
+            List of criteria to determine if the lookup value should be returned during evaluation.
+        lookup_value : Union[int, float]
+            Value to return from the lookup if the criteria evaluate true
+        """
+        self.match_criteria = match_criteria
+        self.lookup_value = lookup_value
+
+    @classmethod
+    def from_discrete_lookup_xml_element(cls, element: ElementTree.Element, ns: dict) -> 'DiscreteLookup':
+        """Create a DiscreteLookup object from an <xtce:DiscreteLookup> XML element
+
+        Parameters
+        ----------
+        element : ElementTree.Element
+            <xtce:DiscreteLookup> XML element from which to parse the DiscreteLookup object.
+        ns : dict
+            Namespace dict for XML parsing
+
+        Returns
+        -------
+        : DiscreteLookup
+        """
+        lookup_value = float(element.attrib['value'])
+        if element.find('xtce:ComparisonList', ns) is not None:
+            match_criteria = [Comparison.from_match_criteria_xml_element(el, ns)
+                              for el in element.findall('xtce:ComparisonList/xtce:Comparison', ns)]
+        elif element.find('xtce:Comparison', ns) is not None:
+            match_criteria = [Comparison.from_match_criteria_xml_element(
+                element.find('xtce:Comparison', ns), ns)]
+        else:
+            raise NotImplementedError("Only Comparison and ComparisonList are implemented for DiscreteLookup.")
+
+        return cls(match_criteria, lookup_value)
+
+    def evaluate(self, parsed_data: dict, current_parsed_value: Optional[Union[int, float]] = None) -> Any:
+        """Evaluate the lookup to determine if it is valid.
+
+        Parameters
+        ----------
+        parsed_data : dict
+            Data parsed so far (for referencing during criteria evaluation).
+        current_parsed_value: Optional[Union[int, float]]
+            If referenced parameter in criterion isn't in parsed_data dict, we assume we are comparing against this
+            currently parsed value.
+
+        Returns
+        -------
+        : any
+            Return the lookup value if the match criteria evaluate true. Return None otherwise.
+        """
+        if all(criterion.evaluate(parsed_data, current_parsed_value) for criterion in self.match_criteria):
+            # If the parsed data so far satisfy all the match criteria
+            return self.lookup_value
+        return None
diff --git a/space_packet_parser/csvdef.py b/space_packet_parser/csvdef.py
deleted file mode 100644
index be1878d..0000000
--- a/space_packet_parser/csvdef.py
+++ /dev/null
@@ -1,278 +0,0 @@
-"""module for handling CSV-defined packet definitions"""
-# Standard
-import csv
-import re
-from collections import namedtuple
-from pathlib import Path
-# Local
-from space_packet_parser.xtcedef import (
-    Comparison,
-    Parameter,
-    IntegerDataEncoding,
-    FloatDataEncoding,
-    StringDataEncoding,
-    IntegerParameterType,
-    FloatParameterType,
-    StringParameterType
-)
-
-FlattenedContainer = namedtuple('FlattenedContainer', ['entry_list', 'restrictions'])
-
-
-class CsvPacketDefinition:
-    """Object representation of a space csv definition of a CCSDS packet object"""
-
-    def __init__(self, csv_def_filepath: str or Path, add_checksum=False):
-        """Instantiate an object representation of a CCSDS packet definition, from a telemetry packet
-        definition csv file. The definition for this format is not as rigorously defined anywhere to my
-        knowledge. The definition has been determined from looking at existing files and referring to
-        the following confluence page:
-        https://confluence.space.colorado.edu/display/OSWHOME/space+CSV+to+XTCE+Conversion.
-
-        Parameters
-        ----------
-        csv_def_filepath : str or Path
-            Path to csv file containing packet definition.
-        """
-
-        # TODO: Add configurable checksum length or just have the check_sum_param passed in directly?
-        if add_checksum:
-            check_sum_type = self.get_param_type_from_str('U16', 'CHECKSUM_Type')
-            self.check_sum_param = Parameter('CHECKSUM', check_sum_type)
-        else:
-            self.check_sum_param = None
-
-        self._csv_def_filepath = csv_def_filepath
-        self._csv_def = self.read_and_format_csv_file()
-        self._flattened_containers = self.gen_flattened_containers()
-
-    def read_and_format_csv_file(self):
-        """Read in csv file and generate a list of RowTuples with each entry representing one row
-        from the file. Also rename any columns to conform to the expected names if needed.
-
-        Returns
-        -------
-        : list of RowTuple
-            A list containing all the rows, in order, from the CSV definition file.
-        """
-        with open(self._csv_def_filepath, encoding='utf-8') as csv_file:
-            csv_reader = csv.DictReader(csv_file)
-            csv_reader = self.fix_column_names(csv_reader)
-            csv_def = list(csv_reader)
-
-        RowTuple = namedtuple('csv_row', csv_def[0].keys())
-
-        def get_tuple_for_row(row_dict):
-            row_tuple = RowTuple(**row_dict)
-            return row_tuple
-
-        csv_def = list(map(get_tuple_for_row, csv_def))
-        return csv_def
-
-    def fix_column_names(self, csv_reader):
-        """Checks the names of some required columns and changes them to allow for uniform processing.
-
-        Parameters
-        ----------
-        csv_reader : csv.DictReader
-            The DictReader generated from reading in the csv CCSDS definition.
-
-        Returns
-        -------
-        : csv.DictReader
-            The input DictReader with any non standard column names replaced.
-        """
-        # TODO: Unify this with the header_name_mappings kwarg used in Parser to allow the user to specify this
-        #   on the fly
-        # The definition allows the packet name to be labeled as 'Packet' or 'Container'. We will rename
-        # this column as 'Container' for consistency with the rest of the container oriented code.
-        if 'Packet' in csv_reader.fieldnames:
-            # rename packet column to container
-            csv_reader.fieldnames[csv_reader.fieldnames.index('Packet')] = 'Container'
-
-        if 'Container' not in csv_reader.fieldnames:
-            raise ValueError("According to definition the csv file must contain either Packet or Container column")
-
-        if 'Type' in csv_reader.fieldnames:
-            csv_reader.fieldnames[csv_reader.fieldnames.index('Type')] = 'DataType'
-            # rename packet column to container
-        if 'DataType' not in csv_reader.fieldnames:
-            raise ValueError("According to definition the csv file must contain either Type or DataType column")
-
-        if 'APID' not in csv_reader.fieldnames:
-            raise NotImplementedError(
-                "APID must be one of the columns in the csv file format, other variations are note yet supported")
-
-        return csv_reader
-
-    def gen_flattened_containers(self):
-        # FIXME: Recommend changing this method to a @property getter instead.
-        """Generates a dict of flattened containers from the csv definition.
-
-        Returns
-        -------
-        : dict
-            A dict of FlattenedContainer namedtuples.
-        """
-
-        container_column = [row.Container for row in self._csv_def]
-        uniq_container_names = list(dict.fromkeys(container_column))
-
-        flattened_containers = {}
-        for container_name in uniq_container_names:
-            next_container = [row for row in self._csv_def if row.Container == container_name]
-            flatten_container = self.gen_flattened_container(next_container)
-            flattened_containers[container_name] = flatten_container
-
-        return flattened_containers
-
-    def gen_flattened_container(self, container) -> FlattenedContainer:
-        """Convert the csv definition for a single container type into a FlattenedContainer
-        containing the restrictions and entry list for this container type.
-
-        Parameters
-        ----------
-        container : list of RowTuple
-            A list containing all the rows, in order, from the CSV definition
-            pertaining to a single container type.
-
-        Returns
-        -------
-        : FlattenedContainer
-            A namedtuple containing an entry list and restrictions.
-            FlattenedContainer(
-            entry_list=[Parameter, Parameter, ...],
-            restrictions={"ParameterName": value, "OtherParamName": value, ...}
-            )
-        """
-        entry_list = self.gen_entry_list(container)
-        restrictions = self.gen_restrictions(container)
-
-        return FlattenedContainer(entry_list, restrictions)
-
-    def gen_restrictions(self, container, pkt_apid_header_name='PKT_APID'):
-        """ Determines and generates a dict of restrictions for a container type.
-        Note: the only restriction currently supported is PKT_APID.
-
-        Parameters
-        ----------
-        container : dict of RowTuple
-            A list containing all the rows, in order, from the CSV definition
-            pertaining to a single container type.
-        pkt_apid_header_name : str
-            The string used in the packet header describing the APID for the CCSDS packet.
-
-        Returns
-        -------
-        : dict
-            A dict containing the restrictions for the container parameter
-        """
-        last_apid = container[0].APID
-        for row in container:
-            next_apid = row.APID
-
-            if next_apid != last_apid:
-                raise NotImplementedError('The only container restriction currently support is APID and there must be '
-                                          'a one to one correlation between Container names and APIDs')
-            last_apid = next_apid
-
-        restrictions = [
-            Comparison(required_value=last_apid, referenced_parameter=pkt_apid_header_name, use_calibrated_value=False)
-        ]
-
-        return restrictions
-
-    def gen_entry_list(self, container: list):
-        """Generates a list of Parameters for the given container. Each Parameter corresponds to one
-        telemetry item for the container.
-
-        Parameters
-        ----------
-        container : list of RowTuple
-            A list containing all the rows, in order, from the CSV definition
-            pertaining to a single container type.
-
-        Returns
-        -------
-        : list of Parameters
-            A list of Parameter objects with each Parameter corresponding to one telemetry
-            item from the container input
-        """
-        pkt_entry_list = []
-        for row in container:
-            param_type_name = row.ItemName + '_Type'
-            param_type = self.get_param_type_from_str(row.DataType, param_type_name)
-            param = Parameter(row.ItemName, param_type)
-            pkt_entry_list.append(param)
-
-        if self.check_sum_param is not None:
-            pkt_entry_list.append(self.check_sum_param)
-
-        return pkt_entry_list
-
-    @staticmethod
-    def get_param_type_from_str(dtype, param_type_name, unit=None):
-        """Determines the ParameterType to use for a given CSV data type format string.
-
-        Parameters
-        ----------
-        dtype : str
-            A string defining the data encoding of a telemetry item.
-            Examples:
-            'U8' - unsigned 8-bit integer
-            'F16' - 16-bit float
-            'C64' - 64 byte character array
-        param_type_name : str
-            Name to be given to the created ParameterType
-        unit : str or None
-            Name of the units for the created ParameterType
-
-        Returns
-        -------
-        : ParameterType
-            A ParameterType corresponding to the input variables
-        """
-
-        # All data types must be a string starting with all letters and ending with integers ie 'U12' or 'Float8'
-        split_i = re.search('[0-9]', dtype).start()
-        if split_i is None:
-            raise NotImplementedError("According to definition derived types may not specify a bit size. "
-                                      "This is not currently supported")
-
-        dtype_size = int(dtype[split_i:])
-        dtype_str = dtype[:split_i]
-
-        if dtype_str[0] == 'U':
-            encoding = IntegerDataEncoding(dtype_size, 'unsigned')
-            paramType = IntegerParameterType(name=param_type_name, encoding=encoding, unit=unit)
-        elif dtype_str[0] == 'I':
-            encoding = IntegerDataEncoding(dtype_size, 'signed')
-            paramType = IntegerParameterType(name=param_type_name, encoding=encoding, unit=unit)
-        elif dtype_str[0] == 'D':
-            encoding = IntegerDataEncoding(dtype_size, 'unsigned')  # TODO: Should this be converted to discrete values?
-            paramType = IntegerParameterType(name=param_type_name, encoding=encoding, unit=unit)
-        elif dtype_str[0] == 'F':
-            encoding = FloatDataEncoding(dtype_size)
-            paramType = FloatParameterType(name=param_type_name, encoding=encoding, unit=unit)
-        elif dtype_str[0] == 'C':
-            encoding = StringDataEncoding(fixed_length=dtype_size)
-            paramType = StringParameterType(name=param_type_name, encoding=encoding, unit=unit)
-        else:
-            raise NotImplementedError("This dtype not yet supported")
-
-        return paramType
-
-    @property
-    def flattened_containers(self):
-        """Accesses a flattened, generic representation of non-abstract packet definitions along with their
-        aggregated inheritance
-        restrictions.
-
-        Returns
-        -------
-        : dict
-            A modified form of the _sequence_container_cache, flattened out to eliminate nested sequence containers
-            and with all restriction logic aggregated together for easy comparisons.
-        """
-
-        return self._flattened_containers
diff --git a/space_packet_parser/definitions.py b/space_packet_parser/definitions.py
new file mode 100644
index 0000000..f483cc4
--- /dev/null
+++ b/space_packet_parser/definitions.py
@@ -0,0 +1,653 @@
+"""Module for parsing XTCE xml files to specify packet format"""
+# Standard
+from collections import namedtuple
+import csv
+import re
+import logging
+from pathlib import Path
+from typing import Tuple, Optional, List, TextIO, Dict
+import warnings
+# Installed
+import lxml.etree as ElementTree
+
+from space_packet_parser.exceptions import ElementNotFoundError, InvalidParameterTypeError
+from space_packet_parser import comparisons, encodings, parameters, parseables
+
+logger = logging.getLogger(__name__)
+
+
+class XtcePacketDefinition:
+    """Object representation of the XTCE definition of a CCSDS packet object"""
+
+    _tag_to_type_template = {
+        '{{{xtce}}}StringParameterType': parameters.StringParameterType,
+        '{{{xtce}}}IntegerParameterType': parameters.IntegerParameterType,
+        '{{{xtce}}}FloatParameterType': parameters.FloatParameterType,
+        '{{{xtce}}}EnumeratedParameterType': parameters.EnumeratedParameterType,
+        '{{{xtce}}}BinaryParameterType': parameters.BinaryParameterType,
+        '{{{xtce}}}BooleanParameterType': parameters.BooleanParameterType,
+        '{{{xtce}}}AbsoluteTimeParameterType': parameters.AbsoluteTimeParameterType,
+        '{{{xtce}}}RelativeTimeParameterType': parameters.RelativeTimeParameterType,
+    }
+
+    def __init__(self, xtce_document: TextIO, ns: Optional[dict] = None):
+        """Instantiate an object representation of a CCSDS packet definition, according to a format specified in an XTCE
+        XML document. The parser iteratively builds sequences of parameters according to the
+        SequenceContainers specified in the XML document's ContainerSet element. The notions of container inheritance
+        (via BaseContainer) and nested container (by including a SequenceContainer within a SequenceContainer) are
+        supported. Exclusion of containers based on topLevelPacket in AncillaryData is not supported, so all
+        containers are examined and returned.
+
+        Parameters
+        ----------
+        xtce_document : TextIO
+            Path to XTCE XML document containing packet definition.
+        ns : Optional[dict]
+            Optional different namespace than the default xtce namespace.
+        """
+        self._sequence_container_cache = {}  # Lookup for parsed sequence container objects
+        self._parameter_cache = {}  # Lookup for parsed parameter objects
+        self._parameter_type_cache = {}  # Lookup for parsed parameter type objects
+        self.tree = ElementTree.parse(xtce_document)
+        self.ns = ns or self.tree.getroot().nsmap
+        self.type_tag_to_object = {k.format(**self.ns): v for k, v in
+                                   self._tag_to_type_template.items()}
+
+        self._populate_sequence_container_cache()
+
+    def __getitem__(self, item):
+        return self._sequence_container_cache[item]
+
+    def _populate_sequence_container_cache(self):
+        """Force populating sequence_container_cache by parsing all SequenceContainers"""
+        for sequence_container in self.container_set.iterfind('xtce:SequenceContainer', self.ns):
+            self._sequence_container_cache[
+                sequence_container.attrib['name']
+            ] = self.parse_sequence_container_contents(sequence_container)
+
+        # Back-populate the list of inheritors for each container
+        for name, sc in self._sequence_container_cache.items():
+            if sc.base_container_name:
+                self._sequence_container_cache[sc.base_container_name].inheritors.append(name)
+
+    def parse_sequence_container_contents(self,
+                                          sequence_container: ElementTree.Element) -> parseables.SequenceContainer:
+        """Parses the list of parameters in a SequenceContainer element, recursively parsing nested SequenceContainers
+        to build an entry list of parameters that flattens the nested structure to derive a sequential ordering of
+        expected parameters for each SequenceContainer. Note that this also stores entry lists for containers that are
+        not intended to stand alone.
+
+        Parameters
+        ----------
+        sequence_container : ElementTree.Element
+            The SequenceContainer element to parse.
+
+        Returns
+        -------
+        : SequenceContainer
+            SequenceContainer containing an entry_list of SequenceContainers and Parameters
+            in the order expected in a packet.
+        """
+        entry_list = []  # List to house Parameters for the current SequenceContainer
+        try:
+            base_container, restriction_criteria = self._get_container_base_container(sequence_container)
+            base_sequence_container = self.parse_sequence_container_contents(base_container)
+            base_container_name = base_sequence_container.name
+        except ElementNotFoundError:
+            base_container_name = None
+            restriction_criteria = None
+
+        container_contents = sequence_container.find('xtce:EntryList', self.ns).findall('*', self.ns)
+
+        for entry in container_contents:
+            if entry.tag == '{{{xtce}}}ParameterRefEntry'.format(**self.ns):  # pylint: disable=consider-using-f-string
+                parameter_name = entry.attrib['parameterRef']
+
+                # If we've already parsed this parameter in a different container
+                if parameter_name in self._parameter_cache:
+                    entry_list.append(self._parameter_cache[parameter_name])
+                else:
+                    parameter_element = self._find_parameter(parameter_name)
+                    parameter_type_name = parameter_element.attrib['parameterTypeRef']
+
+                    # If we've already parsed this parameter type for a different parameter
+                    if parameter_type_name in self._parameter_type_cache:
+                        parameter_type_object = self._parameter_type_cache[parameter_type_name]
+                    else:
+                        parameter_type_element = self._find_parameter_type(parameter_type_name)
+                        try:
+                            parameter_type_class = self.type_tag_to_object[parameter_type_element.tag]
+                        except KeyError as e:
+                            if (
+                                    "ArrayParameterType" in parameter_type_element.tag or
+                                    "AggregateParameterType" in parameter_type_element.tag
+                            ):
+                                raise NotImplementedError(f"Unsupported parameter type {parameter_type_element.tag}. "
+                                                          "Supporting this parameter type is in the roadmap but has "
+                                                          "not yet been implemented.") from e
+                            raise InvalidParameterTypeError(f"Invalid parameter type {parameter_type_element.tag}. "
+                                                            "If you believe this is a valid XTCE parameter type, "
+                                                            "please open a feature request as a Github issue with a "
+                                                            "reference to the XTCE element description for the "
+                                                            "parameter type element.") from e
+                        parameter_type_object = parameter_type_class.from_parameter_type_xml_element(
+                            parameter_type_element, self.ns)
+                        self._parameter_type_cache[parameter_type_name] = parameter_type_object  # Add to cache
+
+                    parameter_short_description = parameter_element.attrib['shortDescription'] if (
+                        'shortDescription' in parameter_element.attrib
+                    ) else None
+                    parameter_long_description = parameter_element.find('xtce:LongDescription', self.ns).text if (
+                        parameter_element.find('xtce:LongDescription', self.ns) is not None
+                    ) else None
+
+                    parameter_object = parameters.Parameter(
+                        name=parameter_name,
+                        parameter_type=parameter_type_object,
+                        short_description=parameter_short_description,
+                        long_description=parameter_long_description
+                    )
+                    entry_list.append(parameter_object)
+                    self._parameter_cache[parameter_name] = parameter_object  # Add to cache
+            elif entry.tag == '{{{xtce}}}ContainerRefEntry'.format(  # pylint: disable=consider-using-f-string
+                    **self.ns):
+                nested_container = self._find_container(name=entry.attrib['containerRef'])
+                entry_list.append(self.parse_sequence_container_contents(nested_container))
+
+        short_description = sequence_container.attrib['shortDescription'] if (
+            'shortDescription' in sequence_container.attrib
+        ) else None
+        long_description = sequence_container.find('xtce:LongDescription', self.ns).text if (
+            sequence_container.find('xtce:LongDescription', self.ns) is not None
+        ) else None
+
+        return parseables.SequenceContainer(name=sequence_container.attrib['name'],
+                                            entry_list=entry_list,
+                                            base_container_name=base_container_name,
+                                            restriction_criteria=restriction_criteria,
+                                            abstract=self._is_abstract_container(sequence_container),
+                                            short_description=short_description,
+                                            long_description=long_description)
+
+    @property
+    def named_containers(self) -> Dict[str, parseables.SequenceContainer]:
+        """Property accessor that returns the dict cache of SequenceContainer objects"""
+        return self._sequence_container_cache
+
+    @property
+    def named_parameters(self) -> Dict[str, parameters.Parameter]:
+        """Property accessor that returns the dict cache of Parameter objects"""
+        return self._parameter_cache
+
+    @property
+    def named_parameter_types(self) -> Dict[str, parameters.ParameterType]:
+        """Property accessor that returns the dict cache of ParameterType objects"""
+        return self._parameter_type_cache
+
+    # DEPRECATED! This is only used by CSV-parser code. Remove for 5.0.0 release
+    @property
+    def flattened_containers(self):
+        """Accesses a flattened, generic representation of non-abstract packet definitions along with their
+        aggregated inheritance
+        restrictions.
+
+        Returns
+        -------
+        : dict
+            A modified form of the _sequence_container_cache, flattened out to eliminate nested sequence containers
+            and with all restriction logic aggregated together for easy comparisons.
+            {
+            "PacketNameA": {
+            FlattenedContainer(
+            entry_list=[Parameter, Parameter, ...],
+            restrictions={"ParameterName": value, "OtherParamName": value, ...}
+            )
+            },
+            "PacketNameB": {
+            FlattenedContainer(
+            entry_list=[Parameter, Parameter, ...],
+            restrictions={"ParameterName": value, "OtherParamName": value, ...}
+            )
+            }, ...
+            }
+        """
+
+        def flatten_container(sequence_container: parseables.SequenceContainer):
+            """Flattens the representation of a SequenceContainer object into a list of Parameters (in order) and
+            an aggregated dictionary of restriction criteria where the keys are Parameter names and the values are the
+            required values of those parameters in order to adopt the SequenceContainer's definition.
+
+            Parameters
+            ----------
+            sequence_container : SequenceContainer
+                SequenceContainer object to flatten, recursively.
+
+            Returns
+            -------
+            : list
+                List of Parameters, in order.
+            : dict
+                Dictionary of required Parameter values in order to use this definition.
+            """
+            aggregated_entry_list = []
+            aggregated_restrictions = []
+            for entry in sequence_container.entry_list:
+                if isinstance(entry, parseables.SequenceContainer):
+                    if entry.restriction_criteria:
+                        aggregated_restrictions += entry.restriction_criteria
+                    entry_list, restrictions = flatten_container(entry)
+                    aggregated_entry_list += entry_list
+                    aggregated_restrictions += restrictions
+                elif isinstance(entry, parameters.Parameter):
+                    aggregated_entry_list.append(entry)
+            return aggregated_entry_list, aggregated_restrictions
+
+        warnings.warn("The 'flattened_containers' property is deprecated to allow for dynamic container "
+                      "inheritance matching during parsing.", DeprecationWarning)
+        return {
+            name: parseables.FlattenedContainer(*flatten_container(sc))
+            for name, sc in self._sequence_container_cache.items()
+            if not sc.abstract
+        }
+
+    @property
+    def container_set(self) -> ElementTree.Element:
+        """Property that returns the <xtce:ContainerSet> element, containing all the sequence container elements."""
+        return self.tree.getroot().find('xtce:TelemetryMetaData/xtce:ContainerSet', self.ns)
+
+    @property
+    def parameter_type_set(self) -> ElementTree.Element:
+        """Property that returns the <xtce:ParameterTypeSet> element, containing all parameter type elements."""
+        return self.tree.getroot().find('xtce:TelemetryMetaData/xtce:ParameterTypeSet', self.ns)
+
+    @property
+    def parameter_set(self) -> ElementTree.Element:
+        """Property that returns the <xtce:ParameterSet> element, containing all parameter elements."""
+        return self.tree.getroot().find('xtce:TelemetryMetaData/xtce:ParameterSet', self.ns)
+
+    @staticmethod
+    def _is_abstract_container(container_element: ElementTree.Element) -> bool:
+        """Determine in a SequenceContainer element is abstract
+
+        Parameters
+        ----------
+        container_element : ElementTree.Element
+            SequenceContainer element to examine
+
+        Returns
+        -------
+        : bool
+            True if SequenceContainer element has the attribute abstract=true. False otherwise.
+        """
+        if 'abstract' in container_element.attrib:
+            return container_element.attrib['abstract'].lower() == 'true'
+        return False
+
+    def _find_container(self, name: str) -> ElementTree.Element:
+        """Finds an XTCE container <xtce:SequenceContainer> by name.
+
+        Parameters
+        ----------
+        name : str
+            Name of the container to find
+
+        Returns
+        -------
+        : ElementTree.Element
+        """
+        containers = self.container_set.findall(f"./xtce:SequenceContainer[@name='{name}']", self.ns)
+        assert len(containers) == 1, f"Found {len(containers)} matching container_set with name {name}. " \
+                                     f"Container names are expected to exist and be unique."
+        return containers[0]
+
+    def _find_parameter(self, name: str) -> ElementTree.Element:
+        """Finds an XTCE Parameter in the tree.
+
+        Parameters
+        ----------
+        name : str
+            Name of the parameter to find
+
+        Returns
+        -------
+        : ElementTree.Element
+        """
+        params = self.parameter_set.findall(f"./xtce:Parameter[@name='{name}']", self.ns)
+        assert len(params) == 1, f"Found {len(params)} matching parameters with name {name}. " \
+                                 f"Parameter names are expected to exist and be unique."
+        return params[0]
+
+    def _find_parameter_type(self, name: str) -> ElementTree.Element:
+        """Finds an XTCE ParameterType in the tree.
+
+        Parameters
+        ----------
+        name : str
+            Name of the parameter type to find
+
+        Returns
+        -------
+        : ElementTree.Element
+        """
+        param_types = self.parameter_type_set.findall(f"./*[@name='{name}']", self.ns)
+        assert len(param_types) == 1, f"Found {len(param_types)} matching parameter types with name {name}. " \
+                                      f"Parameter type names are expected to exist and be unique."
+        return param_types[0]
+
+    def _get_container_base_container(
+            self,
+            container_element: ElementTree.Element) -> Tuple[ElementTree.Element, List[comparisons.MatchCriteria]]:
+        """Examines the container_element and returns information about its inheritance.
+
+        Parameters
+        ----------
+        container_element : ElementTree.Element
+            The container element for which to find its base container.
+
+        Returns
+        -------
+        : ElementTree.Element
+            The base container element of the input container_element.
+        : list
+            The restriction criteria for the inheritance.
+        """
+        base_container_element = container_element.find('xtce:BaseContainer', self.ns)
+        if base_container_element is None:
+            raise ElementNotFoundError(
+                f"Container element {container_element} does not have a BaseContainer child element.")
+
+        restriction_criteria_element = base_container_element.find('xtce:RestrictionCriteria', self.ns)
+        if restriction_criteria_element is not None:
+            comparison_list_element = restriction_criteria_element.find('xtce:ComparisonList', self.ns)
+            single_comparison_element = restriction_criteria_element.find('xtce:Comparison', self.ns)
+            boolean_expression_element = restriction_criteria_element.find('xtce:BooleanExpression', self.ns)
+            custom_algorithm_element = restriction_criteria_element.find('xtce:CustomAlgorithm', self.ns)
+            if custom_algorithm_element is not None:
+                raise NotImplementedError("Detected a CustomAlgorithm in a RestrictionCriteria element. "
+                                          "This is not implemented.")
+
+            if comparison_list_element is not None:
+                comparison_items = comparison_list_element.findall('xtce:Comparison', self.ns)
+                restrictions = [
+                    comparisons.Comparison.from_match_criteria_xml_element(comp, self.ns) for comp in comparison_items]
+            elif single_comparison_element is not None:
+                restrictions = [
+                    comparisons.Comparison.from_match_criteria_xml_element(single_comparison_element, self.ns)]
+            elif boolean_expression_element is not None:
+                restrictions = [
+                    comparisons.BooleanExpression.from_match_criteria_xml_element(boolean_expression_element, self.ns)]
+            else:
+                raise ValueError("Detected a RestrictionCriteria element containing no "
+                                 "Comparison, ComparisonList, BooleanExpression or CustomAlgorithm.")
+            # TODO: Implement NextContainer support inside RestrictionCriteria. This might make the parser much
+            #    more complicated.
+        else:
+            restrictions = []
+        return self._find_container(base_container_element.attrib['containerRef']), restrictions
+
+
+FlattenedContainer = namedtuple('FlattenedContainer', ['entry_list', 'restrictions'])
+
+
+class CsvPacketDefinition:
+    """Object representation of a space csv definition of a CCSDS packet object"""
+
+    def __init__(self, csv_def_filepath: str or Path, add_checksum=False):
+        """Instantiate an object representation of a CCSDS packet definition, from a telemetry packet
+        definition csv file. The definition for this format is not as rigorously defined anywhere to my
+        knowledge. The definition has been determined from looking at existing files and referring to
+        the following confluence page:
+        https://confluence.space.colorado.edu/display/OSWHOME/space+CSV+to+XTCE+Conversion.
+
+        Parameters
+        ----------
+        csv_def_filepath : str or Path
+            Path to csv file containing packet definition.
+        """
+
+        # TODO: Add configurable checksum length or just have the check_sum_param passed in directly?
+        if add_checksum:
+            check_sum_type = self.get_param_type_from_str('U16', 'CHECKSUM_Type')
+            self.check_sum_param = parameters.Parameter('CHECKSUM', check_sum_type)
+        else:
+            self.check_sum_param = None
+
+        self._csv_def_filepath = csv_def_filepath
+        self._csv_def = self.read_and_format_csv_file()
+        self._flattened_containers = self.gen_flattened_containers()
+
+    def read_and_format_csv_file(self):
+        """Read in csv file and generate a list of RowTuples with each entry representing one row
+        from the file. Also rename any columns to conform to the expected names if needed.
+
+        Returns
+        -------
+        : list of RowTuple
+            A list containing all the rows, in order, from the CSV definition file.
+        """
+        with open(self._csv_def_filepath, encoding='utf-8') as csv_file:
+            csv_reader = csv.DictReader(csv_file)
+            csv_reader = self.fix_column_names(csv_reader)
+            csv_def = list(csv_reader)
+
+        RowTuple = namedtuple('csv_row', csv_def[0].keys())
+
+        def get_tuple_for_row(row_dict):
+            row_tuple = RowTuple(**row_dict)
+            return row_tuple
+
+        csv_def = list(map(get_tuple_for_row, csv_def))
+        return csv_def
+
+    def fix_column_names(self, csv_reader):
+        """Checks the names of some required columns and changes them to allow for uniform processing.
+
+        Parameters
+        ----------
+        csv_reader : csv.DictReader
+            The DictReader generated from reading in the csv CCSDS definition.
+
+        Returns
+        -------
+        : csv.DictReader
+            The input DictReader with any non standard column names replaced.
+        """
+        # TODO: Unify this with the header_name_mappings kwarg used in Parser to allow the user to specify this
+        #   on the fly
+        # The definition allows the packet name to be labeled as 'Packet' or 'Container'. We will rename
+        # this column as 'Container' for consistency with the rest of the container oriented code.
+        if 'Packet' in csv_reader.fieldnames:
+            # rename packet column to container
+            csv_reader.fieldnames[csv_reader.fieldnames.index('Packet')] = 'Container'
+
+        if 'Container' not in csv_reader.fieldnames:
+            raise ValueError("According to definition the csv file must contain either Packet or Container column")
+
+        if 'Type' in csv_reader.fieldnames:
+            csv_reader.fieldnames[csv_reader.fieldnames.index('Type')] = 'DataType'
+            # rename packet column to container
+        if 'DataType' not in csv_reader.fieldnames:
+            raise ValueError("According to definition the csv file must contain either Type or DataType column")
+
+        if 'APID' not in csv_reader.fieldnames:
+            raise NotImplementedError(
+                "APID must be one of the columns in the csv file format, other variations are note yet supported")
+
+        return csv_reader
+
+    def gen_flattened_containers(self):
+        # FIXME: Recommend changing this method to a @property getter instead.
+        """Generates a dict of flattened containers from the csv definition.
+
+        Returns
+        -------
+        : dict
+            A dict of FlattenedContainer namedtuples.
+        """
+
+        container_column = [row.Container for row in self._csv_def]
+        uniq_container_names = list(dict.fromkeys(container_column))
+
+        flattened_containers = {}
+        for container_name in uniq_container_names:
+            next_container = [row for row in self._csv_def if row.Container == container_name]
+            flatten_container = self.gen_flattened_container(next_container)
+            flattened_containers[container_name] = flatten_container
+
+        return flattened_containers
+
+    def gen_flattened_container(self, container) -> FlattenedContainer:
+        """Convert the csv definition for a single container type into a FlattenedContainer
+        containing the restrictions and entry list for this container type.
+
+        Parameters
+        ----------
+        container : list of RowTuple
+            A list containing all the rows, in order, from the CSV definition
+            pertaining to a single container type.
+
+        Returns
+        -------
+        : FlattenedContainer
+            A namedtuple containing an entry list and restrictions.
+            FlattenedContainer(
+            entry_list=[Parameter, Parameter, ...],
+            restrictions={"ParameterName": value, "OtherParamName": value, ...}
+            )
+        """
+        entry_list = self.gen_entry_list(container)
+        restrictions = self.gen_restrictions(container)
+
+        return FlattenedContainer(entry_list, restrictions)
+
+    def gen_restrictions(self, container, pkt_apid_header_name='PKT_APID'):
+        """ Determines and generates a dict of restrictions for a container type.
+        Note: the only restriction currently supported is PKT_APID.
+
+        Parameters
+        ----------
+        container : dict of RowTuple
+            A list containing all the rows, in order, from the CSV definition
+            pertaining to a single container type.
+        pkt_apid_header_name : str
+            The string used in the packet header describing the APID for the CCSDS packet.
+
+        Returns
+        -------
+        : dict
+            A dict containing the restrictions for the container parameter
+        """
+        last_apid = container[0].APID
+        for row in container:
+            next_apid = row.APID
+
+            if next_apid != last_apid:
+                raise NotImplementedError('The only container restriction currently support is APID and there must be '
+                                          'a one to one correlation between Container names and APIDs')
+            last_apid = next_apid
+
+        restrictions = [
+            comparisons.Comparison(required_value=last_apid,
+                                   referenced_parameter=pkt_apid_header_name,
+                                   use_calibrated_value=False)
+        ]
+
+        return restrictions
+
+    def gen_entry_list(self, container: list):
+        """Generates a list of Parameters for the given container. Each Parameter corresponds to one
+        telemetry item for the container.
+
+        Parameters
+        ----------
+        container : list of RowTuple
+            A list containing all the rows, in order, from the CSV definition
+            pertaining to a single container type.
+
+        Returns
+        -------
+        : list of Parameters
+            A list of Parameter objects with each Parameter corresponding to one telemetry
+            item from the container input
+        """
+        pkt_entry_list = []
+        for row in container:
+            param_type_name = row.ItemName + '_Type'
+            param_type = self.get_param_type_from_str(row.DataType, param_type_name)
+            param = parameters.Parameter(row.ItemName, param_type)
+            pkt_entry_list.append(param)
+
+        if self.check_sum_param is not None:
+            pkt_entry_list.append(self.check_sum_param)
+
+        return pkt_entry_list
+
+    @staticmethod
+    def get_param_type_from_str(dtype, param_type_name, unit=None):
+        """Determines the ParameterType to use for a given CSV data type format string.
+
+        Parameters
+        ----------
+        dtype : str
+            A string defining the data encoding of a telemetry item.
+            Examples:
+            'U8' - unsigned 8-bit integer
+            'F16' - 16-bit float
+            'C64' - 64 byte character array
+        param_type_name : str
+            Name to be given to the created ParameterType
+        unit : str or None
+            Name of the units for the created ParameterType
+
+        Returns
+        -------
+        : ParameterType
+            A ParameterType corresponding to the input variables
+        """
+
+        # All data types must be a string starting with all letters and ending with integers ie 'U12' or 'Float8'
+        split_i = re.search('[0-9]', dtype).start()
+        if split_i is None:
+            raise NotImplementedError("According to definition derived types may not specify a bit size. "
+                                      "This is not currently supported")
+
+        dtype_size = int(dtype[split_i:])
+        dtype_str = dtype[:split_i]
+
+        if dtype_str[0] == 'U':
+            encoding = encodings.IntegerDataEncoding(dtype_size, 'unsigned')
+            paramType = parameters.IntegerParameterType(name=param_type_name, encoding=encoding, unit=unit)
+        elif dtype_str[0] == 'I':
+            encoding = encodings.IntegerDataEncoding(dtype_size, 'signed')
+            paramType = parameters.IntegerParameterType(name=param_type_name,
+                                                        encoding=encoding,
+                                                        unit=unit)
+        elif dtype_str[0] == 'D':
+            # TODO: Should this be converted to discrete values?
+            encoding = encodings.IntegerDataEncoding(dtype_size, 'unsigned')
+            paramType = parameters.IntegerParameterType(name=param_type_name, encoding=encoding, unit=unit)
+        elif dtype_str[0] == 'F':
+            encoding = encodings.FloatDataEncoding(dtype_size)
+            paramType = parameters.FloatParameterType(name=param_type_name, encoding=encoding, unit=unit)
+        elif dtype_str[0] == 'C':
+            encoding = encodings.StringDataEncoding(fixed_length=dtype_size)
+            paramType = parameters.StringParameterType(name=param_type_name, encoding=encoding, unit=unit)
+        else:
+            raise NotImplementedError("This dtype not yet supported")
+
+        return paramType
+
+    @property
+    def flattened_containers(self):
+        """Accesses a flattened, generic representation of non-abstract packet definitions along with their
+        aggregated inheritance
+        restrictions.
+
+        Returns
+        -------
+        : dict
+            A modified form of the _sequence_container_cache, flattened out to eliminate nested sequence containers
+            and with all restriction logic aggregated together for easy comparisons.
+        """
+
+        return self._flattened_containers
diff --git a/space_packet_parser/encodings.py b/space_packet_parser/encodings.py
new file mode 100644
index 0000000..4dcdeca
--- /dev/null
+++ b/space_packet_parser/encodings.py
@@ -0,0 +1,796 @@
+"""DataEncoding definitions"""
+from abc import ABCMeta, abstractmethod
+import logging
+import struct
+from typing import Any, List, Optional, Tuple, Union
+import warnings
+
+import lxml.etree as ElementTree
+
+from space_packet_parser.exceptions import ElementNotFoundError
+from space_packet_parser import calibrators, comparisons, parseables
+
+
+logger = logging.getLogger(__name__)
+
+
+class DataEncoding(comparisons.AttrComparable, metaclass=ABCMeta):
+    """Abstract base class for XTCE data encodings"""
+
+    @classmethod
+    @abstractmethod
+    def from_data_encoding_xml_element(cls, element: ElementTree.Element, ns: dict) -> 'DataEncoding':
+        """Abstract classmethod to create a data encoding object from an XML element.
+
+        Parameters
+        ----------
+        element : ElementTree.Element
+            XML element
+        ns : dict
+            XML namespace dict
+
+        Returns
+        -------
+        : DataEncoding
+        """
+        return NotImplemented
+
+    @staticmethod
+    def get_default_calibrator(data_encoding_element: ElementTree.Element,
+                               ns: dict) -> Union[calibrators.Calibrator, None]:
+        """Gets the default_calibrator for the data encoding element
+
+        Parameters
+        ----------
+        data_encoding_element : ElementTree.Element
+            The data encoding element which should contain the default_calibrator
+        ns : dict
+            XML namespace dict
+
+        Returns
+        -------
+        : Union[Calibrator, None]
+        """
+        for calibrator in [calibrators.SplineCalibrator,
+                           calibrators.PolynomialCalibrator,
+                           calibrators.MathOperationCalibrator]:
+            # Try to find each type of data encoding element. If we find one, we assume it's the only one.
+            element = data_encoding_element.find(f"xtce:DefaultCalibrator/xtce:{calibrator.__name__}", ns)
+            if element is not None:
+                return calibrator.from_calibrator_xml_element(element, ns)
+        return None
+
+    @staticmethod
+    def get_context_calibrators(
+            data_encoding_element: ElementTree.Element, ns: dict) -> Union[List[calibrators.ContextCalibrator], None]:
+        """Get the context default_calibrator(s) for the data encoding element
+
+        Parameters
+        ----------
+        data_encoding_element : ElementTree.Element
+            XML element
+        ns : dict
+            XML namespace dict
+
+        Returns
+        -------
+        : Union[List[ContextCalibrator], None]
+            List of ContextCalibrator objects or None if there are no context calibrators
+        """
+        if data_encoding_element.find('xtce:ContextCalibratorList', ns):
+            context_calibrators_elements = data_encoding_element.findall(
+                'xtce:ContextCalibratorList/xtce:ContextCalibrator', ns)
+            return [calibrators.ContextCalibrator.from_context_calibrator_xml_element(el, ns)
+                    for el in context_calibrators_elements]
+        return None
+
+    @staticmethod
+    def _get_linear_adjuster(parent_element: ElementTree.Element, ns: dict) -> Union[callable, None]:
+        """Examine a parent (e.g. a <xtce:DynamicValue>) element and find a LinearAdjustment if present,
+        creating and returning a function that evaluates the adjustment.
+
+        Parameters
+        ----------
+        parent_element : ElementTree.Element
+            Parent element which may contain a LinearAdjustment
+        ns : dict
+            XML namespace dict
+
+        Returns
+        -------
+        adjuster : Union[callable, None]
+            Function object that adjusts a SizeInBits value by a linear function or None if no adjuster present
+        """
+        linear_adjustment_element = parent_element.find('xtce:LinearAdjustment', ns)
+        if linear_adjustment_element is not None:
+            slope = (int(linear_adjustment_element.attrib['slope'])
+                     if 'slope' in linear_adjustment_element.attrib else 0)
+            intercept = (int(linear_adjustment_element.attrib['intercept'])
+                         if 'intercept' in linear_adjustment_element.attrib else 0)
+
+            def adjuster(x: int) -> int:
+                """Perform a linear adjustment to a size parameter
+
+                Parameters
+                ----------
+                x : int
+                    Unadjusted size parameter.
+
+                Returns
+                -------
+                : int
+                    Adjusted size parameter
+                """
+                adjusted = (slope * float(x)) + intercept
+                if not adjusted.is_integer():
+                    raise ValueError(f"Error when adjusting a value with a LinearAdjustment. Got y=mx + b as "
+                                     f"{adjusted}={slope}*{x}+{intercept} returned a float. "
+                                     f"Should have been an int.")
+                return int(adjusted)
+
+            return adjuster
+        return None
+
+    def _calculate_size(self, packet: parseables.Packet) -> int:
+        """Calculate the size of the data item in bits.
+
+        Parameters
+        ----------
+        packet: Packet
+            Binary representation of the packet used to get the coming bits and any
+            previously parsed data items to infer field lengths.
+
+        Returns
+        -------
+        : int
+            Size of the data item in bits.
+        """
+        raise NotImplementedError()
+
+    def parse_value(self, packet: parseables.Packet, **kwargs) -> Tuple[Any, Any]:
+        """Parse a value from packet data, possibly using previously parsed data items to inform parsing.
+
+        Parameters
+        ----------
+        packet: Packet
+            Binary representation of the packet used to get the coming bits and any
+            previously parsed data items to infer field lengths.
+        Returns
+        -------
+        : any
+            Raw value
+        : any
+            Calibrated value
+        """
+        raise NotImplementedError()
+
+
+class StringDataEncoding(DataEncoding):
+    """<xtce:StringDataEncoding>"""
+
+    _supported_encodings = ('US-ASCII', 'ISO-8859-1', 'Windows-1252', 'UTF-8', 'UTF-16',
+                            'UTF-16LE', 'UTF-16BE', 'UTF-32', 'UTF-32LE', 'UTF-32BE')
+
+    def __init__(self, encoding: str = 'UTF-8',
+                 byte_order: Optional[str] = None,
+                 termination_character: Optional[str] = None,
+                 fixed_length: Optional[int] = None,
+                 leading_length_size: Optional[int] = None,
+                 dynamic_length_reference: Optional[str] = None,
+                 use_calibrated_value: Optional[bool] = True,
+                 discrete_lookup_length: Optional[List[comparisons.DiscreteLookup]] = None,
+                 length_linear_adjuster: Optional[callable] = None):
+        # pylint: disable=pointless-statement
+        f"""Constructor
+        Only one of termination_character, fixed_length, or leading_length_size should be set. Setting more than one
+        is nonsensical.
+
+        Parameters
+        ----------
+        encoding : str
+            One of the XTCE-supported encodings: {self._supported_encodings}
+            Describes how to read the characters in the string.
+            Default is UTF-8.
+        byte_order : Optional[str]
+            Description of the byte order, used for multi-byte character encodings where the endianness cannot be
+            determined from the encoding specifier. Can be None if encoding is single-byte or UTF-*BE/UTF-*LE.
+        termination_character : Optional[str]
+            A single hexadecimal character, represented as a string. Must be encoded in the same encoding as the string
+            itself. For example, for a utf-8 encoded string, the hex string must be two hex characters (one byte).
+            For a UTF-16* encoded string, the hex representation of the termination character must be four characters
+            (two bytes).
+        fixed_length : Optional[int]
+            Fixed length of the string, in bits.
+        leading_length_size : Optional[int]
+            Fixed size in bits of a leading field that contains the length of the subsequent string.
+        dynamic_length_reference : Optional[str]
+            Name of referenced parameter for dynamic length, in bits. May be combined with a linear_adjuster
+        use_calibrated_value: Optional[bool]
+            Whether to use the calibrated value on the referenced parameter in dynamic_length_reference.
+            Default is True.
+        discrete_lookup_length : Optional[List[DiscreteLookup]]
+            List of DiscreteLookup objects with which to determine string length from another parameter.
+        length_linear_adjuster : Optional[callable]
+            Function that linearly adjusts a size. e.g. if the size reference parameter gives a length in bytes, the
+            linear adjuster should multiply by 8 to give the size in bits.
+        """
+        if encoding not in self._supported_encodings:
+            raise ValueError(f"Got encoding={encoding} (uppercased). "
+                             f"Encoding must be one of {self._supported_encodings}.")
+        self.encoding = encoding
+        if encoding not in ['US-ASCII', 'ISO-8859-1', 'Windows-1252', 'UTF-8']:  # for these, byte order doesn't matter
+            if byte_order is None:
+                if "LE" in encoding:
+                    self.byte_order = "leastSignificantByteFirst"
+                elif "BE" in encoding:
+                    self.byte_order = "mostSignificantByteFirst"
+                else:
+                    raise ValueError("Byte order must be specified for multi-byte character encodings.")
+            else:
+                self.byte_order = byte_order
+        self.termination_character = termination_character
+        if termination_character:
+            # Always in hex, per 4.3.2.2.5.5.4 of XTCE spec
+            self.termination_character = bytes.fromhex(termination_character)
+            # Check that the termination character is a single character in the specified encoding
+            # e.g. b'\x58' in utf-8 is "X"
+            # b'\x21\00' in utf-16-le is "!"
+            # b'\x00\x21' in utf-16-be is "!"
+            if len(self.termination_character.decode(encoding)) != 1:
+                raise ValueError(f"Termination character {termination_character} appears to be malformed. "
+                                 f"Expected a hex string representation of a single character, e.g. '58' for "
+                                 f"character 'X' in utf-8 or '5800' for character 'X' in utf-16-le. Note that "
+                                 f"variable-width encoding is not yet supported in any encoding.")
+        self.fixed_length = fixed_length
+        self.leading_length_size = leading_length_size
+        self.dynamic_length_reference = dynamic_length_reference
+        self.use_calibrated_value = use_calibrated_value
+        self.discrete_lookup_length = discrete_lookup_length
+        self.length_linear_adjuster = length_linear_adjuster
+
+    def _calculate_size(self, packet: parseables.Packet) -> int:
+        """Calculate the length of the string data item in bits.
+
+        Parameters
+        ----------
+        packet: Packet
+            Binary representation of the packet used to get the coming bits and any
+            previously parsed data items to infer field lengths.
+
+        Returns
+        -------
+        : int
+            Number of bits in the string data item
+        """
+        # pylint: disable=too-many-branches
+        if self.fixed_length:
+            strlen_bits = self.fixed_length
+        elif self.leading_length_size is not None:  # strlen_bits is determined from a preceding integer
+            strlen_bits = packet.read_as_int(self.leading_length_size)
+            if strlen_bits % 8 != 0:
+                warnings.warn(f"String length (in bits) is {strlen_bits}, which is not a multiple of 8. "
+                              f"This likely means something is wrong since strings are expected to be integer numbers "
+                              f"of bytes.")
+        elif self.discrete_lookup_length is not None:
+            for discrete_lookup in self.discrete_lookup_length:
+                strlen_bits = discrete_lookup.evaluate(packet.parsed_data)
+                if strlen_bits is not None:
+                    break
+            else:
+                raise ValueError('List of discrete lookup values being used for determining length of '
+                                 f'string {self} found no matches based on {packet}.')
+        elif self.dynamic_length_reference is not None:
+            if self.use_calibrated_value is True:
+                strlen_bits = packet.parsed_data[self.dynamic_length_reference].derived_value
+            else:
+                strlen_bits = packet.parsed_data[self.dynamic_length_reference].raw_value
+            strlen_bits = int(strlen_bits)
+        elif self.termination_character is not None:
+            # Look through the rest of the packet data to find the termination character
+            nbits_left = len(packet) - packet.pos
+            orig_pos = packet.pos
+            string_buffer = packet.read_as_bytes(nbits_left - nbits_left % 8)
+            # Reset the original position because we only wanted to look ahead
+            packet.pos = orig_pos
+            try:
+                strlen_bits = string_buffer.index(self.termination_character) * 8
+            except ValueError as exc:
+                # Termination character not found in the string buffer
+                raise ValueError(f"Reached the end of the packet data without finding the "
+                                 f"termination character {self.termination_character}") from exc
+        else:
+            raise ValueError("Unable to parse StringParameterType. "
+                             "Didn't contain any way to constrain the length of the string.")
+        if not self.termination_character and self.length_linear_adjuster is not None:
+            # Only adjust if we are not doing this by termination character. Adjusting a length that is objectively
+            # determined via termination character is nonsensical.
+            strlen_bits = self.length_linear_adjuster(strlen_bits)
+        return strlen_bits
+        # pylint: enable=too-many-branches
+
+    def parse_value(self, packet: parseables.Packet, **kwargs) -> Tuple[str, None]:
+        """Parse a value from packet data, possibly using previously parsed data items to inform parsing.
+
+        Parameters
+        ----------
+        packet: Packet
+            Binary representation of the packet used to get the coming bits and any
+            previously parsed data items to infer field lengths.
+        Returns
+        -------
+        : str
+            Parsed value
+        : None
+            Calibrated value
+        """
+        nbits = self._calculate_size(packet)
+        parsed_value = packet.read_as_bytes(nbits)
+        if self.termination_character is not None:
+            # We need to skip over the termination character if there was one
+            packet.pos += len(self.termination_character) * 8
+        return parsed_value.decode(self.encoding), None
+
+    @classmethod
+    def from_data_encoding_xml_element(cls, element: ElementTree.Element, ns: dict) -> 'StringDataEncoding':
+        """Create a data encoding object from an <xtce:StringDataEncoding> XML element.
+        Strings in XTCE can be described in three ways:
+
+        1. Using a termination character that marks the end of the string.
+        2. Using a fixed length, which may be derived from referenced parameter either directly or via a discrete
+           lookup table.
+        3. Using a leading size field that describes the size of the following string.
+
+        Parameters
+        ----------
+        element : ElementTree.Element
+            XML element
+        ns : dict
+            XML namespace dict
+
+        Returns
+        -------
+        cls
+        """
+        encoding: str = element.get("encoding", "UTF-8")
+
+        byte_order = None  # fallthrough value
+        if encoding not in ('US-ASCII', 'ISO-8859-1', 'Windows-1252', 'UTF-8'):  # single-byte chars
+            if not (encoding.endswith("BE") or encoding.endswith("LE")):
+                byte_order = element.get("byteOrder")
+                if byte_order is None:
+                    raise ValueError("For multi-byte character encodings, byte order must be specified "
+                                     "either using the byteOrder attribute or via the encoding itself.")
+
+        try:
+            termination_character = element.find('xtce:SizeInBits/xtce:TerminationChar', ns).text
+            return cls(termination_character=termination_character, encoding=encoding, byte_order=byte_order)
+        except AttributeError:
+            pass
+
+        try:
+            leading_length_size = int(
+                element.find('xtce:SizeInBits/xtce:LeadingSize', ns).attrib['sizeInBitsOfSizeTag'])
+            return cls(leading_length_size=leading_length_size, encoding=encoding, byte_order=byte_order)
+        except AttributeError:
+            pass
+
+        fixed_element = element.find('xtce:SizeInBits/xtce:Fixed', ns)
+
+        discrete_lookup_list_element = fixed_element.find('xtce:DiscreteLookupList', ns)
+        if discrete_lookup_list_element is not None:
+            discrete_lookup_list = [comparisons.DiscreteLookup.from_discrete_lookup_xml_element(el, ns)
+                                    for el in discrete_lookup_list_element.findall('xtce:DiscreteLookup', ns)]
+            return cls(encoding=encoding, byte_order=byte_order,
+                       discrete_lookup_length=discrete_lookup_list)
+
+        try:
+            dynamic_value_element = fixed_element.find('xtce:DynamicValue', ns)
+            referenced_parameter = dynamic_value_element.find('xtce:ParameterInstanceRef', ns).attrib['parameterRef']
+            use_calibrated_value = True
+            if 'useCalibratedValue' in dynamic_value_element.find('xtce:ParameterInstanceRef', ns).attrib:
+                use_calibrated_value = dynamic_value_element.find(
+                    'xtce:ParameterInstanceRef', ns).attrib['useCalibratedValue'].lower() == "true"
+            linear_adjuster = cls._get_linear_adjuster(dynamic_value_element, ns)
+            return cls(encoding=encoding, byte_order=byte_order,
+                       dynamic_length_reference=referenced_parameter, use_calibrated_value=use_calibrated_value,
+                       length_linear_adjuster=linear_adjuster)
+        except AttributeError:
+            pass
+
+        try:
+            fixed_length = int(fixed_element.find('xtce:FixedValue', ns).text)
+            return cls(fixed_length=fixed_length, encoding=encoding, byte_order=byte_order)
+        except AttributeError:
+            pass
+
+        raise ElementNotFoundError(f"Failed to parse StringDataEncoding for element {ElementTree.tostring(element)}")
+
+
+class NumericDataEncoding(DataEncoding, metaclass=ABCMeta):
+    """Abstract class that is inherited by IntegerDataEncoding and FloatDataEncoding"""
+
+    def __init__(self, size_in_bits: int,
+                 encoding: str,
+                 byte_order: str = "mostSignificantByteFirst",
+                 default_calibrator: Optional[calibrators.Calibrator] = None,
+                 context_calibrators: Optional[List[calibrators.ContextCalibrator]] = None):
+        """Constructor
+
+        Parameters
+        ----------
+        size_in_bits : int
+            Size of the integer
+        encoding : str
+            String indicating the type of encoding for the integer. FSW seems to use primarily 'signed' and 'unsigned',
+            though 'signed' is not actually a valid specifier according to XTCE. 'twosCompliment' [sic] should be used
+            instead, though we support the unofficial 'signed' specifier here.
+            For supported specifiers, see XTCE spec 4.3.2.2.5.6.2
+        byte_order : str
+            Description of the byte order. Default is 'mostSignficantByteFirst' (big-endian).
+        default_calibrator : Optional[Calibrator]
+            Optional Calibrator object, containing information on how to transform the integer-encoded data, e.g. via
+            a polynomial conversion or spline interpolation.
+        context_calibrators : Optional[List[ContextCalibrator]]
+            List of ContextCalibrator objects, containing match criteria and corresponding calibrators to use in
+            various scenarios, based on other parameters.
+        """
+        self.size_in_bits = size_in_bits
+        self.encoding = encoding
+        self.byte_order = byte_order
+        self.default_calibrator = default_calibrator
+        self.context_calibrators = context_calibrators
+
+    def _calculate_size(self, packet: parseables.Packet) -> int:
+        return self.size_in_bits
+
+    @abstractmethod
+    def _get_raw_value(self, packet: parseables.Packet) -> Union[int, float]:
+        """Read the raw value from the packet data
+
+        Parameters
+        ----------
+        packet: Packet
+            Binary representation of the packet used to get the coming bits and any
+            previously parsed data items to infer field lengths.
+
+        Returns
+        -------
+        : int
+            Raw value
+        """
+        raise NotImplementedError()
+
+    @staticmethod
+    def _twos_complement(val: int, bit_width: int) -> int:
+        """Take the twos complement of val
+        Used when parsing ints and some floats
+        """
+        if (val & (1 << (bit_width - 1))) != 0:  # if sign bit is set e.g., 8bit: 128-255
+            return val - (1 << bit_width)  # compute negative value
+        return val
+
+    def parse_value(self,
+                    packet: parseables.Packet,
+                    **kwargs) -> Tuple[Union[int, float], Union[int, float]]:
+        """Parse a value from packet data, possibly using previously parsed data items to inform parsing.
+
+        Parameters
+        ----------
+        packet: Packet
+            Binary representation of the packet used to get the coming bits and any
+            previously parsed data items to infer field lengths.
+        Returns
+        -------
+        : any
+            Parsed value
+        : any
+            Calibrated value
+        """
+        parsed_value = self._get_raw_value(packet)
+        # Attempt to calibrate
+        calibrated_value = parsed_value  # Provides a fall through in case we have no calibrators
+        if self.context_calibrators:
+            for calibrator in self.context_calibrators:
+                match_criteria = calibrator.match_criteria
+                if all(criterion.evaluate(packet.parsed_data, parsed_value) for criterion in match_criteria):
+                    # If the parsed data so far satisfy all the match criteria
+                    calibrated_value = calibrator.calibrate(parsed_value)
+                    return parsed_value, calibrated_value
+        if self.default_calibrator:  # If no context calibrators or if none apply and there is a default
+            calibrated_value = self.default_calibrator.calibrate(parsed_value)
+        # Ultimate fallthrough
+        return parsed_value, calibrated_value
+
+
+class IntegerDataEncoding(NumericDataEncoding):
+    """<xtce:IntegerDataEncoding>"""
+
+    def _get_raw_value(self, packet: parseables.Packet) -> int:
+        # Extract the bits from the data in big-endian order from the packet
+        val = packet.read_as_int(self.size_in_bits)
+        if self.byte_order == 'leastSignificantByteFirst':
+            # Convert little-endian (LSB first) int to bigendian. Just reverses the order of the bytes.
+            val = int.from_bytes(
+                val.to_bytes(
+                    length=(self.size_in_bits + 7) // 8,
+                    byteorder="little"
+                ),
+                byteorder="big"
+            )
+        if self.encoding == 'unsigned':
+            return val
+        # It is a signed integer, and we need to take into account the first bit
+        return self._twos_complement(val, self.size_in_bits)
+
+    @classmethod
+    def from_data_encoding_xml_element(cls, element: ElementTree.Element, ns: dict) -> 'IntegerDataEncoding':
+        """Create a data encoding object from an <xtce:IntegerDataEncoding> XML element.
+
+        Parameters
+        ----------
+        element : ElementTree.Element
+            XML element
+        ns : dict
+            XML namespace dict
+
+        Returns
+        -------
+        : cls
+        """
+        size_in_bits = int(element.attrib['sizeInBits'])
+        encoding = element.attrib['encoding'] if 'encoding' in element.attrib else "unsigned"
+        byte_order = element.get("byteOrder", "mostSignificantByteFirst")
+        calibrator = cls.get_default_calibrator(element, ns)
+        context_calibrators = cls.get_context_calibrators(element, ns)
+        return cls(size_in_bits=size_in_bits, encoding=encoding, byte_order=byte_order,
+                   default_calibrator=calibrator, context_calibrators=context_calibrators)
+
+
+class FloatDataEncoding(NumericDataEncoding):
+    """<xtce:FloatDataEncoding>"""
+    _supported_encodings = ['IEEE-754', 'MIL-1750A']
+
+    def __init__(self, size_in_bits: int, encoding: str = 'IEEE-754',
+                 byte_order: str = 'mostSignificantByteFirst',
+                 default_calibrator: Optional[calibrators.Calibrator] = None,
+                 context_calibrators: Optional[List[calibrators.ContextCalibrator]] = None):
+        """Constructor
+
+        Parameters
+        ----------
+        size_in_bits : int
+            Size of the encoded value, in bits.
+        encoding : str
+            Encoding method of the float data. Must be either 'IEEE-754' or 'MIL-1750A'. Defaults to IEEE-754.
+        byte_order : str
+            Description of the byte order. Default is 'mostSignificantByteFirst' (big endian).
+        default_calibrator : Optional[Calibrator]
+            Optional Calibrator object, containing information on how to transform the data, e.g. via
+            a polynomial conversion or spline interpolation.
+        context_calibrators : Optional[List[ContextCalibrator]]
+            List of ContextCalibrator objects, containing match criteria and corresponding calibrators to use in
+            various scenarios, based on other parameters.
+        """
+        if encoding not in self._supported_encodings:
+            raise ValueError(f"Invalid encoding type {encoding} for float data. "
+                             f"Must be one of {self._supported_encodings}.")
+        if encoding == 'MIL-1750A' and size_in_bits != 32:
+            raise ValueError("MIL-1750A encoded floats must be 32 bits, per the MIL-1750A spec. See "
+                             "https://www.xgc-tek.com/manuals/mil-std-1750a/c191.html#AEN324")
+        if encoding == 'IEEE-754' and size_in_bits not in (16, 32, 64):
+            raise ValueError(f"Invalid size_in_bits value for IEEE-754 FloatDataEncoding, {size_in_bits}. "
+                             "Must be 16, 32, or 64.")
+        super().__init__(size_in_bits, encoding=encoding, byte_order=byte_order,
+                         default_calibrator=default_calibrator, context_calibrators=context_calibrators)
+        if self.encoding == "MIL-1750A":
+            def _mil_parse_func(mil_bytes: bytes):
+                """Parsing function for MIL-1750A floats"""
+                # MIL 1750A floats are always 32 bit
+                # See: https://www.xgc-tek.com/manuals/mil-std-1750a/c191.html#AEN324
+                #
+                #  MSB                                         LSB MSB          LSB
+                # ------------------------------------------------------------------
+                # | S|                   Mantissa                 |    Exponent    |
+                # ------------------------------------------------------------------
+                #   0  1                                        23 24            31
+                if self.byte_order == "leastSignificantByteFirst":
+                    bytes_as_int = int.from_bytes(mil_bytes, byteorder='little')
+                else:
+                    bytes_as_int = int.from_bytes(mil_bytes, byteorder='big')
+                exponent = bytes_as_int & 0xFF  # last 8 bits
+                mantissa = (bytes_as_int >> 8) & 0xFFFFFF  # bits 0 through 23 (24 bits)
+                # We include the sign bit with the mantissa because we can just take the twos complement
+                # of it directly and use it in the final calculation for the value
+
+                # Both mantissa and exponent are stored as twos complement with no bias
+                exponent = self._twos_complement(exponent, 8)
+                mantissa = self._twos_complement(mantissa, 24)
+
+                # Calculate float value using native Python floats, which are more precise
+                return mantissa * (2.0 ** (exponent - (24 - 1)))
+
+            # Set up the parsing function just once, so we can use it repeatedly with _get_raw_value
+            self.parse_func = _mil_parse_func
+        else:
+            if self.byte_order == "leastSignificantByteFirst":
+                self._struct_format = "<"
+            else:
+                # Big-endian is the default
+                self._struct_format = ">"
+
+            if self.size_in_bits == 16:
+                self._struct_format += "e"
+            elif self.size_in_bits == 32:
+                self._struct_format += "f"
+            elif self.size_in_bits == 64:
+                self._struct_format += "d"
+
+            def ieee_parse_func(data: bytes):
+                """Parsing function for IEEE floats"""
+                # The packet data we got back is always extracted in big-endian order
+                # but the struct format code contains the endianness of the float data
+                return struct.unpack(self._struct_format, data)[0]
+            # Set up the parsing function just once, so we can use it repeatedly with _get_raw_value
+            self.parse_func: callable = ieee_parse_func
+
+    def _get_raw_value(self, packet):
+        """Read the data in as bytes and return a float representation."""
+        data = packet.read_as_bytes(self.size_in_bits)
+        # The parsing function is fully set during initialization to save time during parsing
+        return self.parse_func(data)
+
+    @classmethod
+    def from_data_encoding_xml_element(cls, element: ElementTree.Element, ns: dict) -> 'FloatDataEncoding':
+        """Create a data encoding object from an <xtce:FloatDataEncoding> XML element.
+
+        Parameters
+        ----------
+        element : ElementTree.Element
+            XML element
+        ns : dict
+            XML namespace dict
+
+        Returns
+        -------
+        : cls
+        """
+        size_in_bits = int(element.attrib['sizeInBits'])
+        encoding = element.get("encoding", "IEEE-754")
+        byte_order = element.get("byteOrder", "mostSignificantByteFirst")
+        default_calibrator = cls.get_default_calibrator(element, ns)
+        context_calibrators = cls.get_context_calibrators(element, ns)
+        return cls(size_in_bits=size_in_bits, encoding=encoding, byte_order=byte_order,
+                   default_calibrator=default_calibrator, context_calibrators=context_calibrators)
+
+
+class BinaryDataEncoding(DataEncoding):
+    """<xtce:BinaryDataEncoding>"""
+
+    def __init__(self, fixed_size_in_bits: Optional[int] = None,
+                 size_reference_parameter: Optional[str] = None, use_calibrated_value: bool = True,
+                 size_discrete_lookup_list: Optional[List[comparisons.DiscreteLookup]] = None,
+                 linear_adjuster: Optional[callable] = None):
+        """Constructor
+
+        Parameters
+        ----------
+        fixed_size_in_bits : Optional[int]
+            Fixed size for the binary field, in bits.
+        size_reference_parameter : Optional[str]
+            Name of a parameter to reference for the binary field length, in bits. Note that space often specifies these
+            fields in byte length, not bit length. This should be taken care of by a LinearAdjuster element that simply
+            instructs the value to be multiplied by 8 but that hasn't historically been implemented unfortunately.
+        use_calibrated_value: bool, Optional
+            Default True. If False, the size_reference_parameter is examined for its raw value.
+        size_discrete_lookup_list: Optional[List[DiscreteLookup]]
+            List of DiscreteLookup objects by which to determine the length of the binary data field. This suffers from
+            the same bit/byte conversion problem as size_reference_parameter.
+        linear_adjuster : Optional[callable]
+            Function that linearly adjusts a size. e.g. if the size reference parameter gives a length in bytes, the
+            linear adjuster should multiply by 8 to give the size in bits.
+        """
+        self.fixed_size_in_bits = fixed_size_in_bits
+        self.size_reference_parameter = size_reference_parameter
+        self.use_calibrated_value = use_calibrated_value
+        self.size_discrete_lookup_list = size_discrete_lookup_list
+        self.linear_adjuster = linear_adjuster
+
+    def _calculate_size(self, packet: parseables.Packet) -> int:
+        """Determine the number of bits in the binary field.
+
+        Returns
+        -------
+        : Union[str, None]
+            Format string in the bitstring format. e.g. bin:1024
+        """
+        if self.fixed_size_in_bits is not None:
+            len_bits = self.fixed_size_in_bits
+        elif self.size_reference_parameter is not None:
+            field_length_reference = self.size_reference_parameter
+            if self.use_calibrated_value:
+                len_bits = packet.parsed_data[field_length_reference].derived_value
+            else:
+                len_bits = packet.parsed_data[field_length_reference].raw_value
+        elif self.size_discrete_lookup_list is not None:
+            for discrete_lookup in self.size_discrete_lookup_list:
+                len_bits = discrete_lookup.evaluate(packet.parsed_data)
+                if len_bits is not None:
+                    break
+            else:
+                raise ValueError('List of discrete lookup values being used for determining length of '
+                                 f'string {self} found no matches based on {packet.parsed_data}.')
+        else:
+            raise ValueError("Unable to parse BinaryDataEncoding. "
+                             "No fixed size, dynamic size, or dynamic lookup size were provided.")
+
+        if self.linear_adjuster is not None:
+            len_bits = self.linear_adjuster(len_bits)
+        return len_bits
+
+    def parse_value(self, packet: parseables.Packet, word_size: Optional[int] = None, **kwargs):
+        """Parse a value from packet data, possibly using previously parsed data items to inform parsing.
+
+        Parameters
+        ----------
+        packet: Packet
+            Binary representation of the packet used to get the coming bits and any
+            previously parsed data items to infer field lengths.
+        word_size : Optional[int]
+            Word size for encoded data. This is used to ensure that the cursor ends up at the end of the last word
+            and ready to parse the next data field.
+
+        Returns
+        -------
+        : any
+            Parsed value
+        : any
+            Calibrated value
+        """
+        nbits = self._calculate_size(packet)
+        parsed_value = packet.read_as_bytes(nbits)
+        if word_size:
+            cursor_position_in_word = packet.pos % word_size
+            if cursor_position_in_word != 0:
+                logger.debug(f"Adjusting cursor position to the end of a {word_size} bit word.")
+                packet.pos += word_size - cursor_position_in_word
+        return parsed_value, None
+
+    @classmethod
+    def from_data_encoding_xml_element(cls, element: ElementTree.Element, ns: dict) -> 'BinaryDataEncoding':
+        """Create a data encoding object from an <xtce:BinaryDataEncoding> XML element.
+
+        Parameters
+        ----------
+        element : ElementTree.Element
+            XML element
+        ns : dict
+            XML namespace dict
+
+        Returns
+        -------
+        : BinaryDataEncoding
+        """
+        fixed_value_element = element.find('xtce:SizeInBits/xtce:FixedValue', ns)
+        if fixed_value_element is not None:
+            fixed_size_in_bits = int(fixed_value_element.text)
+            return cls(fixed_size_in_bits=fixed_size_in_bits)
+
+        dynamic_value_element = element.find('xtce:SizeInBits/xtce:DynamicValue', ns)
+        if dynamic_value_element is not None:
+            param_inst_ref = dynamic_value_element.find('xtce:ParameterInstanceRef', ns)
+            referenced_parameter = param_inst_ref.attrib['parameterRef']
+            use_calibrated_value = True
+            if 'useCalibratedValue' in param_inst_ref.attrib:
+                use_calibrated_value = param_inst_ref.attrib['useCalibratedValue'].lower() == "true"
+            linear_adjuster = cls._get_linear_adjuster(dynamic_value_element, ns)
+            return cls(size_reference_parameter=referenced_parameter,
+                       use_calibrated_value=use_calibrated_value, linear_adjuster=linear_adjuster)
+
+        discrete_lookup_list_element = element.find('xtce:SizeInBits/xtce:DiscreteLookupList', ns)
+        if discrete_lookup_list_element is not None:
+            discrete_lookup_list = [comparisons.DiscreteLookup.from_discrete_lookup_xml_element(el, ns)
+                                    for el in discrete_lookup_list_element.findall('xtce:DiscreteLookup', ns)]
+            return cls(size_discrete_lookup_list=discrete_lookup_list)
+
+        raise ValueError("Tried parsing a binary parameter length using Fixed, Dynamic, and DiscreteLookupList "
+                         "but failed. See 3.4.5 of the XTCE Green Book CCSDS 660.1-G-2.")
diff --git a/space_packet_parser/exceptions.py b/space_packet_parser/exceptions.py
new file mode 100644
index 0000000..143d5db
--- /dev/null
+++ b/space_packet_parser/exceptions.py
@@ -0,0 +1,32 @@
+
+"""Space Packet Parser Exceptions"""
+
+
+class ElementNotFoundError(Exception):
+    """Exception for missing XML element"""
+    pass
+
+
+class ComparisonError(Exception):
+    """Exception for problems performing comparisons"""
+    pass
+
+
+class FormatStringError(Exception):
+    """Error indicating a problem determining how to parse a variable length string."""
+    pass
+
+
+class DynamicLengthBinaryParameterError(Exception):
+    """Exception to raise when we try to parse a dynamic length binary field as fixed length"""
+    pass
+
+
+class CalibrationError(Exception):
+    """For errors encountered during value calibration"""
+    pass
+
+
+class InvalidParameterTypeError(Exception):
+    """Error raised when someone is using an invalid ParameterType element"""
+    pass
diff --git a/space_packet_parser/parameters.py b/space_packet_parser/parameters.py
new file mode 100644
index 0000000..259f39e
--- /dev/null
+++ b/space_packet_parser/parameters.py
@@ -0,0 +1,532 @@
+
+"""ParameterType definitions"""
+from abc import ABCMeta
+from dataclasses import dataclass
+from typing import Optional, Union
+import warnings
+
+import lxml.etree as ElementTree
+
+from space_packet_parser import calibrators, comparisons, encodings, parseables
+
+
+class ParameterType(comparisons.AttrComparable, metaclass=ABCMeta):
+    """Abstract base class for XTCE parameter types"""
+
+    def __init__(self, name: str, encoding: encodings.DataEncoding, unit: Optional[str] = None):
+        """Constructor
+
+        Parameters
+        ----------
+        name : str
+            Parameter type name. Usually something like 'MSN__PARAM_Type'
+        encoding : DataEncoding
+            How the data is encoded. e.g. IntegerDataEncoding, StringDataEncoding, etc.
+        unit : Optional[str]
+            String describing the unit for the stored value.
+        """
+        self.name = name
+        self.unit = unit
+        self.encoding = encoding
+
+    def __repr__(self):
+        module = self.__class__.__module__
+        qualname = self.__class__.__qualname__
+        return f"<{module}.{qualname} {self.name}>"
+
+    @classmethod
+    def from_parameter_type_xml_element(cls, element: ElementTree.Element, ns: dict) -> 'ParameterType':
+        """Create a *ParameterType from an <xtce:*ParameterType> XML element.
+
+        Parameters
+        ----------
+        element : ElementTree.Element
+            The XML element from which to create the object.
+        ns: dict
+            XML namespace dict
+
+        Returns
+        -------
+        : ParameterType
+        """
+        name = element.attrib['name']
+        unit = cls.get_units(element, ns)
+        encoding = cls.get_data_encoding(element, ns)
+        return cls(name, encoding, unit)
+
+    @staticmethod
+    def get_units(parameter_type_element: ElementTree.Element, ns: dict) -> Union[str, None]:
+        """Finds the units associated with a parameter type element and parsed them to return a unit string.
+        We assume only one <xtce:Unit> but this could be extended to support multiple units.
+        See section 4.3.2.2.4 of CCSDS 660.1-G-1
+
+        Parameters
+        ----------
+        parameter_type_element : ElementTree.Element
+            The parameter type element
+        ns : dict
+            XML namespace dictionary
+
+        Returns
+        -------
+        : Union[str, None]
+            Unit string or None if no units are defined
+        """
+        # Assume we are not parsing a Time Parameter Type, which stores units differently
+        units = parameter_type_element.findall('xtce:UnitSet/xtce:Unit', ns)
+        # TODO: Implement multiple unit elements for compound unit definitions
+        assert len(units) <= 1, f"Found {len(units)} <xtce:Unit> elements in a single <xtce:UnitSet>." \
+                                f"This is supported in the standard but is rarely used " \
+                                f"and is not yet supported by this library."
+        if units:
+            return " ".join([u.text for u in units])
+        # Units are optional so return None if they aren't specified
+        return None
+
+    @staticmethod
+    def get_data_encoding(parameter_type_element: ElementTree.Element, ns: dict) -> Union[encodings.DataEncoding, None]:
+        """Finds the data encoding XML element associated with a parameter type XML element and parses
+        it, returning an object representation of the data encoding.
+
+        Parameters
+        ----------
+        parameter_type_element : ElementTree.Element
+            The parameter type element
+        ns : dict
+            XML namespace dictionary
+
+        Returns
+        -------
+        : Union[DataEncoding, None]
+            DataEncoding object or None if no data encoding is defined (which is probably an issue)
+        """
+        for data_encoding in [encodings.StringDataEncoding,
+                              encodings.IntegerDataEncoding,
+                              encodings.FloatDataEncoding,
+                              encodings.BinaryDataEncoding]:
+            # Try to find each type of data encoding element. If we find one, we assume it's the only one.
+            element = parameter_type_element.find(f".//xtce:{data_encoding.__name__}", ns)
+            if element is not None:
+                return data_encoding.from_data_encoding_xml_element(element, ns)
+        return None
+
+    def parse_value(self, packet: parseables.Packet, **kwargs):
+        """Using the parameter type definition and associated data encoding, parse a value from a bit stream starting
+        at the current cursor position.
+
+        Parameters
+        ----------
+        packet: Packet
+            Binary representation of the packet used to get the coming bits and any
+            previously parsed data items to infer field lengths.
+
+        Returns
+        -------
+        parsed_value : any
+            Resulting parsed data value.
+        """
+        return self.encoding.parse_value(packet, **kwargs)
+
+
+class StringParameterType(ParameterType):
+    """<xtce:StringParameterType>"""
+
+    def __init__(self, name: str, encoding: encodings.StringDataEncoding, unit: Optional[str] = None):
+        """Constructor
+
+        Parameters
+        ----------
+        name : str
+            Parameter type name. Usually something like 'MSN__PARAM_Type'
+        encoding : StringDataEncoding
+            Must be a StringDataEncoding object since strings can't be encoded other ways.
+        unit : Optional[str]
+            String describing the unit for the stored value.
+        """
+        if not isinstance(encoding, encodings.StringDataEncoding):
+            raise ValueError("StringParameterType may only be instantiated with a StringDataEncoding encoding.")
+        super().__init__(name=name, encoding=encoding, unit=unit)
+        self.encoding = encoding  # Clarifies to static analysis tools that self.encoding is type StringDataEncoding
+
+
+class IntegerParameterType(ParameterType):
+    """<xtce:IntegerParameterType>"""
+    pass
+
+
+class FloatParameterType(ParameterType):
+    """<xtce:FloatParameterType>"""
+    pass
+
+
+class EnumeratedParameterType(ParameterType):
+    """<xtce:EnumeratedParameterType>"""
+
+    def __init__(self, name: str, encoding: encodings.DataEncoding, enumeration: dict, unit: Union[str, None] = None):
+        """Constructor
+
+        Parameters
+        ----------
+        name : str
+            Parameter type name.
+        unit : str
+            Unit string for stored value.
+        encoding : DataEncoding
+            How the data is encoded. e.g. IntegerDataEncoding.
+        enumeration : dict
+            Lookup with label:value pairs matching encoded values to their enum labels.
+        """
+        super().__init__(name=name, encoding=encoding, unit=unit)
+        self.enumeration = enumeration
+
+    def __repr__(self):
+        return f"<{self.__class__.__name__} {self.name}>"
+
+    @classmethod
+    def from_parameter_type_xml_element(cls, element: ElementTree.Element, ns: dict):
+        """Create an EnumeratedParameterType from an <xtce:EnumeratedParameterType> XML element.
+        Overrides ParameterType.from_parameter_type_xml_element
+
+        Parameters
+        ----------
+        element : ElementTree.Element
+            The XML element from which to create the object.
+        ns: dict
+            XML namespace dict
+
+        Returns
+        -------
+        : EnumeratedParameterType
+        """
+        name = element.attrib['name']
+        unit = cls.get_units(element, ns)
+        encoding = cls.get_data_encoding(element, ns)
+        enumeration = cls.get_enumeration_list_contents(element, ns)
+        return cls(name, encoding, enumeration=enumeration, unit=unit)
+
+    @staticmethod
+    def get_enumeration_list_contents(element: ElementTree.Element, ns: dict) -> dict:
+        """Finds the <xtce:EnumerationList> element child of an <xtce:EnumeratedParameterType> and parses it,
+        returning a dict. This method is confusingly named as if it might return a list. Sorry, XML and python
+        semantics are not always compatible. It's called an enumeration list because the XML element is called
+        <xtce:EnumerationList> but it contains key value pairs, so it's best represeneted as a dict.
+
+        Parameters
+        ----------
+        element : ElementTree.Element
+            The XML element from which to search for EnumerationList tags
+        ns : dict
+            XML namespace dict
+
+        Returns
+        -------
+        : dict
+        """
+        enumeration_list = element.find('xtce:EnumerationList', ns)
+        if enumeration_list is None:
+            raise ValueError("An EnumeratedParameterType must contain an EnumerationList.")
+
+        return {
+            int(el.attrib['value']): el.attrib['label']
+            for el in enumeration_list.iterfind('xtce:Enumeration', ns)
+        }
+
+    def parse_value(self, packet: parseables.Packet, **kwargs):
+        """Using the parameter type definition and associated data encoding, parse a value from a bit stream starting
+        at the current cursor position.
+
+        Parameters
+        ----------
+        packet: Packet
+            Binary representation of the packet used to get the coming bits and any
+            previously parsed data items to infer field lengths.
+
+        Returns
+        -------
+        parsed_value : int
+            Raw encoded value
+        derived_value : str
+            Resulting enum label associated with the (usually integer-)encoded data value.
+        """
+        raw, _ = super().parse_value(packet, **kwargs)
+        # Note: The enum lookup only operates on raw values. This is specified in 4.3.2.4.3.6 of the XTCE spec "
+        # CCSDS 660.1-G-2
+        try:
+            label = self.enumeration[raw]
+        except KeyError as exc:
+            raise ValueError(f"Failed to find raw value {raw} in enum lookup list {self.enumeration}.") from exc
+        return raw, label
+
+
+class BinaryParameterType(ParameterType):
+    """<xtce:BinaryParameterType>"""
+
+    def __init__(self, name: str, encoding: encodings.BinaryDataEncoding, unit: Optional[str] = None):
+        """Constructor
+
+        Parameters
+        ----------
+        name : str
+            Parameter type name. Usually something like 'MSN__PARAM_Type'
+        encoding : BinaryDataEncoding
+            Must be a BinaryDataEncoding object since binary data can't be encoded other ways.
+        unit : Optional[str]
+            String describing the unit for the stored value.
+        """
+        if not isinstance(encoding, encodings.BinaryDataEncoding):
+            raise ValueError("BinaryParameterType may only be instantiated with a BinaryDataEncoding encoding.")
+        super().__init__(name=name, encoding=encoding, unit=unit)
+        self.encoding = encoding
+
+
+class BooleanParameterType(ParameterType):
+    """<xtce:BooleanParameterType>"""
+
+    def __init__(self, name: str, encoding: encodings.DataEncoding, unit: Optional[str] = None):
+        """Constructor that just issues a warning if the encoding is String or Binary"""
+        if isinstance(encoding, (encodings.BinaryDataEncoding, encodings.StringDataEncoding)):
+            warnings.warn(f"You are encoding a BooleanParameterType with a {type(encoding)} encoding."
+                          f"This is almost certainly a very bad idea because the behavior of string and binary "
+                          f"encoded booleans is not specified in XTCE. e.g. is the string \"0\" truthy?")
+        super().__init__(name, encoding, unit)
+
+    def parse_value(self, packet: parseables.Packet, **kwargs):
+        """Using the parameter type definition and associated data encoding, parse a value from a bit stream starting
+        at the current cursor position.
+
+        Parameters
+        ----------
+        packet: Packet
+            Binary representation of the packet used to get the coming bits and any
+            previously parsed data items to infer field lengths.
+
+
+        Returns
+        -------
+        parsed_value : int
+            Raw encoded value
+        derived_value : str
+            Resulting boolean representation of the encoded raw value
+        """
+        raw, _ = super().parse_value(packet, **kwargs)
+        # Note: This behaves very strangely for String and Binary data encodings.
+        # Don't use those for Boolean parameters. The behavior isn't specified well in XTCE.
+        return raw, bool(raw)
+
+
+class TimeParameterType(ParameterType, metaclass=ABCMeta):
+    """Abstract class for time parameter types"""
+
+    def __init__(self, name: str, encoding: encodings.DataEncoding, unit: Optional[str] = None,
+                 epoch: Optional[str] = None, offset_from: Optional[str] = None):
+        """Constructor
+
+        Parameters
+        ----------
+        name : str
+            Parameter type name. Usually something like 'MSN__PARAM_Type'.
+        encoding : DataEncoding
+            How the data is encoded. e.g. IntegerDataEncoding, StringDataEncoding, etc.
+        unit : Optional[str]
+            String describing the unit for the stored value. Note that if a scale and offset are provided on
+            the Encoding element, the unit applies to the scaled value, not the raw value.
+        epoch : Optional[str]
+            String describing the starting epoch for the date or datetime encoded in the parameter.
+            Must be xs:date, xs:dateTime, or one of the following: "TAI", "J2000", "UNIX", "POSIX", "GPS".
+        offset_from : Optional[str]
+            Used to reference another time parameter by name. It allows
+            for the stringing together of several dissimilar but related time parameters.
+
+        Notes
+        -----
+        The XTCE spec is not very clear about OffsetFrom or what it is for. We parse it but don't use it for
+        anything.
+        """
+        super().__init__(name, encoding, unit=unit)
+        self.epoch = epoch
+        self.offset_from = offset_from
+
+    @classmethod
+    def from_parameter_type_xml_element(cls, element: ElementTree.Element, ns: dict):
+        """Create a *TimeParameterType from an <xtce:*TimeParameterType> XML element.
+
+        Parameters
+        ----------
+        element : ElementTree.Element
+            The XML element from which to create the object.
+        ns: dict
+            XML namespace dict
+
+        Returns
+        -------
+        : TimeParameterType
+        """
+        name = element.attrib['name']
+        unit = cls.get_units(element, ns)
+        encoding = cls.get_data_encoding(element, ns)
+        encoding_unit_scaler = cls.get_time_unit_linear_scaler(element, ns)
+        if encoding_unit_scaler:
+            encoding.default_calibrator = encoding_unit_scaler
+        epoch = cls.get_epoch(element, ns)
+        offset_from = cls.get_offset_from(element, ns)
+        return cls(name, encoding, unit, epoch, offset_from)
+
+    @staticmethod
+    def get_units(parameter_type_element: ElementTree.Element, ns: dict) -> Union[str, None]:
+        """Finds the units associated with a parameter type element and parsed them to return a unit string.
+        We assume only one <xtce:Unit> but this could be extended to support multiple units.
+        See section 4.3.2.2.4 of CCSDS 660.1-G-1
+
+        Parameters
+        ----------
+        parameter_type_element : ElementTree.Element
+            The parameter type element
+        ns : dict
+            XML namespace dictionary
+
+        Returns
+        -------
+        : Union[str, None]
+            Unit string or None if no units are defined
+        """
+        encoding_element = parameter_type_element.find('xtce:Encoding', ns)
+        if encoding_element and "units" in encoding_element.attrib:
+            units = encoding_element.attrib["units"]
+            return units
+        # Units are optional so return None if they aren't specified
+        return None
+
+    @staticmethod
+    def get_time_unit_linear_scaler(
+            parameter_type_element: ElementTree.Element, ns: dict) -> Union[calibrators.PolynomialCalibrator, None]:
+        """Finds the linear calibrator associated with the Encoding element for the parameter type element.
+        See section 4.3.2.4.8.3 of CCSDS 660.1-G-2
+
+        Parameters
+        ----------
+        parameter_type_element : ElementTree.Element
+            The parameter type element
+        ns : dict
+            XML namespace dictionary
+
+        Returns
+        -------
+        : Union[PolynomialCalibrator, None]
+            The PolynomialCalibrator, or None if we couldn't create a valid calibrator from the XML element
+        """
+        encoding_element = parameter_type_element.find('xtce:Encoding', ns)
+        coefficients = []
+
+        if "offset" in encoding_element.attrib:
+            offset = encoding_element.attrib["offset"]
+            c0 = calibrators.PolynomialCoefficient(coefficient=float(offset), exponent=0)
+            coefficients.append(c0)
+
+        if "scale" in encoding_element.attrib:
+            scale = encoding_element.attrib["scale"]
+            c1 = calibrators.PolynomialCoefficient(coefficient=float(scale), exponent=1)
+            coefficients.append(c1)
+        # If we have an offset but not a scale, we need to add a first order term with coefficient 1
+        elif "offset" in encoding_element.attrib:
+            c1 = calibrators.PolynomialCoefficient(coefficient=1, exponent=1)
+            coefficients.append(c1)
+
+        if coefficients:
+            return calibrators.PolynomialCalibrator(coefficients=coefficients)
+        # If we didn't find offset nor scale, return None (no calibrator)
+        return None
+
+    @staticmethod
+    def get_epoch(parameter_type_element: ElementTree.Element, ns: dict) -> Union[str, None]:
+        """Finds the epoch associated with a parameter type element and parses them to return an epoch string.
+        See section 4.3.2.4.9 of CCSDS 660.1-G-2
+
+        Parameters
+        ----------
+        parameter_type_element : ElementTree.Element
+            The parameter type element
+        ns : dict
+            XML namespace dictionary
+
+        Returns
+        -------
+        : Union[str, None]
+            The epoch string, which may be a datetime string or a named epoch such as TAI. None if the element was
+            not found.
+        """
+        epoch_element = parameter_type_element.find('xtce:ReferenceTime/xtce:Epoch', ns)
+        if epoch_element is not None:
+            return epoch_element.text
+        return None
+
+    @staticmethod
+    def get_offset_from(parameter_type_element: ElementTree.Element, ns: dict) -> Union[str, None]:
+        """Finds the parameter referenced in OffsetFrom in a parameter type element and returns the name of the
+        referenced parameter (which must be of type TimeParameterType).
+        See section 4.3.2.4.9 of CCSDS 660.1-G-1
+
+        Parameters
+        ----------
+        parameter_type_element : ElementTree.Element
+            The parameter type element
+        ns : dict
+            XML namespace dictionary
+
+        Returns
+        -------
+        : Union[str, None]
+            The named of the referenced parameter. None if no OffsetFrom element was found.
+        """
+        offset_from_element = parameter_type_element.find('xtce:ReferenceTime/xtce:OffsetFrom', ns)
+        if offset_from_element is not None:
+            return offset_from_element.attrib['parameterRef']
+        return None
+
+
+class AbsoluteTimeParameterType(TimeParameterType):
+    """<xtce:AbsoluteTimeParameterType>"""
+    pass
+
+
+class RelativeTimeParameterType(TimeParameterType):
+    """<xtce:RelativeTimeParameterType>"""
+    pass
+
+
+@dataclass
+class Parameter(parseables.Parseable):
+    """<xtce:Parameter>
+
+    Parameters
+    ----------
+    name : str
+        Parameter name. Typically something like MSN__PARAMNAME
+    parameter_type : ParameterType
+        Parameter type object that describes how the parameter is stored.
+    short_description : str
+        Short description of parameter as parsed from XTCE
+    long_description : str
+        Long description of parameter as parsed from XTCE
+    """
+    name: str
+    parameter_type: ParameterType
+    short_description: Optional[str] = None
+    long_description: Optional[str] = None
+
+    def parse(self, packet: parseables.Packet, **parse_value_kwargs) -> dict:
+        """Parse this parameter from the packet data.
+
+        Create a ``ParsedDataItem`` and add it to the parsed_items dictionary.
+        """
+        parsed_value, derived_value = self.parameter_type.parse_value(
+            packet, **parse_value_kwargs)
+
+        packet.parsed_data[self.name] = parseables.ParsedDataItem(
+            name=self.name,
+            unit=self.parameter_type.unit,
+            raw_value=parsed_value,
+            derived_value=derived_value,
+            short_description=self.short_description,
+            long_description=self.long_description
+        )
+        return packet.parsed_data
diff --git a/space_packet_parser/parseables.py b/space_packet_parser/parseables.py
new file mode 100644
index 0000000..d7e24d6
--- /dev/null
+++ b/space_packet_parser/parseables.py
@@ -0,0 +1,203 @@
+
+"""Packet containers and parsing utilities for space packets."""
+
+from collections import namedtuple
+from dataclasses import dataclass, field
+from typing import Union, Optional, Protocol, List
+
+
+@dataclass
+class ParsedDataItem:
+    """Representation of a parsed parameter
+
+    Parameters
+    ----------
+    name : str
+        Parameter name
+    unit : str
+        Parameter units
+    raw_value : any
+        Raw representation of the parsed value. May be lots of different types but most often an integer
+    derived_value : float or str
+        May be a calibrated value or an enum lookup
+    short_description : str
+        Parameter short description
+    long_description : str
+        Parameter long description
+    """
+    name: str
+    raw_value: Union[bytes, float, int, str]
+    unit: Optional[str] = None
+    derived_value: Optional[Union[float, str]] = None
+    short_description: Optional[str] = None
+    long_description: Optional[str] = None
+
+
+@dataclass
+class Packet:
+    """CCSDS Packet
+
+    Can be parsed to populate data items. This ``Packet`` class keeps track
+    of the current parsing position for know where to read from next when
+    parsing data items.
+
+    Parameters
+    ----------
+    data : bytes
+        The binary data for a single packet.
+    pos : int
+        The bit cursor position in the packet. Default 0.
+    """
+    rawdata: bytes
+    pos: Optional[int] = 0
+    parsed_data: Optional[dict] = field(default_factory=lambda: {})
+
+    def __len__(self):
+        """The length of the full packet data object in bits."""
+        return len(self.rawdata) * 8
+
+    @property
+    def header(self):
+        """The header content of the packet."""
+        return dict(list(self.parsed_data.items())[:7])
+
+    @property
+    def data(self):
+        """The user data content of the packet."""
+        return dict(list(self.parsed_data.items())[7:])
+
+    def read_as_bytes(self, nbits: int) -> bytes:
+        """Read a number of bits from the packet data as bytes.
+
+        Parameters
+        ----------
+        nbits : int
+            Number of bits to read
+
+        Returns
+        -------
+        : bytes
+            Raw bytes from the packet data
+        """
+        if self.pos + nbits > len(self):
+            raise ValueError("End of packet reached")
+        if self.pos % 8 == 0 and nbits % 8 == 0:
+            # If the read is byte-aligned, we can just return the bytes directly
+            data = self.rawdata[self.pos//8:self.pos//8 + nbits // 8]
+            self.pos += nbits
+            return data
+        # We are non-byte aligned, so we need to extract the bits and convert to bytes
+        bytes_as_int = _extract_bits(self.rawdata, self.pos, nbits)
+        self.pos += nbits
+        return int.to_bytes(bytes_as_int, (nbits + 7) // 8, "big")
+
+    def read_as_int(self, nbits: int) -> int:
+        """Read a number of bits from the packet data as an integer.
+
+        Parameters
+        ----------
+        nbits : int
+            Number of bits to read
+
+        Returns
+        -------
+        : int
+            Integer representation of the bits read from the packet
+        """
+        int_data = _extract_bits(self.rawdata, self.pos, nbits)
+        self.pos += nbits
+        return int_data
+
+
+class Parseable(Protocol):
+    """Defines an object that can be parsed from packet data."""
+    def parse(self, packet: Packet, **parse_value_kwargs) -> dict:
+        """Parse this entry from the packet data and add the necessary items to the parsed_items dictionary."""
+
+
+@dataclass
+class SequenceContainer(Parseable):
+    """<xtce:SequenceContainer>
+
+    Parameters
+    ----------
+    name : str
+        Container name
+    entry_list : list
+        List of Parameter objects
+    long_description : str
+        Long description of the container
+    base_container_name : str
+        Name of the base container from which this may inherit if restriction criteria are met.
+    restriction_criteria : list
+        A list of MatchCriteria elements that evaluate to determine whether the SequenceContainer should
+        be included.
+    abstract : bool
+        True if container has abstract=true attribute. False otherwise.
+    inheritors : list, Optional
+        List of SequenceContainer objects that may inherit this one's entry list if their restriction criteria
+        are met. Any SequenceContainers with this container as base_container_name should be listed here.
+    """
+    name: str
+    entry_list: list  # List of Parameter objects, found by reference
+    short_description: Optional[str] = None
+    long_description: Optional[str] = None
+    base_container_name: Optional[str] = None
+    restriction_criteria: Optional[list] = field(default_factory=lambda: [])
+    abstract: bool = False
+    inheritors: Optional[List['SequenceContainer']] = field(default_factory=lambda: [])
+
+    def __post_init__(self):
+        # Handle the explicit None passing for default values
+        self.restriction_criteria = self.restriction_criteria or []
+        self.inheritors = self.inheritors or []
+
+    def parse(self, packet: Packet, **parse_value_kwargs) -> dict:
+        """Parse the entry list of parameters/containers in the order they are expected in the packet.
+
+        This could be recursive if the entry list contains SequenceContainers.
+        """
+        for entry in self.entry_list:
+            packet.parsed_data = entry.parse(packet=packet, **parse_value_kwargs)
+        return packet.parsed_data
+
+
+FlattenedContainer = namedtuple('FlattenedContainer', ['entry_list', 'restrictions'])
+
+
+def _extract_bits(data: bytes, start_bit: int, nbits: int):
+    """Extract nbits from the data starting from the least significant end.
+
+    If data = 00110101 11001010, start_bit = 2, nbits = 9, then the bits extracted are "110101110".
+    Those bits are turned into a Python integer and returned.
+
+    Parameters
+    ----------
+    data : bytes
+        Data to extract bits from
+    start_bit : int
+        Starting bit location within the data
+    nbits : int
+        Number of bits to extract
+
+    Returns
+    -------
+    int
+        Extracted bits as an integer
+    """
+    # Get the bits from the packet data
+    # Select the bytes that contain the bits we want.
+    start_byte = start_bit // 8  # Byte index containing the start_bit
+    start_bit_within_byte = start_bit % 8  # Bit index within the start_byte
+    end_byte = start_byte + (start_bit_within_byte + nbits + 7) // 8
+    data = data[start_byte:end_byte]  # Chunk of bytes containing the data item we want to parse
+    # Convert the bytes to an integer for bitwise operations
+    value = int.from_bytes(data, byteorder="big")
+    if start_bit_within_byte == 0 and nbits % 8 == 0:
+        # If we're extracting whole bytes starting at a byte boundary, we don't need any bitshifting
+        # This is faster, especially for large binary chunks
+        return value
+
+    # Shift the value to the right to move the LSB of the data item we want to parse
+    # to the least significant position, then mask out the number of bits we want to keep
+    return (value >> (len(data) * 8 - start_bit_within_byte - nbits)) & (2 ** nbits - 1)
diff --git a/space_packet_parser/parser.py b/space_packet_parser/parser.py
index 1cbb14b..31506ed 100644
--- a/space_packet_parser/parser.py
+++ b/space_packet_parser/parser.py
@@ -9,7 +9,7 @@
 from typing import BinaryIO, Optional, Tuple, Union
 import warnings
 # Local
-from space_packet_parser import xtcedef, csvdef
+from space_packet_parser import definitions, parseables
 
 logger = logging.getLogger(__name__)
 
@@ -27,10 +27,6 @@
 
 CCSDS_HEADER_LENGTH_BYTES = 6
 
-# Bring this into the namespace for backwards compatibility
-ParsedDataItem = xtcedef.ParsedDataItem
-Packet = xtcedef.Packet
-
 
 class UnrecognizedPacketTypeError(Exception):
     """Error raised when we can't figure out which kind of packet we are dealing with based on the header"""
@@ -50,13 +46,13 @@ class PacketParser:
     """Class for parsing CCSDS packets"""
 
     def __init__(self,
-                 packet_definition: xtcedef.XtcePacketDefinition or csvdef.CsvPacketDefinition,
+                 packet_definition: Union[definitions.XtcePacketDefinition, definitions.CsvPacketDefinition],
                  word_size: int = None):
         """Constructor
 
         Parameters
         ----------
-        packet_definition: xtcedef.XtcePacketDefinition or csvdef.CsvPacketDefinition
+        packet_definition: definitions.XtcePacketDefinition or definitions.CsvPacketDefinition
             The packet definition object to use for parsing incoming data.
         word_size: int, Optional
             Number of bits per word. If set, binary parameters are assumed to end on a word boundary and any unused bits
@@ -83,11 +79,11 @@ def _parse_header(packet_data: bytes) -> dict:
         header = {}
         current_bit = 0
         for item in CCSDS_HEADER_DEFINITION:
-            header[item.name] = xtcedef.ParsedDataItem(
+            header[item.name] = parseables.ParsedDataItem(
                 name=item.name,
                 unit=None,
                 # pylint: disable=protected-access
-                raw_value=xtcedef._extract_bits(packet_data, current_bit, item.nbits))
+                raw_value=parseables._extract_bits(packet_data, current_bit, item.nbits))
             current_bit += item.nbits
         return header
 
@@ -149,15 +145,15 @@ def _determine_packet_by_restrictions(self, parsed_header: dict) -> Tuple[str, l
         # pylint: enable=inconsistent-return-statements
 
     @staticmethod
-    def parse_packet(packet: xtcedef.Packet,
+    def parse_packet(packet: parseables.Packet,
                      containers: dict,
                      root_container_name: str = "CCSDSPacket",
-                     **parse_value_kwargs) -> Packet:
+                     **parse_value_kwargs) -> parseables.Packet:
         """Parse binary packet data according to the self.packet_definition object
 
         Parameters
         ----------
-        packet: xtcedef.Packet
+        packet: packets.Packet
             Binary representation of the packet used to get the coming bits and any
             previously parsed data items to infer field lengths.
         containers : dict
@@ -170,7 +166,7 @@ def parse_packet(packet: xtcedef.Packet,
         Packet
             A Packet object container header and data attributes.
         """
-        current_container: xtcedef.SequenceContainer = containers[root_container_name]
+        current_container: parseables.SequenceContainer = containers[root_container_name]
         while True:
             packet.parsed_data = current_container.parse(packet, **parse_value_kwargs)
 
@@ -199,12 +195,12 @@ def parse_packet(packet: xtcedef.Packet,
         return packet
 
     @staticmethod
-    def legacy_parse_packet(packet: xtcedef.Packet, entry_list: list, **parse_value_kwargs) -> Packet:
+    def legacy_parse_packet(packet: parseables.Packet, entry_list: list, **parse_value_kwargs) -> parseables.Packet:
         """Parse binary packet data according to the self.flattened_containers property
 
         Parameters
         ----------
-        packet : xtcedef.Packet
+        packet : packets.Packet
             Binary packet data to parse into Packets
         entry_list : list
             List of Parameter objects
@@ -218,7 +214,7 @@ def legacy_parse_packet(packet: xtcedef.Packet, entry_list: list, **parse_value_
         for parameter in entry_list[0:7]:
             parsed_value, _ = parameter.parameter_type.parse_value(packet)
 
-            packet.parsed_data[parameter.name] = xtcedef.ParsedDataItem(
+            packet.parsed_data[parameter.name] = parseables.ParsedDataItem(
                 name=parameter.name,
                 unit=parameter.parameter_type.unit,
                 raw_value=parsed_value
@@ -228,7 +224,7 @@ def legacy_parse_packet(packet: xtcedef.Packet, entry_list: list, **parse_value_
             parsed_value, derived_value = parameter.parameter_type.parse_value(
                 packet, **parse_value_kwargs)
 
-            packet.parsed_data[parameter.name] = xtcedef.ParsedDataItem(
+            packet.parsed_data[parameter.name] = parseables.ParsedDataItem(
                 name=parameter.name,
                 unit=parameter.parameter_type.unit,
                 raw_value=parsed_value,
@@ -428,7 +424,7 @@ def read_bytes_from_source(source: Union[BinaryIO, socket.socket],
             if ccsds_headers_only is True:
                 # update the current position to the end of the packet data
                 current_pos += n_bytes_packet
-                yield Packet(read_buffer[current_pos-n_bytes_packet:current_pos], parsed_data=header)
+                yield parseables.Packet(read_buffer[current_pos-n_bytes_packet:current_pos], parsed_data=header)
                 continue
 
             # Based on PKT_LEN fill buffer enough to read a full packet
@@ -442,9 +438,9 @@ def read_bytes_from_source(source: Union[BinaryIO, socket.socket],
             packet_bytes = read_buffer[current_pos:current_pos + n_bytes_packet]
             current_pos += n_bytes_packet
             # Wrap the bytes in a class that can keep track of position as we read from it
-            packet = xtcedef.Packet(packet_bytes)
+            packet = parseables.Packet(packet_bytes)
             try:
-                if isinstance(self.packet_definition, xtcedef.XtcePacketDefinition):
+                if isinstance(self.packet_definition, definitions.XtcePacketDefinition):
                     packet = self.parse_packet(packet,
                                                self.packet_definition.named_containers,
                                                root_container_name=root_container_name,
diff --git a/space_packet_parser/xtcedef.py b/space_packet_parser/xtcedef.py
deleted file mode 100644
index da99fb6..0000000
--- a/space_packet_parser/xtcedef.py
+++ /dev/null
@@ -1,2797 +0,0 @@
-"""Module for parsing XTCE xml files to specify packet format"""
-# Standard
-from abc import ABCMeta, abstractmethod
-from collections import namedtuple
-from dataclasses import dataclass, field
-import inspect
-import logging
-import struct
-from typing import Tuple, Union, Optional, Protocol, Any, List, TextIO, Dict
-import warnings
-# Installed
-import lxml.etree as ElementTree
-
-logger = logging.getLogger(__name__)
-
-
-# Exceptions
-class ElementNotFoundError(Exception):
-    """Exception for missing XML element"""
-    pass
-
-
-class ComparisonError(Exception):
-    """Exception for problems performing comparisons"""
-    pass
-
-
-class FormatStringError(Exception):
-    """Error indicating a problem determining how to parse a variable length string."""
-    pass
-
-
-class DynamicLengthBinaryParameterError(Exception):
-    """Exception to raise when we try to parse a dynamic length binary field as fixed length"""
-    pass
-
-
-class CalibrationError(Exception):
-    """For errors encountered during value calibration"""
-    pass
-
-
-class InvalidParameterTypeError(Exception):
-    """Error raised when someone is using an invalid ParameterType element"""
-    pass
-
-
-# Common comparable mixin
-class AttrComparable(metaclass=ABCMeta):
-    """Generic class that provides a notion of equality based on all non-callable, non-dunder attributes"""
-
-    def __eq__(self, other):
-        if not isinstance(other, self.__class__):
-            raise NotImplementedError(f"No method to compare {type(other)} with {self.__class__}")
-
-        compare = inspect.getmembers(self, lambda a: not inspect.isroutine(a))
-        compare = [attr[0] for attr in compare
-                   if not (attr[0].startswith('__') or attr[0].startswith(f'_{self.__class__.__name__}__'))]
-        for attr in compare:
-            if getattr(self, attr) != getattr(other, attr):
-                print(f'Mismatch was in {attr}. {getattr(self, attr)} != {getattr(other, attr)}')
-                return False
-        return True
-
-
-@dataclass
-class ParsedDataItem:
-    """Representation of a parsed parameter
-
-    Parameters
-    ----------
-    name : str
-        Parameter name
-    unit : str
-        Parameter units
-    raw_value : any
-        Raw representation of the parsed value. May be lots of different types but most often an integer
-    derived_value : float or str
-        May be a calibrated value or an enum lookup
-    short_description : str
-        Parameter short description
-    long_description : str
-        Parameter long description
-    """
-    name: str
-    raw_value: Union[bytes, float, int, str]
-    unit: Optional[str] = None
-    derived_value: Optional[Union[float, str]] = None
-    short_description: Optional[str] = None
-    long_description: Optional[str] = None
-
-
-# Matching logical objects
-class MatchCriteria(AttrComparable, metaclass=ABCMeta):
-    """<xtce:MatchCriteriaType>
-    This class stores criteria for performing logical operations based on parameter values
-    Classes that inherit from this ABC include those that represent <xtce:Comparison>, <xtce:ComparisonList>,
-    <xtce:BooleanExpression> (not supported), and <xtce:CustomAlgorithm> (not supported)
-    """
-
-    # Valid operator representations in XML. Note: the XTCE spec only allows for &gt; style representations of < and >
-    #   Python's XML parser doesn't appear to support &eq; &ne; &le; or &ge;
-    # We have implemented support for bash-style comparisons just in case.
-    _valid_operators = {
-        "==": "__eq__", "eq": "__eq__",  # equal to
-        "!=": "__ne__", "neq": "__ne__",  # not equal to
-        "&lt;": "__lt__", "lt": "__lt__", "<": "__lt__",  # less than
-        "&gt;": "__gt__", "gt": "__gt__", ">": "__gt__",  # greater than
-        "&lt;=": "__le__", "leq": "__le__", "<=": "__le__",  # less than or equal to
-        "&gt;=": "__ge__", "geq": "__ge__", ">=": "__ge__",  # greater than or equal to
-    }
-
-    @classmethod
-    @abstractmethod
-    def from_match_criteria_xml_element(cls, element: ElementTree.Element, ns: dict):
-        """Abstract classmethod to create a match criteria object from an XML element.
-
-        Parameters
-        ----------
-        element : ElementTree.Element
-            XML element
-        ns : dict
-            XML namespace dict
-
-        Returns
-        -------
-        : cls
-        """
-        raise NotImplementedError()
-
-    @abstractmethod
-    def evaluate(self, parsed_data: dict, current_parsed_value: Optional[Union[int, float]] = None) -> bool:
-        """Evaluate match criteria down to a boolean.
-
-        Parameters
-        ----------
-        parsed_data : dict
-            Dictionary of parsed parameter data so far. Used to evaluate truthyness of the match criteria.
-        current_parsed_value : any, Optional
-            Uncalibrated value that is currently being matched (e.g. as a candidate for calibration).
-            Used to resolve comparisons that reference their own raw value as a condition.
-
-        Returns
-        -------
-        : bool
-            Truthyness of this match criteria based on parsed_data values.
-        """
-        raise NotImplementedError()
-
-
-class Comparison(MatchCriteria):
-    """<xtce:Comparison>"""
-
-    def __init__(self, required_value: any, referenced_parameter: str,
-                 operator: str = "==", use_calibrated_value: bool = True):
-        """Constructor
-
-        Parameters
-        ----------
-        operator : str
-            String representation of the comparison operation. e.g. "<=" or "leq"
-        required_value : any
-            Value with which to compare the referenced parameter using the operator. This value is dynamically
-            coerced to the referenced parameter type during evaluation.
-        referenced_parameter : str
-            Name of the parameter to compare with the value.
-        use_calibrated_value : bool
-            Whether or not to calibrate the value before performing the comparison.
-        """
-        self.required_value = required_value
-        self.referenced_parameter = referenced_parameter
-        self.operator = operator
-        self.use_calibrated_value = use_calibrated_value
-        self._validate()
-
-    def __repr__(self):
-        return f"<{self.__class__.__name__} {self.referenced_parameter}{self.operator}{self.required_value}>"
-
-    def _validate(self):
-        """Validate state as logically consistent.
-
-        Returns
-        -------
-        None
-        """
-        if self.operator not in self._valid_operators:
-            raise ValueError(f"Unrecognized operator syntax {self.operator}. "
-                             f"Must be one of "
-                             f"{set(self._valid_operators.keys())}")
-
-    @classmethod
-    def from_match_criteria_xml_element(cls, element: ElementTree.Element, ns: dict) -> 'Comparison':
-        """Create
-
-        Parameters
-        ----------
-        element : ElementTree.Element
-            XML element
-        ns : dict
-            XML namespace dict
-
-        Returns
-        -------
-        : Comparison
-        """
-        use_calibrated_value = True  # Default
-        if 'useCalibratedValue' in element.attrib:
-            use_calibrated_value = element.attrib['useCalibratedValue'].lower() == 'true'
-
-        value = element.attrib['value']
-
-        parameter_name = element.attrib['parameterRef']
-        operator = '=='
-        if 'comparisonOperator' in element.attrib:
-            operator = element.attrib['comparisonOperator']
-
-        return cls(value, parameter_name, operator=operator, use_calibrated_value=use_calibrated_value)
-
-    def evaluate(self, parsed_data: dict, current_parsed_value: Optional[Union[int, float]] = None) -> bool:
-        """Evaluate comparison down to a boolean. If the parameter to compare is not present in the parsed_data dict,
-        we assume that we are comparing against the current raw value in current_parsed_value.
-
-        Parameters
-        ----------
-        parsed_data : dict
-            Dictionary of parsed parameter data so far. Used to evaluate truthyness of the match criteria.
-        current_parsed_value : Union[int, float]
-            Optional. Uncalibrated value that is currently a candidate for calibration and so has not yet been added
-            to the parsed_data dict. Used to resolve calibrator conditions that reference their own
-            raw value as a comparate.
-
-        Returns
-        -------
-        : bool
-            Truthyness of this match criteria based on parsed_data values.
-        """
-        if self.referenced_parameter in parsed_data:
-            if self.use_calibrated_value:
-                parsed_value = parsed_data[self.referenced_parameter].derived_value
-                if not parsed_value:
-                    raise ComparisonError(f"Comparison {self} was instructed to useCalibratedValue (the default)"
-                                          f"but {self.referenced_parameter} does not appear to have a derived value.")
-            else:
-                parsed_value = parsed_data[self.referenced_parameter].raw_value
-        elif current_parsed_value is not None:
-            # Assume then that the comparison is a reference to its own uncalibrated value
-            parsed_value = current_parsed_value
-            if self.use_calibrated_value:
-                warnings.warn("Performing a comparison against a current value (e.g. a Comparison within a "
-                              "context calibrator contains a reference to its own uncalibrated value but use_"
-                              "calibrated_value is set to true. This is nonsensical. Using the uncalibrated value...")
-        else:
-            raise ValueError("Attempting to resolve a Comparison expression but the referenced parameter does not "
-                             "appear in the parsed data so far and no current raw value was passed "
-                             "to compare with.")
-
-        operator = self._valid_operators[self.operator]
-        t_comparate = type(parsed_value)
-        try:
-            required_value = t_comparate(self.required_value)
-        except ValueError as err:
-            raise ComparisonError(f"Unable to coerce {self.required_value} of type {type(self.required_value)} to "
-                                  f"type {t_comparate} for comparison evaluation.") from err
-        if required_value is None or parsed_value is None:
-            raise ValueError(f"Error in Comparison. Cannot compare {required_value} with {parsed_value}. "
-                             "Neither should be None.")
-
-        # x.__le__(y) style call
-        return getattr(parsed_value, operator)(required_value)
-
-
-class Condition(MatchCriteria):
-    """<xtce:Condition>
-    Note: This xtce model doesn't actually inherit from MatchCriteria in the UML model
-    but it's functionally close enough that we inherit the class here.
-    """
-
-    def __init__(self,
-                 left_param: str,
-                 operator: str,
-                 right_param: Optional[str] = None,
-                 right_value: Optional[Any] = None,
-                 left_use_calibrated_value: bool = True,
-                 right_use_calibrated_value: bool = True):
-        """Constructor
-
-        Parameters
-        ----------
-        left_param : str
-            Parameter name on the LH side of the comparison
-        operator : str
-            Member of MatchCriteria._valid_operators.
-        right_param : Optional[str]
-            Parameter name on the RH side of the comparison.
-        right_value: Optional[Any]
-            Used in case of comparison with a fixed xtce:Value on the RH side.
-        left_use_calibrated_value : bool, Optional
-            Default is True. If False, comparison is made against the uncalibrated value.
-        right_use_calibrated_value: bool, Optional
-            Default is True. If False, comparison is made against the uncalibrated value.
-        """
-        self.left_param = left_param
-        self.right_param = right_param
-        self.right_value = right_value
-        self.operator = operator
-        self.right_use_calibrated_value = right_use_calibrated_value
-        self.left_use_calibrated_value = left_use_calibrated_value
-        self._validate()
-
-    def _validate(self):
-        """Check that the instantiated object actually makes logical sense.
-
-        Returns
-        -------
-        None
-        """
-        if self.operator not in self._valid_operators:
-            raise ValueError(f"Unrecognized operator syntax {self.operator}. "
-                             f"Must be one of "
-                             f"{set(self._valid_operators.keys())}")
-        if self.right_param and self.right_value:
-            raise ComparisonError(f"Received both a right_value and a right_param reference to Condition {self}.")
-        if self.right_value and self.right_use_calibrated_value:
-            raise ComparisonError(f"Unable to use calibrated form of a fixed value in Condition {self}.")
-
-    @staticmethod
-    def _parse_parameter_instance_ref(element: ElementTree.Element):
-        """Parse an xtce:ParameterInstanceRef element
-
-        Parameters
-        ----------
-        element: ElementTree.Element
-            xtce:ParameterInstanceRef element
-
-        Returns
-        -------
-        parameter_name: str
-            Name of referenced parameter
-        use_calibrated_value: bool
-            Whether to use the calibrated form of the referenced parameter
-        """
-        parameter_name = element.attrib['parameterRef']
-        use_calibrated_value = True  # Default
-        if 'useCalibratedValue' in element.attrib:
-            use_calibrated_value = element.attrib['useCalibratedValue'].lower() == 'true'
-        return parameter_name, use_calibrated_value
-
-    @classmethod
-    def from_match_criteria_xml_element(cls, element: ElementTree.Element, ns: dict):
-        """Classmethod to create a Condition object from an XML element.
-
-        Parameters
-        ----------
-        element : ElementTree.Element
-            XML element
-        ns : dict
-            XML namespace dict
-
-        Returns
-        -------
-        : cls
-        """
-        operator = element.find('xtce:ComparisonOperator', ns).text
-        params = element.findall('xtce:ParameterInstanceRef', ns)
-        if len(params) == 1:
-            left_param, use_calibrated_value = cls._parse_parameter_instance_ref(params[0])
-            right_value = element.find('xtce:Value', ns).text
-            return cls(left_param, operator, right_value=right_value,
-                       left_use_calibrated_value=use_calibrated_value,
-                       right_use_calibrated_value=False)
-        if len(params) == 2:
-            left_param, left_use_calibrated_value = cls._parse_parameter_instance_ref(params[0])
-            right_param, right_use_calibrated_value = cls._parse_parameter_instance_ref(params[1])
-            return cls(left_param, operator, right_param=right_param,
-                       left_use_calibrated_value=left_use_calibrated_value,
-                       right_use_calibrated_value=right_use_calibrated_value)
-        raise ValueError(f'Failed to parse a Condition element {element}. '
-                         'See 3.4.3.4.2 of XTCE Green Book CCSDS 660.1-G-2')
-
-    def evaluate(self, parsed_data: dict, current_parsed_value: Optional[Union[int, float]] = None) -> bool:
-        """Evaluate match criteria down to a boolean.
-
-        Parameters
-        ----------
-        parsed_data : dict
-            Dictionary of parsed parameter data so far. Used to evaluate truthyness of the match criteria.
-        current_parsed_value : Optional[Union[int, float]]
-            Current value being parsed. NOTE: This is currently ignored. See the TODO item below.
-
-        Returns
-        -------
-        : bool
-            Truthyness of this match criteria based on parsed_data values.
-        """
-
-        def _get_parsed_value(parameter_name: str, use_calibrated: bool):
-            """Retrieves the previously parsed value from the passed in parsed_data"""
-            try:
-                return parsed_data[parameter_name].derived_value if use_calibrated \
-                    else parsed_data[parameter_name].raw_value
-            except KeyError as e:
-                raise ComparisonError(f"Attempting to perform a Condition evaluation on {self.left_param} but "
-                                      "the referenced parameter does not appear in the hitherto parsed data passed to "
-                                      "the evaluate method. If you intended a comparison against the raw value of the "
-                                      "parameter currently being parsed, unfortunately that is not currently supported."
-                                      ) from e
-
-        # TODO: Consider allowing one of the parameters to be the parameter currently being evaluated.
-        #    This isn't explicitly provided for in the XTCE spec but it seems reasonable to be able to
-        #    perform conditionals against the current raw value of a parameter, e.g. while determining if it
-        #    should be calibrated. Note that only one of the parameters can be used this way and it must reference
-        #    an uncalibrated value so the logic and error handling must be done carefully.
-        left_value = _get_parsed_value(self.left_param, self.left_use_calibrated_value)
-        # Convert XML operator representation to a python-compatible operator (e.g. '&gt;' to '__gt__')
-        operator = self._valid_operators[self.operator]
-
-        if self.right_param is not None:
-            right_value = _get_parsed_value(self.right_param, self.right_use_calibrated_value)
-        elif self.right_value is not None:
-            t_left_param = type(left_value)  # Coerce right value xml representation to correct type
-            right_value = t_left_param(self.right_value)
-        else:
-            raise ValueError(f"Error when evaluating condition {self}. Neither right_param nor right_value is set.")
-        if left_value is None or right_value is None:
-            raise ComparisonError(f"Error comparing {left_value} and {right_value}. Neither should be None.")
-
-        # x.__le__(y) style call
-        return getattr(left_value, operator)(right_value)
-
-
-Anded = namedtuple('Anded', ['conditions', 'ors'])
-Ored = namedtuple('Ored', ['conditions', 'ands'])
-
-
-class BooleanExpression(MatchCriteria):
-    """<xtce:BooleanExpression>"""
-
-    def __init__(self, expression: Union[Condition, Anded, Ored]):
-        self.expression = expression
-
-    @classmethod
-    def from_match_criteria_xml_element(cls, element: ElementTree.Element, ns: dict) -> 'BooleanExpression':
-        """Abstract classmethod to create a match criteria object from an XML element.
-
-        Parameters
-        ----------
-        element : ElementTree.Element
-           XML element
-        ns : dict
-           XML namespace dict
-
-        Returns
-        -------
-        : BooleanExpression
-        """
-
-        def _parse_anded(anded_el: ElementTree.Element) -> Anded:
-            """Create an Anded object from an xtce:ANDedConditions element
-
-            Parameters
-            ----------
-            anded_el: ElementTree.Element
-                xtce:ANDedConditions element
-
-            Returns
-            -------
-            : Anded
-            """
-            conditions = [Condition.from_match_criteria_xml_element(el, ns)
-                          for el in anded_el.findall('xtce:Condition', ns)]
-            anded_ors = [_parse_ored(anded_or) for anded_or in anded_el.findall('xtce:ORedConditions', ns)]
-            return Anded(conditions, anded_ors)
-
-        def _parse_ored(ored_el: ElementTree.Element) -> Ored:
-            """Create an Ored object from an xtce:ARedConditions element
-
-            Parameters
-            ----------
-            ored_el: ElementTree.Element
-                xtce:ORedConditions element
-
-            Returns
-            -------
-            : Ored
-            """
-            conditions = [Condition.from_match_criteria_xml_element(el, ns)
-                          for el in ored_el.findall('xtce:Condition', ns)]
-            ored_ands = [_parse_anded(ored_and) for ored_and in ored_el.findall('xtce:ANDedConditions', ns)]
-            return Ored(conditions, ored_ands)
-
-        if element.find('xtce:Condition', ns) is not None:
-            condition = Condition.from_match_criteria_xml_element(element.find('xtce:Condition', ns), ns)
-            return cls(expression=condition)
-        if element.find('xtce:ANDedConditions', ns) is not None:
-            return cls(expression=_parse_anded(element.find('xtce:ANDedConditions', ns)))
-        if element.find('xtce:ORedConditions', ns) is not None:
-            return cls(expression=_parse_ored(element.find('xtce:ORedConditions', ns)))
-        raise ValueError(f"Failed to parse {element}")
-
-    def evaluate(self, parsed_data: dict, current_parsed_value: Optional[Union[int, float]] = None) -> bool:
-        """Evaluate the criteria in the BooleanExpression down to a single boolean.
-
-        Parameters
-        ----------
-        parsed_data : dict
-            Dictionary of parsed parameter data so far. Used to evaluate truthyness of the match criteria.
-        current_parsed_value : Optional[Union[int, float]]
-            Current value being parsed.
-
-        Returns
-        -------
-        : bool
-            Truthyness of this match criteria based on parsed_data values.
-        """
-
-        def _or(ored: Ored):
-            for condition in ored.conditions:
-                if condition.evaluate(parsed_data) is True:
-                    return True
-            for anded in ored.ands:
-                if _and(anded):
-                    return True
-            return False
-
-        def _and(anded: Anded):
-            for condition in anded.conditions:
-                if condition.evaluate(parsed_data) is False:
-                    return False
-            for ored in anded.ors:
-                if not _or(ored):
-                    return False
-            return True
-
-        if isinstance(self.expression, Condition):
-            return self.expression.evaluate(parsed_data)
-        if isinstance(self.expression, Anded):
-            return _and(self.expression)
-        if isinstance(self.expression, Ored):
-            return _or(self.expression)
-
-        raise ValueError(f"Error evaluating an unknown expression {self.expression}.")
-
-
-class DiscreteLookup(AttrComparable):
-    """<xtce:DiscreteLookup>"""
-
-    def __init__(self, match_criteria: list, lookup_value: Union[int, float]):
-        """Constructor
-
-        Parameters
-        ----------
-        match_criteria : list
-            List of criteria to determine if the lookup value should be returned during evaluation.
-        lookup_value : Union[int, float]
-            Value to return from the lookup if the criteria evaluate true
-        """
-        self.match_criteria = match_criteria
-        self.lookup_value = lookup_value
-
-    @classmethod
-    def from_discrete_lookup_xml_element(cls, element: ElementTree.Element, ns: dict) -> 'DiscreteLookup':
-        """Create a DiscreteLookup object from an <xtce:DiscreteLookup> XML element
-
-        Parameters
-        ----------
-        element : ElementTree.Element
-            <xtce:DiscreteLookup> XML element from which to parse the DiscreteLookup object.
-        ns : dict
-            Namespace dict for XML parsing
-
-        Returns
-        -------
-        : DiscreteLookup
-        """
-        lookup_value = float(element.attrib['value'])
-        if element.find('xtce:ComparisonList', ns) is not None:
-            match_criteria = [Comparison.from_match_criteria_xml_element(el, ns)
-                              for el in element.findall('xtce:ComparisonList/xtce:Comparison', ns)]
-        elif element.find('xtce:Comparison', ns) is not None:
-            match_criteria = [Comparison.from_match_criteria_xml_element(
-                element.find('xtce:Comparison', ns), ns)]
-        else:
-            raise NotImplementedError("Only Comparison and ComparisonList are implemented for DiscreteLookup.")
-
-        return cls(match_criteria, lookup_value)
-
-    def evaluate(self, parsed_data: dict, current_parsed_value: Optional[Union[int, float]] = None) -> Any:
-        """Evaluate the lookup to determine if it is valid.
-
-        Parameters
-        ----------
-        parsed_data : dict
-            Data parsed so far (for referencing during criteria evaluation).
-        current_parsed_value: Optional[Union[int, float]]
-            If referenced parameter in criterion isn't in parsed_data dict, we assume we are comparing against this
-            currently parsed value.
-
-        Returns
-        -------
-        : any
-            Return the lookup value if the match criteria evaluate true. Return None otherwise.
-        """
-        if all(criterion.evaluate(parsed_data, current_parsed_value) for criterion in self.match_criteria):
-            # If the parsed data so far satisfy all the match criteria
-            return self.lookup_value
-        return None
-
-
-# Calibrator definitions
-class Calibrator(AttrComparable, metaclass=ABCMeta):
-    """Abstract base class for XTCE calibrators"""
-
-    @classmethod
-    @abstractmethod
-    def from_calibrator_xml_element(cls, element: ElementTree.Element, ns: dict) -> 'Calibrator':
-        """Abstract classmethod to create a default_calibrator object from an XML element.
-
-        Parameters
-        ----------
-        element : ElementTree.Element
-            XML element
-        ns : dict
-            XML namespace dict
-
-        Returns
-        -------
-        cls
-        """
-        return NotImplemented
-
-    @abstractmethod
-    def calibrate(self, uncalibrated_value: Union[int, float]) -> Union[int, float]:
-        """Takes an integer-encoded or float-encoded value and returns a calibrated version.
-
-        Parameters
-        ----------
-        uncalibrated_value : Union[int, float]
-            The uncalibrated, raw encoded value
-
-        Returns
-        -------
-        : Union[int, float]
-            Calibrated value
-        """
-        raise NotImplementedError
-
-
-SplinePoint = namedtuple('SplinePoint', ['raw', 'calibrated'])
-
-
-class SplineCalibrator(Calibrator):
-    """<xtce:SplineCalibrator>"""
-    _order_mapping = {'zero': 0, 'first': 1, 'second': 2, 'third': 3}
-
-    def __init__(self, points: list, order: int = 0, extrapolate: bool = False):
-        """Constructor
-
-        Parameters
-        ----------
-        points : list
-            List of SplinePoint objects. These points are sorted by their raw values on instantiation.
-        order : int
-            Spline order. Only zero and first order splines are supported.
-        extrapolate : bool
-            Whether or not to allow extrapolation outside the bounds of the spline points. If False, raises an
-            error when calibrate is called for a query point outside the bounds of the spline points.
-        """
-        if order > 1:
-            raise NotImplementedError("Spline calibrators of order > 1 are not implemented. Consider contributing "
-                                      "if you need this functionality. It does not appear to be commonly used but "
-                                      "it probably would not be too hard to implement.")
-        self.order = order
-        self.points = sorted(points, key=lambda point: point.raw)  # Sort points before storing
-        self.extrapolate = extrapolate
-
-    @classmethod
-    def from_calibrator_xml_element(cls, element: ElementTree.Element, ns: dict) -> 'SplineCalibrator':
-        """Create a spline default_calibrator object from an <xtce:SplineCalibrator> XML element."""
-        point_elements = element.findall('xtce:SplinePoint', ns)
-        spline_points = [
-            SplinePoint(raw=float(p.attrib['raw']), calibrated=float(p.attrib['calibrated']))
-            for p in point_elements
-        ]
-        order = int(cls._order_mapping[element.attrib['order']]) if 'order' in element.attrib else 0
-        extrapolate = element.attrib['extrapolate'].lower() == 'true' if 'extrapolate' in element.attrib else False
-        return cls(order=order, points=spline_points, extrapolate=extrapolate)
-
-    def calibrate(self, uncalibrated_value: float) -> float:
-        """Take an integer-encoded value and returns a calibrated version according to the spline points.
-
-        Parameters
-        ----------
-        uncalibrated_value : float
-            Query point.
-
-        Returns
-        -------
-        : float
-            Calibrated value
-        """
-        if self.order == 0:
-            return self._zero_order_spline_interp(uncalibrated_value)
-        if self.order == 1:
-            return self._first_order_spline_interp(uncalibrated_value)
-        raise NotImplementedError(f"SplineCalibrator is not implemented for spline order {self.order}.")
-
-    def _zero_order_spline_interp(self, query_point: float) -> float:
-        """Abstraction for zero order spline interpolation. If extrapolation is set to a truthy value, we use
-        the nearest point to extrapolate outside the range of the given spline points. Within the range of spline
-        points, we use nearest lower point interpolation.
-
-        Parameters
-        ----------
-        query_point : float
-            Query point.
-
-        Returns
-        -------
-        : float
-            Calibrated value.
-        """
-        x = [float(p.raw) for p in self.points]
-        y = [float(p.calibrated) for p in self.points]
-        if min(x) <= query_point <= max(x):
-            first_greater = [p.raw > query_point for p in self.points].index(True)
-            return y[first_greater - 1]
-        if query_point > max(x) and self.extrapolate:
-            return y[-1]
-        if query_point < min(x) and self.extrapolate:
-            return y[0]
-        raise CalibrationError(f"Extrapolation is set to a falsy value ({self.extrapolate}) but query value "
-                               f"{query_point} falls outside the range of spline points {self.points}")
-
-    def _first_order_spline_interp(self, query_point: float) -> float:
-        """Abstraction for first order spline interpolation. If extrapolation is set to a truthy value, we use the
-        end points to make a linear function and use it to extrapolate.
-
-        Parameters
-        ----------
-        query_point : float
-            Query point.
-
-        Returns
-        -------
-        float
-            Calibrated value.
-        """
-
-        def linear_func(xq: float, x0: float, x1: float, y0: float, y1: float) -> float:
-            """Evaluate a linear function through points (x0, y0), (x1, y1) at point xq
-
-            Parameters
-            ----------
-            xq : float
-            x0 : float
-            x1 : float
-            y0 : float
-            y1 : float
-
-            Returns
-            -------
-            yq : float
-                Interpolated point
-            """
-            slope = (y1 - y0) / (x1 - x0)
-            return (slope * (xq - x0)) + y0
-
-        x = [p.raw for p in self.points]
-        y = [p.calibrated for p in self.points]
-        if min(x) <= query_point <= max(x):
-            first_greater = [p.raw > query_point for p in self.points].index(True)
-            return linear_func(query_point,
-                               x[first_greater - 1], x[first_greater],
-                               y[first_greater - 1], y[first_greater])
-        if query_point > max(x) and self.extrapolate:
-            return linear_func(query_point, x[-2], x[-1], y[-2], y[-1])
-        if query_point < min(x) and self.extrapolate:
-            return linear_func(query_point, x[0], x[1], y[0], y[1])
-        raise CalibrationError(f"Extrapolation is set to a falsy value ({self.extrapolate}) but query value "
-                               f"{query_point} falls outside the range of spline points {self.points}")
-
-
-PolynomialCoefficient = namedtuple('PolynomialCoefficient', ['coefficient', 'exponent'])
-
-
-class PolynomialCalibrator(Calibrator):
-    """<xtce:PolynomialCalibrator>"""
-
-    def __init__(self, coefficients: list):
-        """Constructor
-
-        Parameters
-        ----------
-        coefficients : list
-            List of PolynomialCoefficient objects that define the polynomial.
-        """
-        self.coefficients = coefficients  # Coefficients should be a list of PolynomialCoefficients
-
-    @classmethod
-    def from_calibrator_xml_element(cls, element: ElementTree.Element, ns: dict) -> 'PolynomialCalibrator':
-        """Create a polynomial default_calibrator object from an <xtce:PolynomialCalibrator> XML element.
-
-        Parameters
-        ----------
-        element : ElementTree.Element
-            <xtce:PolynomialCalibrator> XML element
-        ns : dict
-            Namespace dict
-
-        Returns
-        -------
-
-        """
-        terms = element.findall('xtce:Term', ns)
-        coefficients = [
-            PolynomialCoefficient(coefficient=float(term.attrib['coefficient']), exponent=int(term.attrib['exponent']))
-            for term in terms
-        ]
-        return cls(coefficients=coefficients)
-
-    def calibrate(self, uncalibrated_value: float) -> float:
-        """Evaluate the polynomial defined by object coefficients at the specified uncalibrated point.
-
-        Parameters
-        ----------
-        uncalibrated_value : float
-            Query point.
-
-        Returns
-        -------
-        float
-            Calibrated value
-        """
-        return sum(a * (uncalibrated_value ** n) for a, n in self.coefficients)
-
-
-class MathOperationCalibrator(Calibrator):
-    """<xtce:MathOperationCalibrator>"""
-    err_msg = "The MathOperationCalibrator element is not supported in this package but pull requests are welcome!"
-
-    def __init__(self):
-        """Constructor
-
-        Not implemented.
-        """
-        raise NotImplementedError(self.err_msg)
-
-    @classmethod
-    def from_calibrator_xml_element(cls, element: ElementTree.Element, ns: dict) -> 'MathOperationCalibrator':
-        """Create a math operation default_calibrator from an <xtce:MathOperationCalibrator> XML element."""
-        raise NotImplementedError(cls.err_msg)
-
-    def calibrate(self, uncalibrated_value: int):
-        """Stub
-
-        Parameters
-        ----------
-        uncalibrated_value
-
-        Returns
-        -------
-
-        """
-        raise NotImplementedError(self.err_msg)
-
-
-class ContextCalibrator(AttrComparable):
-    """<xtce:ContextCalibrator>"""
-
-    def __init__(self, match_criteria: list, calibrator: Calibrator):
-        """Constructor
-
-        Parameters
-        ----------
-        match_criteria : Union[MatchCriteria, list]
-            Object representing the logical operations to be performed to determine whether to use this
-            default_calibrator. This can be a Comparison, a ComparsonList (a list of Comparison objects),
-            a BooleanExpression (not supported), or a CustomAlgorithm (not supported)
-        calibrator : Calibrator
-            Calibrator to use if match criteria evaluates to True
-        """
-        self.match_criteria = match_criteria
-        self.calibrator = calibrator
-
-    @staticmethod
-    def get_context_match_criteria(element: ElementTree.Element, ns: dict) -> List[MatchCriteria]:
-        """Parse contextual requirements from a Comparison, ComparisonList, or BooleanExpression
-
-        Parameters
-        ----------
-        element : ElementTree.Element
-            <xtce:ContextCalibrator> XML element from which to parse the ContextCalibrator object.
-        ns : dict
-            Namespace dict for XML parsing
-
-        Returns
-        -------
-        : List[MatchCriteria]
-            List of Comparisons that can be evaluated to determine whether this calibrator should be used.
-        """
-        context_match_element = element.find('xtce:ContextMatch', ns)
-        if context_match_element.find('xtce:ComparisonList', ns) is not None:
-            return [Comparison.from_match_criteria_xml_element(el, ns)
-                    for el in context_match_element.findall('xtce:ComparisonList/xtce:Comparison', ns)]
-        if context_match_element.find('xtce:Comparison', ns) is not None:
-            return [Comparison.from_match_criteria_xml_element(
-                context_match_element.find('xtce:Comparison', ns), ns)]
-        if context_match_element.find('xtce:BooleanExpression', ns) is not None:
-            return [BooleanExpression.from_match_criteria_xml_element(
-                context_match_element.find('xtce:BooleanExpression', ns), ns)]
-        raise NotImplementedError("ContextCalibrator doesn't contain Comparison, ComparisonList, or BooleanExpression. "
-                                  "This probably means the match criteria is an unsupported type "
-                                  "(CustomAlgorithm).")
-
-    @classmethod
-    def from_context_calibrator_xml_element(cls, element: ElementTree.Element, ns: dict) -> 'ContextCalibrator':
-        """Create a ContextCalibrator object from an <xtce:ContextCalibrator> XML element
-
-        Parameters
-        ----------
-        element : ElementTree.Element
-            <xtce:ContextCalibrator> XML element from which to parse the ContextCalibrator object.
-        ns : dict
-            Namespace dict for XML parsing
-
-        Returns
-        -------
-        : cls
-        """
-        match_criteria = cls.get_context_match_criteria(element, ns)
-
-        if element.find('xtce:Calibrator/xtce:SplineCalibrator', ns) is not None:
-            calibrator = SplineCalibrator.from_calibrator_xml_element(
-                element.find('xtce:Calibrator/xtce:SplineCalibrator', ns), ns)
-        elif element.find('xtce:Calibrator/xtce:PolynomialCalibrator', ns):
-            calibrator = PolynomialCalibrator.from_calibrator_xml_element(
-                element.find('xtce:Calibrator/xtce:PolynomialCalibrator', ns), ns)
-        else:
-            raise NotImplementedError(
-                "Unsupported default_calibrator type. space_packet_parser only supports Polynomial and Spline"
-                "calibrators for ContextCalibrators.")
-
-        return cls(match_criteria=match_criteria, calibrator=calibrator)
-
-    def calibrate(self, parsed_value: Union[int, float]) -> Union[int, float]:
-        """Wrapper method for the internal `Calibrator.calibrate`
-
-        Parameters
-        ----------
-        parsed_value : Union[int, float]
-            Uncalibrated value.
-
-        Returns
-        -------
-        : Union[int, float]
-            Calibrated value
-        """
-        return self.calibrator.calibrate(parsed_value)
-
-
-@dataclass
-class Packet:
-    """CCSDS Packet
-
-    Can be parsed to populate data items. This ``Packet`` class keeps track
-    of the current parsing position for know where to read from next when
-    parsing data items.
-
-    Parameters
-    ----------
-    data : bytes
-        The binary data for a single packet.
-    pos : int
-        The bit cursor position in the packet. Default 0.
-    """
-    rawdata: bytes
-    pos: Optional[int] = 0
-    parsed_data: Optional[dict] = field(default_factory=lambda: {})
-
-    def __len__(self):
-        """The length of the full packet data object in bits."""
-        return len(self.rawdata) * 8
-
-    @property
-    def header(self):
-        """Parsed header data items."""
-        return dict(list(self.parsed_data.items())[:7])
-
-    @property
-    def data(self):
-        """Parsed user data items."""
-        return dict(list(self.parsed_data.items())[7:])
-
-    def read_as_bytes(self, nbits: int) -> bytes:
-        """Read a number of bits from the packet data as bytes.
-
-        Parameters
-        ----------
-        nbits : int
-            Number of bits to read
-
-        Returns
-        -------
-        : bytes
-            Raw bytes from the packet data
-        """
-        if self.pos + nbits > len(self):
-            raise ValueError("End of packet reached")
-        if self.pos % 8 == 0 and nbits % 8 == 0:
-            # If the read is byte-aligned, we can just return the bytes directly
-            data = self.rawdata[self.pos//8:self.pos//8 + nbits // 8]
-            self.pos += nbits
-            return data
-        # We are non-byte aligned, so we need to extract the bits and convert to bytes
-        bytes_as_int = _extract_bits(self.rawdata, self.pos, nbits)
-        self.pos += nbits
-        return int.to_bytes(bytes_as_int, (nbits + 7) // 8, "big")
-
-    def read_as_int(self, nbits: int) -> int:
-        """Read a number of bits from the packet data as an integer.
-
-        Parameters
-        ----------
-        nbits : int
-            Number of bits to read
-
-        Returns
-        -------
-        : int
-            Integer representation of the bits read from the packet
-        """
-        int_data = _extract_bits(self.rawdata, self.pos, nbits)
-        self.pos += nbits
-        return int_data
-
-
-# DataEncoding definitions
-class DataEncoding(AttrComparable, metaclass=ABCMeta):
-    """Abstract base class for XTCE data encodings"""
-
-    @classmethod
-    @abstractmethod
-    def from_data_encoding_xml_element(cls, element: ElementTree.Element, ns: dict) -> 'DataEncoding':
-        """Abstract classmethod to create a data encoding object from an XML element.
-
-        Parameters
-        ----------
-        element : ElementTree.Element
-            XML element
-        ns : dict
-            XML namespace dict
-
-        Returns
-        -------
-        : DataEncoding
-        """
-        return NotImplemented
-
-    @staticmethod
-    def get_default_calibrator(data_encoding_element: ElementTree.Element, ns: dict) -> Union[Calibrator, None]:
-        """Gets the default_calibrator for the data encoding element
-
-        Parameters
-        ----------
-        data_encoding_element : ElementTree.Element
-            The data encoding element which should contain the default_calibrator
-        ns : dict
-            XML namespace dict
-
-        Returns
-        -------
-        : Union[Calibrator, None]
-        """
-        for calibrator in [SplineCalibrator, PolynomialCalibrator, MathOperationCalibrator]:
-            # Try to find each type of data encoding element. If we find one, we assume it's the only one.
-            element = data_encoding_element.find(f"xtce:DefaultCalibrator/xtce:{calibrator.__name__}", ns)
-            if element is not None:
-                return calibrator.from_calibrator_xml_element(element, ns)
-        return None
-
-    @staticmethod
-    def get_context_calibrators(
-            data_encoding_element: ElementTree.Element, ns: dict) -> Union[List[ContextCalibrator], None]:
-        """Get the context default_calibrator(s) for the data encoding element
-
-        Parameters
-        ----------
-        data_encoding_element : ElementTree.Element
-            XML element
-        ns : dict
-            XML namespace dict
-
-        Returns
-        -------
-        : Union[List[ContextCalibrator], None]
-            List of ContextCalibrator objects or None if there are no context calibrators
-        """
-        if data_encoding_element.find('xtce:ContextCalibratorList', ns):
-            context_calibrators_elements = data_encoding_element.findall(
-                'xtce:ContextCalibratorList/xtce:ContextCalibrator', ns)
-            return [ContextCalibrator.from_context_calibrator_xml_element(el, ns)
-                    for el in context_calibrators_elements]
-        return None
-
-    @staticmethod
-    def _get_linear_adjuster(parent_element: ElementTree.Element, ns: dict) -> Union[callable, None]:
-        """Examine a parent (e.g. a <xtce:DynamicValue>) element and find a LinearAdjustment if present,
-        creating and returning a function that evaluates the adjustment.
-
-        Parameters
-        ----------
-        parent_element : ElementTree.Element
-            Parent element which may contain a LinearAdjustment
-        ns : dict
-            XML namespace dict
-
-        Returns
-        -------
-        adjuster : Union[callable, None]
-            Function object that adjusts a SizeInBits value by a linear function or None if no adjuster present
-        """
-        linear_adjustment_element = parent_element.find('xtce:LinearAdjustment', ns)
-        if linear_adjustment_element is not None:
-            slope = (int(linear_adjustment_element.attrib['slope'])
-                     if 'slope' in linear_adjustment_element.attrib else 0)
-            intercept = (int(linear_adjustment_element.attrib['intercept'])
-                         if 'intercept' in linear_adjustment_element.attrib else 0)
-
-            def adjuster(x: int) -> int:
-                """Perform a linear adjustment to a size parameter
-
-                Parameters
-                ----------
-                x : int
-                    Unadjusted size parameter.
-
-                Returns
-                -------
-                : int
-                    Adjusted size parameter
-                """
-                adjusted = (slope * float(x)) + intercept
-                if not adjusted.is_integer():
-                    raise ValueError(f"Error when adjusting a value with a LinearAdjustment. Got y=mx + b as "
-                                     f"{adjusted}={slope}*{x}+{intercept} returned a float. "
-                                     f"Should have been an int.")
-                return int(adjusted)
-
-            return adjuster
-        return None
-
-    def _calculate_size(self, packet: Packet) -> int:
-        """Calculate the size of the data item in bits.
-
-        Parameters
-        ----------
-        packet: Packet
-            Binary representation of the packet used to get the coming bits and any
-            previously parsed data items to infer field lengths.
-
-        Returns
-        -------
-        : int
-            Size of the data item in bits.
-        """
-        raise NotImplementedError()
-
-    def parse_value(self, packet: Packet, **kwargs) -> Tuple[Any, Any]:
-        """Parse a value from packet data, possibly using previously parsed data items to inform parsing.
-
-        Parameters
-        ----------
-        packet: Packet
-            Binary representation of the packet used to get the coming bits and any
-            previously parsed data items to infer field lengths.
-        Returns
-        -------
-        : any
-            Raw value
-        : any
-            Calibrated value
-        """
-        raise NotImplementedError()
-
-
-class StringDataEncoding(DataEncoding):
-    """<xtce:StringDataEncoding>"""
-
-    _supported_encodings = ('US-ASCII', 'ISO-8859-1', 'Windows-1252', 'UTF-8', 'UTF-16',
-                            'UTF-16LE', 'UTF-16BE', 'UTF-32', 'UTF-32LE', 'UTF-32BE')
-
-    def __init__(self, encoding: str = 'UTF-8',
-                 byte_order: Optional[str] = None,
-                 termination_character: Optional[str] = None,
-                 fixed_length: Optional[int] = None,
-                 leading_length_size: Optional[int] = None,
-                 dynamic_length_reference: Optional[str] = None,
-                 use_calibrated_value: Optional[bool] = True,
-                 discrete_lookup_length: Optional[List[DiscreteLookup]] = None,
-                 length_linear_adjuster: Optional[callable] = None):
-        # pylint: disable=pointless-statement
-        f"""Constructor
-        Only one of termination_character, fixed_length, or leading_length_size should be set. Setting more than one
-        is nonsensical.
-
-        Parameters
-        ----------
-        encoding : str
-            One of the XTCE-supported encodings: {self._supported_encodings}
-            Describes how to read the characters in the string.
-            Default is UTF-8.
-        byte_order : Optional[str]
-            Description of the byte order, used for multi-byte character encodings where the endianness cannot be
-            determined from the encoding specifier. Can be None if encoding is single-byte or UTF-*BE/UTF-*LE.
-        termination_character : Optional[str]
-            A single hexadecimal character, represented as a string. Must be encoded in the same encoding as the string
-            itself. For example, for a utf-8 encoded string, the hex string must be two hex characters (one byte).
-            For a UTF-16* encoded string, the hex representation of the termination character must be four characters
-            (two bytes).
-        fixed_length : Optional[int]
-            Fixed length of the string, in bits.
-        leading_length_size : Optional[int]
-            Fixed size in bits of a leading field that contains the length of the subsequent string.
-        dynamic_length_reference : Optional[str]
-            Name of referenced parameter for dynamic length, in bits. May be combined with a linear_adjuster
-        use_calibrated_value: Optional[bool]
-            Whether to use the calibrated value on the referenced parameter in dynamic_length_reference.
-            Default is True.
-        discrete_lookup_length : Optional[List[DiscreteLookup]]
-            List of DiscreteLookup objects with which to determine string length from another parameter.
-        length_linear_adjuster : Optional[callable]
-            Function that linearly adjusts a size. e.g. if the size reference parameter gives a length in bytes, the
-            linear adjuster should multiply by 8 to give the size in bits.
-        """
-        if encoding not in self._supported_encodings:
-            raise ValueError(f"Got encoding={encoding} (uppercased). "
-                             f"Encoding must be one of {self._supported_encodings}.")
-        self.encoding = encoding
-        if encoding not in ['US-ASCII', 'ISO-8859-1', 'Windows-1252', 'UTF-8']:  # for these, byte order doesn't matter
-            if byte_order is None:
-                if "LE" in encoding:
-                    self.byte_order = "leastSignificantByteFirst"
-                elif "BE" in encoding:
-                    self.byte_order = "mostSignificantByteFirst"
-                else:
-                    raise ValueError("Byte order must be specified for multi-byte character encodings.")
-            else:
-                self.byte_order = byte_order
-        self.termination_character = termination_character
-        if termination_character:
-            # Always in hex, per 4.3.2.2.5.5.4 of XTCE spec
-            self.termination_character = bytes.fromhex(termination_character)
-            # Check that the termination character is a single character in the specified encoding
-            # e.g. b'\x58' in utf-8 is "X"
-            # b'\x21\00' in utf-16-le is "!"
-            # b'\x00\x21' in utf-16-be is "!"
-            if len(self.termination_character.decode(encoding)) != 1:
-                raise ValueError(f"Termination character {termination_character} appears to be malformed. "
-                                 f"Expected a hex string representation of a single character, e.g. '58' for "
-                                 f"character 'X' in utf-8 or '5800' for character 'X' in utf-16-le. Note that "
-                                 f"variable-width encoding is not yet supported in any encoding.")
-        self.fixed_length = fixed_length
-        self.leading_length_size = leading_length_size
-        self.dynamic_length_reference = dynamic_length_reference
-        self.use_calibrated_value = use_calibrated_value
-        self.discrete_lookup_length = discrete_lookup_length
-        self.length_linear_adjuster = length_linear_adjuster
-
-    def _calculate_size(self, packet: Packet) -> int:
-        """Calculate the length of the string data item in bits.
-
-        Parameters
-        ----------
-        packet: Packet
-            Binary representation of the packet used to get the coming bits and any
-            previously parsed data items to infer field lengths.
-
-        Returns
-        -------
-        : int
-            Number of bits in the string data item
-        """
-        # pylint: disable=too-many-branches
-        if self.fixed_length:
-            strlen_bits = self.fixed_length
-        elif self.leading_length_size is not None:  # strlen_bits is determined from a preceding integer
-            strlen_bits = packet.read_as_int(self.leading_length_size)
-            if strlen_bits % 8 != 0:
-                warnings.warn(f"String length (in bits) is {strlen_bits}, which is not a multiple of 8. "
-                              f"This likely means something is wrong since strings are expected to be integer numbers "
-                              f"of bytes.")
-        elif self.discrete_lookup_length is not None:
-            for discrete_lookup in self.discrete_lookup_length:
-                strlen_bits = discrete_lookup.evaluate(packet.parsed_data)
-                if strlen_bits is not None:
-                    break
-            else:
-                raise ValueError('List of discrete lookup values being used for determining length of '
-                                 f'string {self} found no matches based on {packet}.')
-        elif self.dynamic_length_reference is not None:
-            if self.use_calibrated_value is True:
-                strlen_bits = packet.parsed_data[self.dynamic_length_reference].derived_value
-            else:
-                strlen_bits = packet.parsed_data[self.dynamic_length_reference].raw_value
-            strlen_bits = int(strlen_bits)
-        elif self.termination_character is not None:
-            # Look through the rest of the packet data to find the termination character
-            nbits_left = len(packet) - packet.pos
-            orig_pos = packet.pos
-            string_buffer = packet.read_as_bytes(nbits_left - nbits_left % 8)
-            # Reset the original position because we only wanted to look ahead
-            packet.pos = orig_pos
-            try:
-                strlen_bits = string_buffer.index(self.termination_character) * 8
-            except ValueError as exc:
-                # Termination character not found in the string buffer
-                raise ValueError(f"Reached the end of the packet data without finding the "
-                                 f"termination character {self.termination_character}") from exc
-        else:
-            raise ValueError("Unable to parse StringParameterType. "
-                             "Didn't contain any way to constrain the length of the string.")
-        if not self.termination_character and self.length_linear_adjuster is not None:
-            # Only adjust if we are not doing this by termination character. Adjusting a length that is objectively
-            # determined via termination character is nonsensical.
-            strlen_bits = self.length_linear_adjuster(strlen_bits)
-        return strlen_bits
-        # pylint: enable=too-many-branches
-
-    def parse_value(self, packet: Packet, **kwargs) -> Tuple[str, None]:
-        """Parse a value from packet data, possibly using previously parsed data items to inform parsing.
-
-        Parameters
-        ----------
-        packet: Packet
-            Binary representation of the packet used to get the coming bits and any
-            previously parsed data items to infer field lengths.
-        Returns
-        -------
-        : str
-            Parsed value
-        : None
-            Calibrated value
-        """
-        nbits = self._calculate_size(packet)
-        parsed_value = packet.read_as_bytes(nbits)
-        if self.termination_character is not None:
-            # We need to skip over the termination character if there was one
-            packet.pos += len(self.termination_character) * 8
-        return parsed_value.decode(self.encoding), None
-
-    @classmethod
-    def from_data_encoding_xml_element(cls, element: ElementTree.Element, ns: dict) -> 'StringDataEncoding':
-        """Create a data encoding object from an <xtce:StringDataEncoding> XML element.
-        Strings in XTCE can be described in three ways:
-
-        1. Using a termination character that marks the end of the string.
-        2. Using a fixed length, which may be derived from referenced parameter either directly or via a discrete
-           lookup table.
-        3. Using a leading size field that describes the size of the following string.
-
-        Parameters
-        ----------
-        element : ElementTree.Element
-            XML element
-        ns : dict
-            XML namespace dict
-
-        Returns
-        -------
-        cls
-        """
-        encoding: str = element.get("encoding", "UTF-8")
-
-        byte_order = None  # fallthrough value
-        if encoding not in ('US-ASCII', 'ISO-8859-1', 'Windows-1252', 'UTF-8'):  # single-byte chars
-            if not (encoding.endswith("BE") or encoding.endswith("LE")):
-                byte_order = element.get("byteOrder")
-                if byte_order is None:
-                    raise ValueError("For multi-byte character encodings, byte order must be specified "
-                                     "either using the byteOrder attribute or via the encoding itself.")
-
-        try:
-            termination_character = element.find('xtce:SizeInBits/xtce:TerminationChar', ns).text
-            return cls(termination_character=termination_character, encoding=encoding, byte_order=byte_order)
-        except AttributeError:
-            pass
-
-        try:
-            leading_length_size = int(
-                element.find('xtce:SizeInBits/xtce:LeadingSize', ns).attrib['sizeInBitsOfSizeTag'])
-            return cls(leading_length_size=leading_length_size, encoding=encoding, byte_order=byte_order)
-        except AttributeError:
-            pass
-
-        fixed_element = element.find('xtce:SizeInBits/xtce:Fixed', ns)
-
-        discrete_lookup_list_element = fixed_element.find('xtce:DiscreteLookupList', ns)
-        if discrete_lookup_list_element is not None:
-            discrete_lookup_list = [DiscreteLookup.from_discrete_lookup_xml_element(el, ns)
-                                    for el in discrete_lookup_list_element.findall('xtce:DiscreteLookup', ns)]
-            return cls(encoding=encoding, byte_order=byte_order,
-                       discrete_lookup_length=discrete_lookup_list)
-
-        try:
-            dynamic_value_element = fixed_element.find('xtce:DynamicValue', ns)
-            referenced_parameter = dynamic_value_element.find('xtce:ParameterInstanceRef', ns).attrib['parameterRef']
-            use_calibrated_value = True
-            if 'useCalibratedValue' in dynamic_value_element.find('xtce:ParameterInstanceRef', ns).attrib:
-                use_calibrated_value = dynamic_value_element.find(
-                    'xtce:ParameterInstanceRef', ns).attrib['useCalibratedValue'].lower() == "true"
-            linear_adjuster = cls._get_linear_adjuster(dynamic_value_element, ns)
-            return cls(encoding=encoding, byte_order=byte_order,
-                       dynamic_length_reference=referenced_parameter, use_calibrated_value=use_calibrated_value,
-                       length_linear_adjuster=linear_adjuster)
-        except AttributeError:
-            pass
-
-        try:
-            fixed_length = int(fixed_element.find('xtce:FixedValue', ns).text)
-            return cls(fixed_length=fixed_length, encoding=encoding, byte_order=byte_order)
-        except AttributeError:
-            pass
-
-        raise ElementNotFoundError(f"Failed to parse StringDataEncoding for element {ElementTree.tostring(element)}")
-
-
-class NumericDataEncoding(DataEncoding, metaclass=ABCMeta):
-    """Abstract class that is inherited by IntegerDataEncoding and FloatDataEncoding"""
-
-    def __init__(self, size_in_bits: int,
-                 encoding: str,
-                 byte_order: str = "mostSignificantByteFirst",
-                 default_calibrator: Optional[Calibrator] = None,
-                 context_calibrators: Optional[List[ContextCalibrator]] = None):
-        """Constructor
-
-        Parameters
-        ----------
-        size_in_bits : int
-            Size of the integer
-        encoding : str
-            String indicating the type of encoding for the integer. FSW seems to use primarily 'signed' and 'unsigned',
-            though 'signed' is not actually a valid specifier according to XTCE. 'twosCompliment' [sic] should be used
-            instead, though we support the unofficial 'signed' specifier here.
-            For supported specifiers, see XTCE spec 4.3.2.2.5.6.2
-        byte_order : str
-            Description of the byte order. Default is 'mostSignficantByteFirst' (big-endian).
-        default_calibrator : Optional[Calibrator]
-            Optional Calibrator object, containing information on how to transform the integer-encoded data, e.g. via
-            a polynomial conversion or spline interpolation.
-        context_calibrators : Optional[List[ContextCalibrator]]
-            List of ContextCalibrator objects, containing match criteria and corresponding calibrators to use in
-            various scenarios, based on other parameters.
-        """
-        self.size_in_bits = size_in_bits
-        self.encoding = encoding
-        self.byte_order = byte_order
-        self.default_calibrator = default_calibrator
-        self.context_calibrators = context_calibrators
-
-    def _calculate_size(self, packet: Packet) -> int:
-        return self.size_in_bits
-
-    @abstractmethod
-    def _get_raw_value(self, packet: Packet) -> Union[int, float]:
-        """Read the raw value from the packet data
-
-        Parameters
-        ----------
-        packet: Packet
-            Binary representation of the packet used to get the coming bits and any
-            previously parsed data items to infer field lengths.
-
-        Returns
-        -------
-        : int
-            Raw value
-        """
-        raise NotImplementedError()
-
-    @staticmethod
-    def _twos_complement(val: int, bit_width: int) -> int:
-        """Take the twos complement of val
-
-        Used when parsing ints and some floats
-        """
-        if (val & (1 << (bit_width - 1))) != 0:  # if sign bit is set e.g., 8bit: 128-255
-            return val - (1 << bit_width)  # compute negative value
-        return val
-
-    def parse_value(self,
-                    packet: Packet,
-                    **kwargs) -> Tuple[Union[int, float], Union[int, float]]:
-        """Parse a value from packet data, possibly using previously parsed data items to inform parsing.
-
-        Parameters
-        ----------
-        packet: Packet
-            Binary representation of the packet used to get the coming bits and any
-            previously parsed data items to infer field lengths.
-        Returns
-        -------
-        : any
-            Parsed value
-        : any
-            Calibrated value
-        """
-        parsed_value = self._get_raw_value(packet)
-        # Attempt to calibrate
-        calibrated_value = parsed_value  # Provides a fall through in case we have no calibrators
-        if self.context_calibrators:
-            for calibrator in self.context_calibrators:
-                match_criteria = calibrator.match_criteria
-                if all(criterion.evaluate(packet.parsed_data, parsed_value) for criterion in match_criteria):
-                    # If the parsed data so far satisfy all the match criteria
-                    calibrated_value = calibrator.calibrate(parsed_value)
-                    return parsed_value, calibrated_value
-        if self.default_calibrator:  # If no context calibrators or if none apply and there is a default
-            calibrated_value = self.default_calibrator.calibrate(parsed_value)
-        # Ultimate fallthrough
-        return parsed_value, calibrated_value
-
-
-class IntegerDataEncoding(NumericDataEncoding):
-    """<xtce:IntegerDataEncoding>"""
-
-    def _get_raw_value(self, packet: Packet) -> int:
-        # Extract the bits from the data in big-endian order from the packet
-        val = packet.read_as_int(self.size_in_bits)
-        if self.byte_order == 'leastSignificantByteFirst':
-            # Convert little-endian (LSB first) int to bigendian. Just reverses the order of the bytes.
-            val = int.from_bytes(
-                val.to_bytes(
-                    length=(self.size_in_bits + 7) // 8,
-                    byteorder="little"
-                ),
-                byteorder="big"
-            )
-        if self.encoding == 'unsigned':
-            return val
-        # It is a signed integer, and we need to take into account the first bit
-        return self._twos_complement(val, self.size_in_bits)
-
-    @classmethod
-    def from_data_encoding_xml_element(cls, element: ElementTree.Element, ns: dict) -> 'IntegerDataEncoding':
-        """Create a data encoding object from an <xtce:IntegerDataEncoding> XML element.
-
-        Parameters
-        ----------
-        element : ElementTree.Element
-            XML element
-        ns : dict
-            XML namespace dict
-
-        Returns
-        -------
-        : cls
-        """
-        size_in_bits = int(element.attrib['sizeInBits'])
-        encoding = element.attrib['encoding'] if 'encoding' in element.attrib else "unsigned"
-        byte_order = element.get("byteOrder", "mostSignificantByteFirst")
-        calibrator = cls.get_default_calibrator(element, ns)
-        context_calibrators = cls.get_context_calibrators(element, ns)
-        return cls(size_in_bits=size_in_bits, encoding=encoding, byte_order=byte_order,
-                   default_calibrator=calibrator, context_calibrators=context_calibrators)
-
-
-class FloatDataEncoding(NumericDataEncoding):
-    """<xtce:FloatDataEncoding>"""
-    _supported_encodings = ['IEEE-754', 'MIL-1750A']
-
-    def __init__(self, size_in_bits: int, encoding: str = 'IEEE-754',
-                 byte_order: str = 'mostSignificantByteFirst',
-                 default_calibrator: Optional[Calibrator] = None,
-                 context_calibrators: Optional[List[ContextCalibrator]] = None):
-        """Constructor
-
-        Parameters
-        ----------
-        size_in_bits : int
-            Size of the encoded value, in bits.
-        encoding : str
-            Encoding method of the float data. Must be either 'IEEE-754' or 'MIL-1750A'. Defaults to IEEE-754.
-        byte_order : str
-            Description of the byte order. Default is 'mostSignificantByteFirst' (big endian).
-        default_calibrator : Optional[Calibrator]
-            Optional Calibrator object, containing information on how to transform the data, e.g. via
-            a polynomial conversion or spline interpolation.
-        context_calibrators : Optional[List[ContextCalibrator]]
-            List of ContextCalibrator objects, containing match criteria and corresponding calibrators to use in
-            various scenarios, based on other parameters.
-        """
-        if encoding not in self._supported_encodings:
-            raise ValueError(f"Invalid encoding type {encoding} for float data. "
-                             f"Must be one of {self._supported_encodings}.")
-        if encoding == 'MIL-1750A' and size_in_bits != 32:
-            raise ValueError("MIL-1750A encoded floats must be 32 bits, per the MIL-1750A spec. See "
-                             "https://www.xgc-tek.com/manuals/mil-std-1750a/c191.html#AEN324")
-        if encoding == 'IEEE-754' and size_in_bits not in (16, 32, 64):
-            raise ValueError(f"Invalid size_in_bits value for IEEE-754 FloatDataEncoding, {size_in_bits}. "
-                             "Must be 16, 32, or 64.")
-        super().__init__(size_in_bits, encoding=encoding, byte_order=byte_order,
-                         default_calibrator=default_calibrator, context_calibrators=context_calibrators)
-
-        if self.encoding == "MIL-1750A":
-            def _mil_parse_func(mil_bytes: bytes):
-                """Parsing function for MIL-1750A floats"""
-                # MIL 1750A floats are always 32 bit
-                # See: https://www.xgc-tek.com/manuals/mil-std-1750a/c191.html#AEN324
-                #
-                #  MSB                                         LSB MSB          LSB
-                # ------------------------------------------------------------------
-                # | S|                   Mantissa                 |    Exponent    |
-                # ------------------------------------------------------------------
-                #   0  1                                        23 24            31
-                if self.byte_order == "mostSignificantByteFirst":
-                    bytes_as_int = int.from_bytes(mil_bytes, byteorder='big')
-                else:
-                    bytes_as_int = int.from_bytes(mil_bytes, byteorder='little')
-                exponent = bytes_as_int & 0xFF  # last 8 bits
-                mantissa = (bytes_as_int >> 8) & 0xFFFFFF  # bits 0 through 23 (24 bits)
-                # We include the sign bit with the mantissa because we can just take the twos complement
-                # of it directly and use it in the final calculation for the value
-
-                # Both mantissa and exponent are stored as twos complement with no bias
-                exponent = self._twos_complement(exponent, 8)
-                mantissa = self._twos_complement(mantissa, 24)
-
-                # Calculate float value using native Python floats, which are more precise
-                return mantissa * (2.0 ** (exponent - (24 - 1)))
-
-            # Set up the parsing function just once, so we can use it repeatedly with _get_raw_value
-            self.parse_func = _mil_parse_func
-        else:
-            if self.byte_order == "leastSignificantByteFirst":
-                self._struct_format = "<"
-            else:
-                # Big-endian is the default
-                self._struct_format = ">"
-
-            if self.size_in_bits == 16:
-                self._struct_format += "e"
-            elif self.size_in_bits == 32:
-                self._struct_format += "f"
-            elif self.size_in_bits == 64:
-                self._struct_format += "d"
-
-            def ieee_parse_func(data: bytes):
-                """Parsing function for IEEE floats"""
-                # The packet data we got back is always extracted in big-endian order
-                # but the struct format code contains the endianness of the float data
-                return struct.unpack(self._struct_format, data)[0]
-
-            # Set up the parsing function just once, so we can use it repeatedly with _get_raw_value
-            self.parse_func: callable = ieee_parse_func
-
-    def _get_raw_value(self, packet):
-        """Read the data in as bytes and return a float representation."""
-        data = packet.read_as_bytes(self.size_in_bits)
-        # The parsing function is fully set during initialization to save time during parsing
-        return self.parse_func(data)
-
-    @classmethod
-    def from_data_encoding_xml_element(cls, element: ElementTree.Element, ns: dict) -> 'FloatDataEncoding':
-        """Create a data encoding object from an <xtce:FloatDataEncoding> XML element.
-
-        Parameters
-        ----------
-        element : ElementTree.Element
-            XML element
-        ns : dict
-            XML namespace dict
-
-        Returns
-        -------
-        : cls
-        """
-        size_in_bits = int(element.attrib['sizeInBits'])
-        encoding = element.get("encoding", "IEEE-754")
-        byte_order = element.get("byteOrder", "mostSignificantByteFirst")
-        default_calibrator = cls.get_default_calibrator(element, ns)
-        context_calibrators = cls.get_context_calibrators(element, ns)
-        return cls(size_in_bits=size_in_bits, encoding=encoding, byte_order=byte_order,
-                   default_calibrator=default_calibrator, context_calibrators=context_calibrators)
-
-
-class BinaryDataEncoding(DataEncoding):
-    """<xtce:BinaryDataEncoding>"""
-
-    def __init__(self, fixed_size_in_bits: Optional[int] = None,
-                 size_reference_parameter: Optional[str] = None, use_calibrated_value: bool = True,
-                 size_discrete_lookup_list: Optional[List[DiscreteLookup]] = None,
-                 linear_adjuster: Optional[callable] = None):
-        """Constructor
-
-        Parameters
-        ----------
-        fixed_size_in_bits : Optional[int]
-            Fixed size for the binary field, in bits.
-        size_reference_parameter : Optional[str]
-            Name of a parameter to reference for the binary field length, in bits. Note that space often specifies these
-            fields in byte length, not bit length. This should be taken care of by a LinearAdjuster element that simply
-            instructs the value to be multiplied by 8 but that hasn't historically been implemented unfortunately.
-        use_calibrated_value: bool, Optional
-            Default True. If False, the size_reference_parameter is examined for its raw value.
-        size_discrete_lookup_list: Optional[List[DiscreteLookup]]
-            List of DiscreteLookup objects by which to determine the length of the binary data field. This suffers from
-            the same bit/byte conversion problem as size_reference_parameter.
-        linear_adjuster : Optional[callable]
-            Function that linearly adjusts a size. e.g. if the size reference parameter gives a length in bytes, the
-            linear adjuster should multiply by 8 to give the size in bits.
-        """
-        self.fixed_size_in_bits = fixed_size_in_bits
-        self.size_reference_parameter = size_reference_parameter
-        self.use_calibrated_value = use_calibrated_value
-        self.size_discrete_lookup_list = size_discrete_lookup_list
-        self.linear_adjuster = linear_adjuster
-
-    def _calculate_size(self, packet: Packet) -> int:
-        """Determine the number of bits in the binary field.
-
-        Returns
-        -------
-        : Union[str, None]
-            Format string in the bitstring format. e.g. bin:1024
-        """
-        if self.fixed_size_in_bits is not None:
-            len_bits = self.fixed_size_in_bits
-        elif self.size_reference_parameter is not None:
-            field_length_reference = self.size_reference_parameter
-            if self.use_calibrated_value:
-                len_bits = packet.parsed_data[field_length_reference].derived_value
-            else:
-                len_bits = packet.parsed_data[field_length_reference].raw_value
-        elif self.size_discrete_lookup_list is not None:
-            for discrete_lookup in self.size_discrete_lookup_list:
-                len_bits = discrete_lookup.evaluate(packet.parsed_data)
-                if len_bits is not None:
-                    break
-            else:
-                raise ValueError('List of discrete lookup values being used for determining length of '
-                                 f'string {self} found no matches based on {packet.parsed_data}.')
-        else:
-            raise ValueError("Unable to parse BinaryDataEncoding. "
-                             "No fixed size, dynamic size, or dynamic lookup size were provided.")
-
-        if self.linear_adjuster is not None:
-            len_bits = self.linear_adjuster(len_bits)
-        return len_bits
-
-    def parse_value(self, packet: Packet, word_size: Optional[int] = None, **kwargs):
-        """Parse a value from packet data, possibly using previously parsed data items to inform parsing.
-
-        Parameters
-        ----------
-        packet: Packet
-            Binary representation of the packet used to get the coming bits and any
-            previously parsed data items to infer field lengths.
-        word_size : Optional[int]
-            Word size for encoded data. This is used to ensure that the cursor ends up at the end of the last word
-            and ready to parse the next data field.
-
-        Returns
-        -------
-        : any
-            Parsed value
-        : any
-            Calibrated value
-        """
-        nbits = self._calculate_size(packet)
-        parsed_value = packet.read_as_bytes(nbits)
-        if word_size:
-            cursor_position_in_word = packet.pos % word_size
-            if cursor_position_in_word != 0:
-                logger.debug(f"Adjusting cursor position to the end of a {word_size} bit word.")
-                packet.pos += word_size - cursor_position_in_word
-        return parsed_value, None
-
-    @classmethod
-    def from_data_encoding_xml_element(cls, element: ElementTree.Element, ns: dict) -> 'BinaryDataEncoding':
-        """Create a data encoding object from an <xtce:BinaryDataEncoding> XML element.
-
-        Parameters
-        ----------
-        element : ElementTree.Element
-            XML element
-        ns : dict
-            XML namespace dict
-
-        Returns
-        -------
-        : BinaryDataEncoding
-        """
-        fixed_value_element = element.find('xtce:SizeInBits/xtce:FixedValue', ns)
-        if fixed_value_element is not None:
-            fixed_size_in_bits = int(fixed_value_element.text)
-            return cls(fixed_size_in_bits=fixed_size_in_bits)
-
-        dynamic_value_element = element.find('xtce:SizeInBits/xtce:DynamicValue', ns)
-        if dynamic_value_element is not None:
-            param_inst_ref = dynamic_value_element.find('xtce:ParameterInstanceRef', ns)
-            referenced_parameter = param_inst_ref.attrib['parameterRef']
-            use_calibrated_value = True
-            if 'useCalibratedValue' in param_inst_ref.attrib:
-                use_calibrated_value = param_inst_ref.attrib['useCalibratedValue'].lower() == "true"
-            linear_adjuster = cls._get_linear_adjuster(dynamic_value_element, ns)
-            return cls(size_reference_parameter=referenced_parameter,
-                       use_calibrated_value=use_calibrated_value, linear_adjuster=linear_adjuster)
-
-        discrete_lookup_list_element = element.find('xtce:SizeInBits/xtce:DiscreteLookupList', ns)
-        if discrete_lookup_list_element is not None:
-            discrete_lookup_list = [DiscreteLookup.from_discrete_lookup_xml_element(el, ns)
-                                    for el in discrete_lookup_list_element.findall('xtce:DiscreteLookup', ns)]
-            return cls(size_discrete_lookup_list=discrete_lookup_list)
-
-        raise ValueError("Tried parsing a binary parameter length using Fixed, Dynamic, and DiscreteLookupList "
-                         "but failed. See 3.4.5 of the XTCE Green Book CCSDS 660.1-G-2.")
-
-
-# ParameterType definitions
-class ParameterType(AttrComparable, metaclass=ABCMeta):
-    """Abstract base class for XTCE parameter types"""
-
-    def __init__(self, name: str, encoding: DataEncoding, unit: Optional[str] = None):
-        """Constructor
-
-        Parameters
-        ----------
-        name : str
-            Parameter type name. Usually something like 'MSN__PARAM_Type'
-        encoding : DataEncoding
-            How the data is encoded. e.g. IntegerDataEncoding, StringDataEncoding, etc.
-        unit : Optional[str]
-            String describing the unit for the stored value.
-        """
-        self.name = name
-        self.unit = unit
-        self.encoding = encoding
-
-    def __repr__(self):
-        module = self.__class__.__module__
-        qualname = self.__class__.__qualname__
-        return f"<{module}.{qualname} {self.name}>"
-
-    @classmethod
-    def from_parameter_type_xml_element(cls, element: ElementTree.Element, ns: dict) -> 'ParameterType':
-        """Create a *ParameterType from an <xtce:*ParameterType> XML element.
-
-        Parameters
-        ----------
-        element : ElementTree.Element
-            The XML element from which to create the object.
-        ns: dict
-            XML namespace dict
-
-        Returns
-        -------
-        : ParameterType
-        """
-        name = element.attrib['name']
-        unit = cls.get_units(element, ns)
-        encoding = cls.get_data_encoding(element, ns)
-        return cls(name, encoding, unit)
-
-    @staticmethod
-    def get_units(parameter_type_element: ElementTree.Element, ns: dict) -> Union[str, None]:
-        """Finds the units associated with a parameter type element and parsed them to return a unit string.
-        We assume only one <xtce:Unit> but this could be extended to support multiple units.
-        See section 4.3.2.2.4 of CCSDS 660.1-G-1
-
-        Parameters
-        ----------
-        parameter_type_element : ElementTree.Element
-            The parameter type element
-        ns : dict
-            XML namespace dictionary
-
-        Returns
-        -------
-        : Union[str, None]
-            Unit string or None if no units are defined
-        """
-        # Assume we are not parsing a Time Parameter Type, which stores units differently
-        units = parameter_type_element.findall('xtce:UnitSet/xtce:Unit', ns)
-        # TODO: Implement multiple unit elements for compound unit definitions
-        assert len(units) <= 1, f"Found {len(units)} <xtce:Unit> elements in a single <xtce:UnitSet>." \
-                                f"This is supported in the standard but is rarely used " \
-                                f"and is not yet supported by this library."
-        if units:
-            return " ".join([u.text for u in units])
-        # Units are optional so return None if they aren't specified
-        return None
-
-    @staticmethod
-    def get_data_encoding(parameter_type_element: ElementTree.Element, ns: dict) -> Union[DataEncoding, None]:
-        """Finds the data encoding XML element associated with a parameter type XML element and parses
-        it, returning an object representation of the data encoding.
-
-        Parameters
-        ----------
-        parameter_type_element : ElementTree.Element
-            The parameter type element
-        ns : dict
-            XML namespace dictionary
-
-        Returns
-        -------
-        : Union[DataEncoding, None]
-            DataEncoding object or None if no data encoding is defined (which is probably an issue)
-        """
-        for data_encoding in [StringDataEncoding, IntegerDataEncoding, FloatDataEncoding, BinaryDataEncoding]:
-            # Try to find each type of data encoding element. If we find one, we assume it's the only one.
-            element = parameter_type_element.find(f".//xtce:{data_encoding.__name__}", ns)
-            if element is not None:
-                return data_encoding.from_data_encoding_xml_element(element, ns)
-        return None
-
-    def parse_value(self, packet: Packet, **kwargs):
-        """Using the parameter type definition and associated data encoding, parse a value from a bit stream starting
-        at the current cursor position.
-
-        Parameters
-        ----------
-        packet: Packet
-            Binary representation of the packet used to get the coming bits and any
-            previously parsed data items to infer field lengths.
-
-        Returns
-        -------
-        parsed_value : any
-            Resulting parsed data value.
-        """
-        return self.encoding.parse_value(packet, **kwargs)
-
-
-class StringParameterType(ParameterType):
-    """<xtce:StringParameterType>"""
-
-    def __init__(self, name: str, encoding: StringDataEncoding, unit: Optional[str] = None):
-        """Constructor
-
-        Parameters
-        ----------
-        name : str
-            Parameter type name. Usually something like 'MSN__PARAM_Type'
-        encoding : StringDataEncoding
-            Must be a StringDataEncoding object since strings can't be encoded other ways.
-        unit : Optional[str]
-            String describing the unit for the stored value.
-        """
-        if not isinstance(encoding, StringDataEncoding):
-            raise ValueError("StringParameterType may only be instantiated with a StringDataEncoding encoding.")
-        super().__init__(name=name, encoding=encoding, unit=unit)
-        self.encoding = encoding  # Clarifies to static analysis tools that self.encoding is type StringDataEncoding
-
-
-class IntegerParameterType(ParameterType):
-    """<xtce:IntegerParameterType>"""
-    pass
-
-
-class FloatParameterType(ParameterType):
-    """<xtce:FloatParameterType>"""
-    pass
-
-
-class EnumeratedParameterType(ParameterType):
-    """<xtce:EnumeratedParameterType>"""
-
-    def __init__(self, name: str, encoding: DataEncoding, enumeration: dict, unit: Union[str, None] = None):
-        """Constructor
-
-        Parameters
-        ----------
-        name : str
-            Parameter type name.
-        unit : str
-            Unit string for stored value.
-        encoding : DataEncoding
-            How the data is encoded. e.g. IntegerDataEncoding.
-        enumeration : dict
-            Lookup with label:value pairs matching encoded values to their enum labels.
-        """
-        super().__init__(name=name, encoding=encoding, unit=unit)
-        self.enumeration = enumeration
-
-    def __repr__(self):
-        return f"<{self.__class__.__name__} {self.name}>"
-
-    @classmethod
-    def from_parameter_type_xml_element(cls, element: ElementTree.Element, ns: dict):
-        """Create an EnumeratedParameterType from an <xtce:EnumeratedParameterType> XML element.
-        Overrides ParameterType.from_parameter_type_xml_element
-
-        Parameters
-        ----------
-        element : ElementTree.Element
-            The XML element from which to create the object.
-        ns: dict
-            XML namespace dict
-
-        Returns
-        -------
-        : EnumeratedParameterType
-        """
-        name = element.attrib['name']
-        unit = cls.get_units(element, ns)
-        encoding = cls.get_data_encoding(element, ns)
-        enumeration = cls.get_enumeration_list_contents(element, ns)
-        return cls(name, encoding, enumeration=enumeration, unit=unit)
-
-    @staticmethod
-    def get_enumeration_list_contents(element: ElementTree.Element, ns: dict) -> dict:
-        """Finds the <xtce:EnumerationList> element child of an <xtce:EnumeratedParameterType> and parses it,
-        returning a dict. This method is confusingly named as if it might return a list. Sorry, XML and python
-        semantics are not always compatible. It's called an enumeration list because the XML element is called
-        <xtce:EnumerationList> but it contains key value pairs, so it's best represeneted as a dict.
-
-        Parameters
-        ----------
-        element : ElementTree.Element
-            The XML element from which to search for EnumerationList tags
-        ns : dict
-            XML namespace dict
-
-        Returns
-        -------
-        : dict
-        """
-        enumeration_list = element.find('xtce:EnumerationList', ns)
-        if enumeration_list is None:
-            raise ValueError("An EnumeratedParameterType must contain an EnumerationList.")
-
-        return {
-            int(el.attrib['value']): el.attrib['label']
-            for el in enumeration_list.iterfind('xtce:Enumeration', ns)
-        }
-
-    def parse_value(self, packet: Packet, **kwargs):
-        """Using the parameter type definition and associated data encoding, parse a value from a bit stream starting
-        at the current cursor position.
-
-        Parameters
-        ----------
-        packet: Packet
-            Binary representation of the packet used to get the coming bits and any
-            previously parsed data items to infer field lengths.
-
-        Returns
-        -------
-        parsed_value : int
-            Raw encoded value
-        derived_value : str
-            Resulting enum label associated with the (usually integer-)encoded data value.
-        """
-        raw, _ = super().parse_value(packet, **kwargs)
-        # Note: The enum lookup only operates on raw values. This is specified in 4.3.2.4.3.6 of the XTCE spec "
-        # CCSDS 660.1-G-2
-        try:
-            label = self.enumeration[raw]
-        except KeyError as exc:
-            raise ValueError(f"Failed to find raw value {raw} in enum lookup list {self.enumeration}.") from exc
-        return raw, label
-
-
-class BinaryParameterType(ParameterType):
-    """<xtce:BinaryParameterType>"""
-
-    def __init__(self, name: str, encoding: BinaryDataEncoding, unit: Optional[str] = None):
-        """Constructor
-
-        Parameters
-        ----------
-        name : str
-            Parameter type name. Usually something like 'MSN__PARAM_Type'
-        encoding : BinaryDataEncoding
-            Must be a BinaryDataEncoding object since binary data can't be encoded other ways.
-        unit : Optional[str]
-            String describing the unit for the stored value.
-        """
-        if not isinstance(encoding, BinaryDataEncoding):
-            raise ValueError("BinaryParameterType may only be instantiated with a BinaryDataEncoding encoding.")
-        super().__init__(name=name, encoding=encoding, unit=unit)
-        self.encoding = encoding
-
-
-class BooleanParameterType(ParameterType):
-    """<xtce:BooleanParameterType>"""
-
-    def __init__(self, name: str, encoding: DataEncoding, unit: Optional[str] = None):
-        """Constructor that just issues a warning if the encoding is String or Binary"""
-        if isinstance(encoding, (BinaryDataEncoding, StringDataEncoding)):
-            warnings.warn(f"You are encoding a BooleanParameterType with a {type(encoding)} encoding."
-                          f"This is almost certainly a very bad idea because the behavior of string and binary "
-                          f"encoded booleans is not specified in XTCE. e.g. is the string \"0\" truthy?")
-        super().__init__(name, encoding, unit)
-
-    def parse_value(self, packet: Packet, **kwargs):
-        """Using the parameter type definition and associated data encoding, parse a value from a bit stream starting
-        at the current cursor position.
-
-        Parameters
-        ----------
-        packet: Packet
-            Binary representation of the packet used to get the coming bits and any
-            previously parsed data items to infer field lengths.
-
-
-        Returns
-        -------
-        parsed_value : int
-            Raw encoded value
-        derived_value : str
-            Resulting boolean representation of the encoded raw value
-        """
-        raw, _ = super().parse_value(packet, **kwargs)
-        # Note: This behaves very strangely for String and Binary data encodings.
-        # Don't use those for Boolean parameters. The behavior isn't specified well in XTCE.
-        return raw, bool(raw)
-
-
-class TimeParameterType(ParameterType, metaclass=ABCMeta):
-    """Abstract class for time parameter types"""
-
-    def __init__(self, name: str, encoding: DataEncoding, unit: Optional[str] = None,
-                 epoch: Optional[str] = None, offset_from: Optional[str] = None):
-        """Constructor
-
-        Parameters
-        ----------
-        name : str
-            Parameter type name. Usually something like 'MSN__PARAM_Type'.
-        encoding : DataEncoding
-            How the data is encoded. e.g. IntegerDataEncoding, StringDataEncoding, etc.
-        unit : Optional[str]
-            String describing the unit for the stored value. Note that if a scale and offset are provided on
-            the Encoding element, the unit applies to the scaled value, not the raw value.
-        epoch : Optional[str]
-            String describing the starting epoch for the date or datetime encoded in the parameter.
-            Must be xs:date, xs:dateTime, or one of the following: "TAI", "J2000", "UNIX", "POSIX", "GPS".
-        offset_from : Optional[str]
-            Used to reference another time parameter by name. It allows
-            for the stringing together of several dissimilar but related time parameters.
-
-        Notes
-        -----
-        The XTCE spec is not very clear about OffsetFrom or what it is for. We parse it but don't use it for
-        anything.
-        """
-        super().__init__(name, encoding, unit=unit)
-        self.epoch = epoch
-        self.offset_from = offset_from
-
-    @classmethod
-    def from_parameter_type_xml_element(cls, element: ElementTree.Element, ns: dict):
-        """Create a *TimeParameterType from an <xtce:*TimeParameterType> XML element.
-
-        Parameters
-        ----------
-        element : ElementTree.Element
-            The XML element from which to create the object.
-        ns: dict
-            XML namespace dict
-
-        Returns
-        -------
-        : TimeParameterType
-        """
-        name = element.attrib['name']
-        unit = cls.get_units(element, ns)
-        encoding = cls.get_data_encoding(element, ns)
-        encoding_unit_scaler = cls.get_time_unit_linear_scaler(element, ns)
-        if encoding_unit_scaler:
-            encoding.default_calibrator = encoding_unit_scaler
-        epoch = cls.get_epoch(element, ns)
-        offset_from = cls.get_offset_from(element, ns)
-        return cls(name, encoding, unit, epoch, offset_from)
-
-    @staticmethod
-    def get_units(parameter_type_element: ElementTree.Element, ns: dict) -> Union[str, None]:
-        """Finds the units associated with a parameter type element and parsed them to return a unit string.
-        We assume only one <xtce:Unit> but this could be extended to support multiple units.
-        See section 4.3.2.2.4 of CCSDS 660.1-G-1
-
-        Parameters
-        ----------
-        parameter_type_element : ElementTree.Element
-            The parameter type element
-        ns : dict
-            XML namespace dictionary
-
-        Returns
-        -------
-        : Union[str, None]
-            Unit string or None if no units are defined
-        """
-        encoding_element = parameter_type_element.find('xtce:Encoding', ns)
-        if encoding_element and "units" in encoding_element.attrib:
-            units = encoding_element.attrib["units"]
-            return units
-        # Units are optional so return None if they aren't specified
-        return None
-
-    @staticmethod
-    def get_time_unit_linear_scaler(
-            parameter_type_element: ElementTree.Element, ns: dict) -> Union[PolynomialCalibrator, None]:
-        """Finds the linear calibrator associated with the Encoding element for the parameter type element.
-        See section 4.3.2.4.8.3 of CCSDS 660.1-G-2
-
-        Parameters
-        ----------
-        parameter_type_element : ElementTree.Element
-            The parameter type element
-        ns : dict
-            XML namespace dictionary
-
-        Returns
-        -------
-        : Union[PolynomialCalibrator, None]
-            The PolynomialCalibrator, or None if we couldn't create a valid calibrator from the XML element
-        """
-        encoding_element = parameter_type_element.find('xtce:Encoding', ns)
-        coefficients = []
-
-        if "offset" in encoding_element.attrib:
-            offset = encoding_element.attrib["offset"]
-            c0 = PolynomialCoefficient(coefficient=float(offset), exponent=0)
-            coefficients.append(c0)
-
-        if "scale" in encoding_element.attrib:
-            scale = encoding_element.attrib["scale"]
-            c1 = PolynomialCoefficient(coefficient=float(scale), exponent=1)
-            coefficients.append(c1)
-        # If we have an offset but not a scale, we need to add a first order term with coefficient 1
-        elif "offset" in encoding_element.attrib:
-            c1 = PolynomialCoefficient(coefficient=1, exponent=1)
-            coefficients.append(c1)
-
-        if coefficients:
-            return PolynomialCalibrator(coefficients=coefficients)
-        # If we didn't find offset nor scale, return None (no calibrator)
-        return None
-
-    @staticmethod
-    def get_epoch(parameter_type_element: ElementTree.Element, ns: dict) -> Union[str, None]:
-        """Finds the epoch associated with a parameter type element and parses them to return an epoch string.
-        See section 4.3.2.4.9 of CCSDS 660.1-G-2
-
-        Parameters
-        ----------
-        parameter_type_element : ElementTree.Element
-            The parameter type element
-        ns : dict
-            XML namespace dictionary
-
-        Returns
-        -------
-        : Union[str, None]
-            The epoch string, which may be a datetime string or a named epoch such as TAI. None if the element was
-            not found.
-        """
-        epoch_element = parameter_type_element.find('xtce:ReferenceTime/xtce:Epoch', ns)
-        if epoch_element is not None:
-            return epoch_element.text
-        return None
-
-    @staticmethod
-    def get_offset_from(parameter_type_element: ElementTree.Element, ns: dict) -> Union[str, None]:
-        """Finds the parameter referenced in OffsetFrom in a parameter type element and returns the name of the
-        referenced parameter (which must be of type TimeParameterType).
-        See section 4.3.2.4.9 of CCSDS 660.1-G-1
-
-        Parameters
-        ----------
-        parameter_type_element : ElementTree.Element
-            The parameter type element
-        ns : dict
-            XML namespace dictionary
-
-        Returns
-        -------
-        : Union[str, None]
-            The named of the referenced parameter. None if no OffsetFrom element was found.
-        """
-        offset_from_element = parameter_type_element.find('xtce:ReferenceTime/xtce:OffsetFrom', ns)
-        if offset_from_element is not None:
-            return offset_from_element.attrib['parameterRef']
-        return None
-
-
-class AbsoluteTimeParameterType(TimeParameterType):
-    """<xtce:AbsoluteTimeParameterType>"""
-    pass
-
-
-class RelativeTimeParameterType(TimeParameterType):
-    """<xtce:RelativeTimeParameterType>"""
-    pass
-
-
-class Parseable(Protocol):
-    """Defines an object that can be parsed from packet data."""
-    def parse(self, packet, **parse_value_kwargs) -> dict:
-        """Parse this entry from the packet data and add the necessary items to the parsed_items dictionary."""
-
-
-@dataclass
-class Parameter(Parseable):
-    """<xtce:Parameter>
-
-    Parameters
-    ----------
-    name : str
-        Parameter name. Typically something like MSN__PARAMNAME
-    parameter_type : ParameterType
-        Parameter type object that describes how the parameter is stored.
-    short_description : str
-        Short description of parameter as parsed from XTCE
-    long_description : str
-        Long description of parameter as parsed from XTCE
-    """
-    name: str
-    parameter_type: ParameterType
-    short_description: Optional[str] = None
-    long_description: Optional[str] = None
-
-    def parse(self, packet: Packet, **parse_value_kwargs) -> dict:
-        """Parse this parameter from the packet data.
-
-        Create a ``ParsedDataItem`` and add it to the parsed_items dictionary.
-        """
-        parsed_value, derived_value = self.parameter_type.parse_value(
-            packet, **parse_value_kwargs)
-
-        packet.parsed_data[self.name] = ParsedDataItem(
-            name=self.name,
-            unit=self.parameter_type.unit,
-            raw_value=parsed_value,
-            derived_value=derived_value,
-            short_description=self.short_description,
-            long_description=self.long_description
-        )
-        return packet.parsed_data
-
-
-@dataclass
-class SequenceContainer(Parseable):
-    """<xtce:SequenceContainer>
-
-    Parameters
-    ----------
-    name : str
-        Container name
-    entry_list : list
-        List of Parameter objects
-    long_description : str
-        Long description of the container
-    base_container_name : str
-        Name of the base container from which this may inherit if restriction criteria are met.
-    restriction_criteria : list
-        A list of MatchCriteria elements that evaluate to determine whether the SequenceContainer should
-        be included.
-    abstract : bool
-        True if container has abstract=true attribute. False otherwise.
-    inheritors : list, Optional
-        List of SequenceContainer objects that may inherit this one's entry list if their restriction criteria
-        are met. Any SequenceContainers with this container as base_container_name should be listed here.
-    """
-    name: str
-    entry_list: list  # List of Parameter objects, found by reference
-    short_description: Optional[str] = None
-    long_description: Optional[str] = None
-    base_container_name: Optional[str] = None
-    restriction_criteria: Optional[list] = field(default_factory=lambda: [])
-    abstract: bool = False
-    inheritors: Optional[List['SequenceContainer']] = field(default_factory=lambda: [])
-
-    def __post_init__(self):
-        # Handle the explicit None passing for default values
-        self.restriction_criteria = self.restriction_criteria or []
-        self.inheritors = self.inheritors or []
-
-    def parse(self, packet: Packet, **parse_value_kwargs) -> dict:
-        """Parse the entry list of parameters/containers in the order they are expected in the packet.
-
-        This could be recursive if the entry list contains SequenceContainers.
-        """
-        for entry in self.entry_list:
-            packet.parsed_data = entry.parse(packet=packet, **parse_value_kwargs)
-        return packet.parsed_data
-
-
-FlattenedContainer = namedtuple('FlattenedContainer', ['entry_list', 'restrictions'])
-
-
-class XtcePacketDefinition:
-    """Object representation of the XTCE definition of a CCSDS packet object"""
-
-    _tag_to_type_template = {
-        '{{{xtce}}}StringParameterType': StringParameterType,
-        '{{{xtce}}}IntegerParameterType': IntegerParameterType,
-        '{{{xtce}}}FloatParameterType': FloatParameterType,
-        '{{{xtce}}}EnumeratedParameterType': EnumeratedParameterType,
-        '{{{xtce}}}BinaryParameterType': BinaryParameterType,
-        '{{{xtce}}}BooleanParameterType': BooleanParameterType,
-        '{{{xtce}}}AbsoluteTimeParameterType': AbsoluteTimeParameterType,
-        '{{{xtce}}}RelativeTimeParameterType': RelativeTimeParameterType,
-    }
-
-    def __init__(self, xtce_document: TextIO, ns: Optional[dict] = None):
-        """Instantiate an object representation of a CCSDS packet definition, according to a format specified in an XTCE
-        XML document. The parser iteratively builds sequences of parameters according to the
-        SequenceContainers specified in the XML document's ContainerSet element. The notions of container inheritance
-        (via BaseContainer) and nested container (by including a SequenceContainer within a SequenceContainer) are
-        supported. Exclusion of containers based on topLevelPacket in AncillaryData is not supported, so all
-        containers are examined and returned.
-
-        Parameters
-        ----------
-        xtce_document : TextIO
-            Path to XTCE XML document containing packet definition.
-        ns : Optional[dict]
-            Optional different namespace than the default xtce namespace.
-        """
-        self._sequence_container_cache = {}  # Lookup for parsed sequence container objects
-        self._parameter_cache = {}  # Lookup for parsed parameter objects
-        self._parameter_type_cache = {}  # Lookup for parsed parameter type objects
-        self.tree = ElementTree.parse(xtce_document)
-        self.ns = ns or self.tree.getroot().nsmap
-        self.type_tag_to_object = {k.format(**self.ns): v for k, v in
-                                   self._tag_to_type_template.items()}
-
-        self._populate_sequence_container_cache()
-
-    def __getitem__(self, item):
-        return self._sequence_container_cache[item]
-
-    def _populate_sequence_container_cache(self):
-        """Force populating sequence_container_cache by parsing all SequenceContainers"""
-        for sequence_container in self.container_set.iterfind('xtce:SequenceContainer', self.ns):
-            self._sequence_container_cache[
-                sequence_container.attrib['name']
-            ] = self.parse_sequence_container_contents(sequence_container)
-
-        # Back-populate the list of inheritors for each container
-        for name, sc in self._sequence_container_cache.items():
-            if sc.base_container_name:
-                self._sequence_container_cache[sc.base_container_name].inheritors.append(name)
-
-    def parse_sequence_container_contents(self, sequence_container: ElementTree.Element) -> SequenceContainer:
-        """Parses the list of parameters in a SequenceContainer element, recursively parsing nested SequenceContainers
-        to build an entry list of parameters that flattens the nested structure to derive a sequential ordering of
-        expected parameters for each SequenceContainer. Note that this also stores entry lists for containers that are
-        not intended to stand alone.
-
-        Parameters
-        ----------
-        sequence_container : ElementTree.Element
-            The SequenceContainer element to parse.
-
-        Returns
-        -------
-        : SequenceContainer
-            SequenceContainer containing an entry_list of SequenceContainers and Parameters
-            in the order expected in a packet.
-        """
-        entry_list = []  # List to house Parameters for the current SequenceContainer
-        try:
-            base_container, restriction_criteria = self._get_container_base_container(sequence_container)
-            base_sequence_container = self.parse_sequence_container_contents(base_container)
-            base_container_name = base_sequence_container.name
-        except ElementNotFoundError:
-            base_container_name = None
-            restriction_criteria = None
-
-        container_contents = sequence_container.find('xtce:EntryList', self.ns).findall('*', self.ns)
-
-        for entry in container_contents:
-            if entry.tag == '{{{xtce}}}ParameterRefEntry'.format(**self.ns):  # pylint: disable=consider-using-f-string
-                parameter_name = entry.attrib['parameterRef']
-
-                # If we've already parsed this parameter in a different container
-                if parameter_name in self._parameter_cache:
-                    entry_list.append(self._parameter_cache[parameter_name])
-                else:
-                    parameter_element = self._find_parameter(parameter_name)
-                    parameter_type_name = parameter_element.attrib['parameterTypeRef']
-
-                    # If we've already parsed this parameter type for a different parameter
-                    if parameter_type_name in self._parameter_type_cache:
-                        parameter_type_object = self._parameter_type_cache[parameter_type_name]
-                    else:
-                        parameter_type_element = self._find_parameter_type(parameter_type_name)
-                        try:
-                            parameter_type_class = self.type_tag_to_object[parameter_type_element.tag]
-                        except KeyError as e:
-                            if (
-                                    "ArrayParameterType" in parameter_type_element.tag or
-                                    "AggregateParameterType" in parameter_type_element.tag
-                            ):
-                                raise NotImplementedError(f"Unsupported parameter type {parameter_type_element.tag}. "
-                                                          "Supporting this parameter type is in the roadmap but has "
-                                                          "not yet been implemented.") from e
-                            raise InvalidParameterTypeError(f"Invalid parameter type {parameter_type_element.tag}. "
-                                                            "If you believe this is a valid XTCE parameter type, "
-                                                            "please open a feature request as a Github issue with a "
-                                                            "reference to the XTCE element description for the "
-                                                            "parameter type element.") from e
-                        parameter_type_object = parameter_type_class.from_parameter_type_xml_element(
-                            parameter_type_element, self.ns)
-                        self._parameter_type_cache[parameter_type_name] = parameter_type_object  # Add to cache
-
-                    parameter_short_description = parameter_element.attrib['shortDescription'] if (
-                        'shortDescription' in parameter_element.attrib
-                    ) else None
-                    parameter_long_description = parameter_element.find('xtce:LongDescription', self.ns).text if (
-                        parameter_element.find('xtce:LongDescription', self.ns) is not None
-                    ) else None
-
-                    parameter_object = Parameter(
-                        name=parameter_name,
-                        parameter_type=parameter_type_object,
-                        short_description=parameter_short_description,
-                        long_description=parameter_long_description
-                    )
-                    entry_list.append(parameter_object)
-                    self._parameter_cache[parameter_name] = parameter_object  # Add to cache
-            elif entry.tag == '{{{xtce}}}ContainerRefEntry'.format(  # pylint: disable=consider-using-f-string
-                    **self.ns):
-                nested_container = self._find_container(name=entry.attrib['containerRef'])
-                entry_list.append(self.parse_sequence_container_contents(nested_container))
-
-        short_description = sequence_container.attrib['shortDescription'] if (
-            'shortDescription' in sequence_container.attrib
-        ) else None
-        long_description = sequence_container.find('xtce:LongDescription', self.ns).text if (
-            sequence_container.find('xtce:LongDescription', self.ns) is not None
-        ) else None
-
-        return SequenceContainer(name=sequence_container.attrib['name'],
-                                 entry_list=entry_list,
-                                 base_container_name=base_container_name,
-                                 restriction_criteria=restriction_criteria,
-                                 abstract=self._is_abstract_container(sequence_container),
-                                 short_description=short_description,
-                                 long_description=long_description)
-
-    @property
-    def named_containers(self) -> Dict[str, SequenceContainer]:
-        """Property accessor that returns the dict cache of SequenceContainer objects"""
-        return self._sequence_container_cache
-
-    @property
-    def named_parameters(self) -> Dict[str, Parameter]:
-        """Property accessor that returns the dict cache of Parameter objects"""
-        return self._parameter_cache
-
-    @property
-    def named_parameter_types(self) -> Dict[str, ParameterType]:
-        """Property accessor that returns the dict cache of ParameterType objects"""
-        return self._parameter_type_cache
-
-    # DEPRECATED! This is only used by CSV-parser code. Remove for 5.0.0 release
-    @property
-    def flattened_containers(self):
-        """Accesses a flattened, generic representation of non-abstract packet definitions along with their
-        aggregated inheritance
-        restrictions.
-
-        Returns
-        -------
-        : dict
-            A modified form of the _sequence_container_cache, flattened out to eliminate nested sequence containers
-            and with all restriction logic aggregated together for easy comparisons.
-            {
-            "PacketNameA": {
-            FlattenedContainer(
-            entry_list=[Parameter, Parameter, ...],
-            restrictions={"ParameterName": value, "OtherParamName": value, ...}
-            )
-            },
-            "PacketNameB": {
-            FlattenedContainer(
-            entry_list=[Parameter, Parameter, ...],
-            restrictions={"ParameterName": value, "OtherParamName": value, ...}
-            )
-            }, ...
-            }
-        """
-
-        def flatten_container(sequence_container: SequenceContainer):
-            """Flattens the representation of a SequenceContainer object into a list of Parameters (in order) and
-            an aggregated dictionary of restriction criteria where the keys are Parameter names and the values are the
-            required values of those parameters in order to adopt the SequenceContainer's definition.
-
-            Parameters
-            ----------
-            sequence_container : SequenceContainer
-                SequenceContainer object to flatten, recursively.
-
-            Returns
-            -------
-            : list
-                List of Parameters, in order.
-            : dict
-                Dictionary of required Parameter values in order to use this definition.
-            """
-            aggregated_entry_list = []
-            aggregated_restrictions = []
-            for entry in sequence_container.entry_list:
-                if isinstance(entry, SequenceContainer):
-                    if entry.restriction_criteria:
-                        aggregated_restrictions += entry.restriction_criteria
-                    entry_list, restrictions = flatten_container(entry)
-                    aggregated_entry_list += entry_list
-                    aggregated_restrictions += restrictions
-                elif isinstance(entry, Parameter):
-                    aggregated_entry_list.append(entry)
-            return aggregated_entry_list, aggregated_restrictions
-
-        warnings.warn("The 'flattened_containers' property is deprecated to allow for dynamic container "
-                      "inheritance matching during parsing.", DeprecationWarning)
-        return {
-            name: FlattenedContainer(*flatten_container(sc))
-            for name, sc in self._sequence_container_cache.items()
-            if not sc.abstract
-        }
-
-    @property
-    def container_set(self) -> ElementTree.Element:
-        """Property that returns the <xtce:ContainerSet> element, containing all the sequence container elements."""
-        return self.tree.getroot().find('xtce:TelemetryMetaData/xtce:ContainerSet', self.ns)
-
-    @property
-    def parameter_type_set(self) -> ElementTree.Element:
-        """Property that returns the <xtce:ParameterTypeSet> element, containing all parameter type elements."""
-        return self.tree.getroot().find('xtce:TelemetryMetaData/xtce:ParameterTypeSet', self.ns)
-
-    @property
-    def parameter_set(self) -> ElementTree.Element:
-        """Property that returns the <xtce:ParameterSet> element, containing all parameter elements."""
-        return self.tree.getroot().find('xtce:TelemetryMetaData/xtce:ParameterSet', self.ns)
-
-    @staticmethod
-    def _is_abstract_container(container_element: ElementTree.Element) -> bool:
-        """Determine in a SequenceContainer element is abstract
-
-        Parameters
-        ----------
-        container_element : ElementTree.Element
-            SequenceContainer element to examine
-
-        Returns
-        -------
-        : bool
-            True if SequenceContainer element has the attribute abstract=true. False otherwise.
-        """
-        if 'abstract' in container_element.attrib:
-            return container_element.attrib['abstract'].lower() == 'true'
-        return False
-
-    def _find_container(self, name: str) -> ElementTree.Element:
-        """Finds an XTCE container <xtce:SequenceContainer> by name.
-
-        Parameters
-        ----------
-        name : str
-            Name of the container to find
-
-        Returns
-        -------
-        : ElementTree.Element
-        """
-        matches = self.container_set.findall(f"./xtce:SequenceContainer[@name='{name}']", self.ns)
-        assert len(matches) == 1, f"Found {len(matches)} matching container_set with name {name}. " \
-                                  f"Container names are expected to exist and be unique."
-        return matches[0]
-
-    def _find_parameter(self, name: str) -> ElementTree.Element:
-        """Finds an XTCE Parameter in the tree.
-
-        Parameters
-        ----------
-        name : str
-            Name of the parameter to find
-
-        Returns
-        -------
-        : ElementTree.Element
-        """
-        matches = self.parameter_set.findall(f"./xtce:Parameter[@name='{name}']", self.ns)
-        assert len(matches) == 1, f"Found {len(matches)} matching parameters with name {name}. " \
-                                  f"Parameter names are expected to exist and be unique."
-        return matches[0]
-
-    def _find_parameter_type(self, name: str) -> ElementTree.Element:
-        """Finds an XTCE ParameterType in the tree.
-
-        Parameters
-        ----------
-        name : str
-            Name of the parameter type to find
-
-        Returns
-        -------
-        : ElementTree.Element
-        """
-        matches = self.parameter_type_set.findall(f"./*[@name='{name}']", self.ns)
-        assert len(matches) == 1, f"Found {len(matches)} matching parameter types with name {name}. " \
-                                  f"Parameter type names are expected to exist and be unique."
-        return matches[0]
-
-    def _get_container_base_container(
-            self,
-            container_element: ElementTree.Element) -> Tuple[ElementTree.Element, List[MatchCriteria]]:
-        """Examines the container_element and returns information about its inheritance.
-
-        Parameters
-        ----------
-        container_element : ElementTree.Element
-            The container element for which to find its base container.
-
-        Returns
-        -------
-        : ElementTree.Element
-            The base container element of the input container_element.
-        : list
-            The restriction criteria for the inheritance.
-        """
-        base_container_element = container_element.find('xtce:BaseContainer', self.ns)
-        if base_container_element is None:
-            raise ElementNotFoundError(
-                f"Container element {container_element} does not have a BaseContainer child element.")
-
-        restriction_criteria_element = base_container_element.find('xtce:RestrictionCriteria', self.ns)
-        if restriction_criteria_element is not None:
-            comparison_list_element = restriction_criteria_element.find('xtce:ComparisonList', self.ns)
-            single_comparison_element = restriction_criteria_element.find('xtce:Comparison', self.ns)
-            boolean_expression_element = restriction_criteria_element.find('xtce:BooleanExpression', self.ns)
-            custom_algorithm_element = restriction_criteria_element.find('xtce:CustomAlgorithm', self.ns)
-            if custom_algorithm_element is not None:
-                raise NotImplementedError("Detected a CustomAlgorithm in a RestrictionCriteria element. "
-                                          "This is not implemented.")
-
-            if comparison_list_element is not None:
-                comparisons = comparison_list_element.findall('xtce:Comparison', self.ns)
-                restrictions = [Comparison.from_match_criteria_xml_element(comp, self.ns) for comp in comparisons]
-            elif single_comparison_element is not None:
-                restrictions = [Comparison.from_match_criteria_xml_element(single_comparison_element, self.ns)]
-            elif boolean_expression_element is not None:
-                restrictions = [BooleanExpression.from_match_criteria_xml_element(boolean_expression_element, self.ns)]
-            else:
-                raise ValueError("Detected a RestrictionCriteria element containing no "
-                                 "Comparison, ComparisonList, BooleanExpression or CustomAlgorithm.")
-            # TODO: Implement NextContainer support inside RestrictionCriteria. This might make the parser much
-            #    more complicated.
-        else:
-            restrictions = []
-        return self._find_container(base_container_element.attrib['containerRef']), restrictions
-
-
-def _extract_bits(data: bytes, start_bit: int, nbits: int):
-    """Extract nbits from the data starting from the least significant end.
-
-    If data = 00110101 11001010, start_bit = 2, nbits = 9, then the bits extracted are "110101110".
-    Those bits are turned into a Python integer and returned.
-
-    Parameters
-    ----------
-    data : bytes
-        Data to extract bits from
-    start_bit : int
-        Starting bit location within the data
-    nbits : int
-        Number of bits to extract
-
-    Returns
-    -------
-    int
-        Extracted bits as an integer
-    """
-    # Get the bits from the packet data
-    # Select the bytes that contain the bits we want.
-    start_byte = start_bit // 8  # Byte index containing the start_bit
-    start_bit_within_byte = start_bit % 8  # Bit index within the start_byte
-    end_byte = start_byte + (start_bit_within_byte + nbits + 7) // 8
-    data = data[start_byte:end_byte]  # Chunk of bytes containing the data item we want to parse
-    # Convert the bytes to an integer for bitwise operations
-    value = int.from_bytes(data, byteorder="big")
-    if start_bit_within_byte == 0 and nbits % 8 == 0:
-        # If we're extracting whole bytes starting at a byte boundary, we don't need any bitshifting
-        # This is faster, especially for large binary chunks
-        return value
-
-    # Shift the value to the right to move the LSB of the data item we want to parse
-    # to the least significant position, then mask out the number of bits we want to keep
-    return (value >> (len(data) * 8 - start_bit_within_byte - nbits)) & (2 ** nbits - 1)


Test patch:
diff --git a/tests/integration/test_bufferedreader_parsing.py b/tests/integration/test_bufferedreader_parsing.py
index e1d79ed..fd75c14 100644
--- a/tests/integration/test_bufferedreader_parsing.py
+++ b/tests/integration/test_bufferedreader_parsing.py
@@ -1,14 +1,14 @@
 """Integration test for parsing JPSS packets"""
 # Local
-from space_packet_parser import xtcedef
-from space_packet_parser import parser
+from space_packet_parser import definitions
+from space_packet_parser import parser, parseables
 
 
 def test_jpss_xtce_packet_parsing(jpss_test_data_dir):
     """Test parsing a real XTCE document"""
     jpss_xtce = jpss_test_data_dir / 'jpss1_geolocation_xtce_v1.xml'
-    jpss_definition = xtcedef.XtcePacketDefinition(xtce_document=jpss_xtce)
-    assert isinstance(jpss_definition, xtcedef.XtcePacketDefinition)
+    jpss_definition = definitions.XtcePacketDefinition(xtce_document=jpss_xtce)
+    assert isinstance(jpss_definition, definitions.XtcePacketDefinition)
     jpss_parser = parser.PacketParser(jpss_definition)
 
     jpss_packet_file = jpss_test_data_dir / 'J01_G011_LZ_2021-04-09T00-00-00Z_V01.DAT1'
@@ -17,7 +17,7 @@ def test_jpss_xtce_packet_parsing(jpss_test_data_dir):
         jpss_packet_generator = jpss_parser.generator(binary_data, show_progress=True)
         n_packets = 0
         for jpss_packet in jpss_packet_generator:
-            assert isinstance(jpss_packet, parser.Packet)
+            assert isinstance(jpss_packet, parseables.Packet)
             assert jpss_packet.header['PKT_APID'].raw_value == 11
             assert jpss_packet.header['VERSION'].raw_value == 0
             n_packets += 1
diff --git a/tests/integration/test_csv_based_parsing/test_ctim_parsing.py b/tests/integration/test_csv_based_parsing/test_ctim_parsing.py
index 195af19..7d7ce93 100644
--- a/tests/integration/test_csv_based_parsing/test_ctim_parsing.py
+++ b/tests/integration/test_csv_based_parsing/test_ctim_parsing.py
@@ -1,12 +1,12 @@
 """Test parsing of CTIM packet data"""
 # Local
-from space_packet_parser import csvdef, parser
+from space_packet_parser import definitions, parser
 
 
 def test_csv_packet_definition_parsing(ctim_test_data_dir):
     """Test parsing a real csv document"""
     test_csv_file = ctim_test_data_dir / 'ct_tlm.csv'
-    csv_pkt_def = csvdef.CsvPacketDefinition(test_csv_file)
+    csv_pkt_def = definitions.CsvPacketDefinition(test_csv_file)
 
     test_packet_file = ctim_test_data_dir / 'ccsds_2021_155_14_39_51'
     parser_inst = parser.PacketParser(csv_pkt_def)
diff --git a/tests/integration/test_socket_parsing.py b/tests/integration/test_socket_parsing.py
index 83ff589..905c18d 100644
--- a/tests/integration/test_socket_parsing.py
+++ b/tests/integration/test_socket_parsing.py
@@ -8,7 +8,7 @@
 import pytest
 
 # Local
-from space_packet_parser.xtcedef import XtcePacketDefinition
+from space_packet_parser.definitions import XtcePacketDefinition
 from space_packet_parser.parser import PacketParser
 
 
diff --git a/tests/integration/test_xtce_based_parsing/test_inheritance_restrictions.py b/tests/integration/test_xtce_based_parsing/test_inheritance_restrictions.py
index 0fcfb77..2d7534e 100644
--- a/tests/integration/test_xtce_based_parsing/test_inheritance_restrictions.py
+++ b/tests/integration/test_xtce_based_parsing/test_inheritance_restrictions.py
@@ -1,14 +1,14 @@
 """Test RestrictionCriteria being used creatively with JPSS data"""
 # Local
-from space_packet_parser import xtcedef
-from space_packet_parser import parser
+from space_packet_parser import definitions
+from space_packet_parser import parser, parseables
 
 
 def test_jpss_xtce_packet_parsing(jpss_test_data_dir):
     """Test parsing a real XTCE document"""
     jpss_xtce = jpss_test_data_dir / 'contrived_inheritance_structure.xml'
-    jpss_definition = xtcedef.XtcePacketDefinition(xtce_document=jpss_xtce)
-    assert isinstance(jpss_definition, xtcedef.XtcePacketDefinition)
+    jpss_definition = definitions.XtcePacketDefinition(xtce_document=jpss_xtce)
+    assert isinstance(jpss_definition, definitions.XtcePacketDefinition)
     jpss_parser = parser.PacketParser(jpss_definition)
 
     jpss_packet_file = jpss_test_data_dir / 'J01_G011_LZ_2021-04-09T00-00-00Z_V01.DAT1'
@@ -16,7 +16,7 @@ def test_jpss_xtce_packet_parsing(jpss_test_data_dir):
         jpss_packet_generator = jpss_parser.generator(binary_data)
         for _ in range(3):  # Iterate through 3 packets and check that the parsed APID remains the same
             jpss_packet = next(jpss_packet_generator)
-            assert isinstance(jpss_packet, parser.Packet)
+            assert isinstance(jpss_packet, parseables.Packet)
             assert jpss_packet.header['PKT_APID'].raw_value == 11
             assert jpss_packet.header['VERSION'].raw_value == 0
         jpss_packet_generator.close()
diff --git a/tests/integration/test_xtce_based_parsing/test_jpss_parsing.py b/tests/integration/test_xtce_based_parsing/test_jpss_parsing.py
index 7f1d44d..e6fee23 100644
--- a/tests/integration/test_xtce_based_parsing/test_jpss_parsing.py
+++ b/tests/integration/test_xtce_based_parsing/test_jpss_parsing.py
@@ -1,14 +1,14 @@
 """Integration test for parsing JPSS packets"""
 # Local
-from space_packet_parser import xtcedef
-from space_packet_parser import parser
+from space_packet_parser import definitions
+from space_packet_parser import parser, parseables
 
 
 def test_jpss_xtce_packet_parsing(jpss_test_data_dir):
     """Test parsing a real XTCE document"""
     jpss_xtce = jpss_test_data_dir / 'jpss1_geolocation_xtce_v1.xml'
-    jpss_definition = xtcedef.XtcePacketDefinition(xtce_document=jpss_xtce)
-    assert isinstance(jpss_definition, xtcedef.XtcePacketDefinition)
+    jpss_definition = definitions.XtcePacketDefinition(xtce_document=jpss_xtce)
+    assert isinstance(jpss_definition, definitions.XtcePacketDefinition)
     jpss_parser = parser.PacketParser(jpss_definition)
 
     jpss_packet_file = jpss_test_data_dir / 'J01_G011_LZ_2021-04-09T00-00-00Z_V01.DAT1'
@@ -18,7 +18,7 @@ def test_jpss_xtce_packet_parsing(jpss_test_data_dir):
 
         n_packets = 0
         for jpss_packet in jpss_packet_generator:
-            assert isinstance(jpss_packet, parser.Packet)
+            assert isinstance(jpss_packet, parseables.Packet)
             assert jpss_packet.header['PKT_APID'].raw_value == 11
             assert jpss_packet.header['VERSION'].raw_value == 0
             assert jpss_packet.data['USEC'].short_description == "Secondary Header Fine Time (microsecond)"
diff --git a/tests/integration/test_xtce_based_parsing/test_suda_parsing.py b/tests/integration/test_xtce_based_parsing/test_suda_parsing.py
index bf4a826..1883ad0 100644
--- a/tests/integration/test_xtce_based_parsing/test_suda_parsing.py
+++ b/tests/integration/test_xtce_based_parsing/test_suda_parsing.py
@@ -4,8 +4,8 @@
 The data used here is SUDA data but the fields are parsed using IDEX naming conventions.
 """
 # Local
-from space_packet_parser import xtcedef
-from space_packet_parser import parser
+from space_packet_parser import definitions
+from space_packet_parser import parser, parseables
 
 
 def parse_hg_waveform(waveform_raw: str):
@@ -45,8 +45,8 @@ def parse_waveform_data(waveform: str, scitype: int):
 def test_suda_xtce_packet_parsing(suda_test_data_dir):
     """Test parsing a real XTCE document"""
     suda_xtce = suda_test_data_dir / 'suda_combined_science_definition.xml'
-    suda_definition = xtcedef.XtcePacketDefinition(xtce_document=suda_xtce)
-    assert isinstance(suda_definition, xtcedef.XtcePacketDefinition)
+    suda_definition = definitions.XtcePacketDefinition(xtce_document=suda_xtce)
+    assert isinstance(suda_definition, definitions.XtcePacketDefinition)
     suda_parser = parser.PacketParser(suda_definition)
     suda_packet_file = suda_test_data_dir / 'sciData_2022_130_17_41_53.spl'
 
@@ -55,7 +55,7 @@ def test_suda_xtce_packet_parsing(suda_test_data_dir):
                                                       skip_header_bits=32,
                                                       show_progress=True)
         for suda_packet in suda_packet_generator:
-            assert isinstance(suda_packet, parser.Packet)
+            assert isinstance(suda_packet, parseables.Packet)
             assert suda_packet.header['PKT_APID'].raw_value == 1425, "APID is not as expected."
             assert suda_packet.header['VERSION'].raw_value == 0, "CCSDS header VERSION incorrect."
 
diff --git a/tests/unit/test_csvdef.py b/tests/unit/test_csvdef.py
index aed11fd..f4b7c5c 100644
--- a/tests/unit/test_csvdef.py
+++ b/tests/unit/test_csvdef.py
@@ -1,44 +1,44 @@
 """Tests for the CSV based packet definition"""
 import pytest
 # Local
-from space_packet_parser import csvdef, xtcedef, parser
-from space_packet_parser.csvdef import CsvPacketDefinition
+from space_packet_parser import definitions, parameters, encodings, parseables, parser
+from space_packet_parser.definitions import CsvPacketDefinition
 
 
 @pytest.mark.parametrize(
     ('dtype_str', 'name', 'expectation'),
     [
-        ('U11', 'test_uint', xtcedef.IntegerParameterType(name='test_uint',
-                                                          encoding=xtcedef.IntegerDataEncoding(11, 'unsigned'),
+        ('U11', 'test_uint', parameters.IntegerParameterType(name='test_uint',
+                                                          encoding=encodings.IntegerDataEncoding(11, 'unsigned'),
                                                           unit='foo')),
-        ('U5', 'test_uint', xtcedef.IntegerParameterType(name='test_uint',
-                                                         encoding=xtcedef.IntegerDataEncoding(5, 'unsigned'),
+        ('U5', 'test_uint', parameters.IntegerParameterType(name='test_uint',
+                                                         encoding=encodings.IntegerDataEncoding(5, 'unsigned'),
                                                          unit='foo')),
-        ('UINT11', 'test_uint', xtcedef.IntegerParameterType(name='test_uint',
-                                                             encoding=xtcedef.IntegerDataEncoding(11, 'unsigned'),
+        ('UINT11', 'test_uint', parameters.IntegerParameterType(name='test_uint',
+                                                             encoding=encodings.IntegerDataEncoding(11, 'unsigned'),
                                                              unit='foo')),
-        ('D3', 'test_discrete', xtcedef.IntegerParameterType(name='test_discrete',
-                                                             encoding=xtcedef.IntegerDataEncoding(3, 'unsigned'),
+        ('D3', 'test_discrete', parameters.IntegerParameterType(name='test_discrete',
+                                                             encoding=encodings.IntegerDataEncoding(3, 'unsigned'),
                                                              unit='foo')),
-        ('INT16', 'test_uint', xtcedef.IntegerParameterType(name='test_uint',
-                                                            encoding=xtcedef.IntegerDataEncoding(16, 'signed'),
+        ('INT16', 'test_uint', parameters.IntegerParameterType(name='test_uint',
+                                                            encoding=encodings.IntegerDataEncoding(16, 'signed'),
                                                             unit='foo')),
-        ('I16', 'test_uint', xtcedef.IntegerParameterType(name='test_uint',
-                                                          encoding=xtcedef.IntegerDataEncoding(16, 'signed'),
+        ('I16', 'test_uint', parameters.IntegerParameterType(name='test_uint',
+                                                          encoding=encodings.IntegerDataEncoding(16, 'signed'),
                                                           unit='foo')),
-        ('F16', 'test_flt', xtcedef.FloatParameterType(name='test_flt',
-                                                       encoding=xtcedef.FloatDataEncoding(16),
+        ('F16', 'test_flt', parameters.FloatParameterType(name='test_flt',
+                                                       encoding=encodings.FloatDataEncoding(16),
                                                        unit='foo')),
-        ('Float16', 'test_flt', xtcedef.FloatParameterType(name='test_flt',
-                                                           encoding=xtcedef.FloatDataEncoding(16),
+        ('Float16', 'test_flt', parameters.FloatParameterType(name='test_flt',
+                                                           encoding=encodings.FloatDataEncoding(16),
                                                            unit='foo')),
-        ('C12', 'test_str', xtcedef.StringParameterType(name='test_str',
-                                                        encoding=xtcedef.StringDataEncoding(fixed_length=12),
+        ('C12', 'test_str', parameters.StringParameterType(name='test_str',
+                                                        encoding=encodings.StringDataEncoding(fixed_length=12),
                                                         unit='foo'))
     ]
 )
 def test_get_param_type_from_str(dtype_str: str, name, expectation):
-    actual = csvdef.CsvPacketDefinition.get_param_type_from_str(dtype=dtype_str,
+    actual = definitions.CsvPacketDefinition.get_param_type_from_str(dtype=dtype_str,
                                                                 param_type_name=name,
                                                                 unit='foo')
 
@@ -58,4 +58,4 @@ def test_csv_packet_definition(ctim_test_data_dir):
         parser_inst = parser.PacketParser(csv_pkt_def)
         pkt_gen = parser_inst.generator(pkt_file, show_progress=True)
         packet = next(pkt_gen)
-    assert isinstance(packet, xtcedef.Packet)
+    assert isinstance(packet, parseables.Packet)
diff --git a/tests/unit/test_parser.py b/tests/unit/test_parser.py
index de14481..77e2da8 100644
--- a/tests/unit/test_parser.py
+++ b/tests/unit/test_parser.py
@@ -2,7 +2,7 @@
 # Installed
 import pytest
 # Local
-from space_packet_parser import parser
+from space_packet_parser import parseables
 
 
 @pytest.mark.parametrize(
@@ -16,7 +16,7 @@
 )
 def test_parsed_data_item(name, raw_value, unit, derived_value, short_description, long_description, valid):
     """Test ParsedDataItem"""
-    pdi = parser.ParsedDataItem(name, raw_value, unit, derived_value, short_description, long_description)
+    pdi = parseables.ParsedDataItem(name, raw_value, unit, derived_value, short_description, long_description)
     assert pdi.name == name
     assert pdi.raw_value == raw_value
     assert pdi.unit == unit
diff --git a/tests/unit/test_xtcedef.py b/tests/unit/test_xtcedef.py
index ee1f768..98416d3 100644
--- a/tests/unit/test_xtcedef.py
+++ b/tests/unit/test_xtcedef.py
@@ -5,7 +5,8 @@
 import pytest
 import lxml.etree as ElementTree
 # Local
-from space_packet_parser import xtcedef, parser
+from space_packet_parser.exceptions import CalibrationError, ComparisonError
+from space_packet_parser import calibrators, comparisons, definitions, encodings, parameters, parseables, parser
 
 XTCE_URI = "http://www.omg.org/space/xtce"
 TEST_NAMESPACE = {'xtce': XTCE_URI}
@@ -40,8 +41,8 @@ def test_invalid_parameter_type_error(test_data_dir):
 </xtce:SpaceSystem>
 """
     x = io.TextIOWrapper(io.BytesIO(test_xtce_document.encode("UTF-8")))
-    with pytest.raises(xtcedef.InvalidParameterTypeError):
-        xtcedef.XtcePacketDefinition(x)
+    with pytest.raises(definitions.InvalidParameterTypeError):
+        definitions.XtcePacketDefinition(x)
 
 
 def test_unsupported_parameter_type_error(test_data_dir):
@@ -82,12 +83,12 @@ def test_unsupported_parameter_type_error(test_data_dir):
 """
     x = io.TextIOWrapper(io.BytesIO(test_xtce_document.encode("UTF-8")))
     with pytest.raises(NotImplementedError):
-        xtcedef.XtcePacketDefinition(x)
+        definitions.XtcePacketDefinition(x)
 
 
 def test_attr_comparable():
     """Test abstract class that allows comparisons based on all non-callable attributes"""
-    class TestClass(xtcedef.AttrComparable):
+    class TestClass(comparisons.AttrComparable):
         """Test Class"""
         def __init__(self, public, private, dunder):
             self.public = public
@@ -119,82 +120,82 @@ def ignored(self, x):
 <xtce:Comparison xmlns:xtce="http://www.omg.org/space/xtce" 
     comparisonOperator="==" value="678" parameterRef="MSN__PARAM"/>
 """,
-         {'MSN__PARAM': parser.ParsedDataItem('MSN__PARAM', 3, None, 678)}, None, True),
+         {'MSN__PARAM': parseables.ParsedDataItem('MSN__PARAM', 3, None, 678)}, None, True),
         ("""
 <xtce:Comparison xmlns:xtce="http://www.omg.org/space/xtce" 
     comparisonOperator="eq" value="678" parameterRef="MSN__PARAM"/>
 """,
-         {'MSN__PARAM': parser.ParsedDataItem('MSN__PARAM', 3, None, 668)}, None, False),
+         {'MSN__PARAM': parseables.ParsedDataItem('MSN__PARAM', 3, None, 668)}, None, False),
         ("""
 <xtce:Comparison xmlns:xtce="http://www.omg.org/space/xtce" 
     comparisonOperator="!=" value="678" parameterRef="MSN__PARAM"/>
 """,
-         {'MSN__PARAM': parser.ParsedDataItem('MSN__PARAM', 3, None, 678)}, None, False),
+         {'MSN__PARAM': parseables.ParsedDataItem('MSN__PARAM', 3, None, 678)}, None, False),
         ("""
 <xtce:Comparison xmlns:xtce="http://www.omg.org/space/xtce" 
     comparisonOperator="neq" value="678" parameterRef="MSN__PARAM"/>
 """,
-         {'MSN__PARAM': parser.ParsedDataItem('MSN__PARAM', 3, None, 658)}, None, True),
+         {'MSN__PARAM': parseables.ParsedDataItem('MSN__PARAM', 3, None, 658)}, None, True),
         ("""
 <xtce:Comparison xmlns:xtce="http://www.omg.org/space/xtce" 
     comparisonOperator="&lt;" value="678" parameterRef="MSN__PARAM"/>
 """,
-         {'MSN__PARAM': parser.ParsedDataItem('MSN__PARAM', 3, None, 679)}, None, False),
+         {'MSN__PARAM': parseables.ParsedDataItem('MSN__PARAM', 3, None, 679)}, None, False),
         ("""
 <xtce:Comparison xmlns:xtce="http://www.omg.org/space/xtce" 
     comparisonOperator="lt" value="678" parameterRef="MSN__PARAM"/>
 """,
-         {'MSN__PARAM': parser.ParsedDataItem('MSN__PARAM', 3, None, 670)}, None, True),
+         {'MSN__PARAM': parseables.ParsedDataItem('MSN__PARAM', 3, None, 670)}, None, True),
         ("""
 <xtce:Comparison xmlns:xtce="http://www.omg.org/space/xtce" 
     comparisonOperator="&gt;" value="678" parameterRef="MSN__PARAM"/>
 """,
-         {'MSN__PARAM': parser.ParsedDataItem('MSN__PARAM', 3, None, 678)}, None, False),
+         {'MSN__PARAM': parseables.ParsedDataItem('MSN__PARAM', 3, None, 678)}, None, False),
         ("""
 <xtce:Comparison xmlns:xtce="http://www.omg.org/space/xtce" 
     comparisonOperator="gt" value="678" parameterRef="MSN__PARAM"/>
 """,
-         {'MSN__PARAM': parser.ParsedDataItem('MSN__PARAM', 3, None, 679)}, None, True),
+         {'MSN__PARAM': parseables.ParsedDataItem('MSN__PARAM', 3, None, 679)}, None, True),
         ("""
 <xtce:Comparison xmlns:xtce="http://www.omg.org/space/xtce" 
     comparisonOperator="&lt;=" value="678" parameterRef="MSN__PARAM"/>
 """,
-         {'MSN__PARAM': parser.ParsedDataItem('MSN__PARAM', 3, None, 660)}, None, True),
+         {'MSN__PARAM': parseables.ParsedDataItem('MSN__PARAM', 3, None, 660)}, None, True),
         ("""
 <xtce:Comparison xmlns:xtce="http://www.omg.org/space/xtce" 
     comparisonOperator="leq" value="678" parameterRef="MSN__PARAM"/>
 """,
-         {'MSN__PARAM': parser.ParsedDataItem('MSN__PARAM', 3, None, 690)}, None, False),
+         {'MSN__PARAM': parseables.ParsedDataItem('MSN__PARAM', 3, None, 690)}, None, False),
         ("""
 <xtce:Comparison xmlns:xtce="http://www.omg.org/space/xtce" 
     comparisonOperator="&gt;=" value="678" parameterRef="MSN__PARAM"/>
 """,
-         {'MSN__PARAM': parser.ParsedDataItem('MSN__PARAM', 3, None, 660)}, None, False),
+         {'MSN__PARAM': parseables.ParsedDataItem('MSN__PARAM', 3, None, 660)}, None, False),
         ("""
 <xtce:Comparison xmlns:xtce="http://www.omg.org/space/xtce" 
     comparisonOperator="geq" value="678" parameterRef="MSN__PARAM"/>
 """,
-         {'MSN__PARAM': parser.ParsedDataItem('MSN__PARAM', 3, None, 690)}, None, True),
+         {'MSN__PARAM': parseables.ParsedDataItem('MSN__PARAM', 3, None, 690)}, None, True),
         ("""
 <xtce:Comparison xmlns:xtce="http://www.omg.org/space/xtce" 
     comparisonOperator="==" value="678" parameterRef="MSN__PARAM" useCalibratedValue="false"/>
 """,
-         {'MSN__PARAM': parser.ParsedDataItem('MSN__PARAM', 678, None, 690)}, None, True),
+         {'MSN__PARAM': parseables.ParsedDataItem('MSN__PARAM', 678, None, 690)}, None, True),
         ("""
 <xtce:Comparison xmlns:xtce="http://www.omg.org/space/xtce" 
     comparisonOperator="==" value="678" parameterRef="MSN__PARAM" useCalibratedValue="true"/>
 """,
-         {'MSN__PARAM': parser.ParsedDataItem('MSN__PARAM', 3, None, 678)}, None, True),
+         {'MSN__PARAM': parseables.ParsedDataItem('MSN__PARAM', 3, None, 678)}, None, True),
         ("""
 <xtce:Comparison xmlns:xtce="http://www.omg.org/space/xtce" 
     comparisonOperator="==" value="foostring" parameterRef="MSN__PARAM" useCalibratedValue="false"/>
 """,
-         {'MSN__PARAM': parser.ParsedDataItem('MSN__PARAM', 'foostring', None, 'calibratedfoostring')}, None, True),
+         {'MSN__PARAM': parseables.ParsedDataItem('MSN__PARAM', 'foostring', None, 'calibratedfoostring')}, None, True),
         ("""
 <xtce:Comparison xmlns:xtce="http://www.omg.org/space/xtce" 
     comparisonOperator="==" value="3.14" parameterRef="MSN__PARAM"/>
 """,
-         {'MSN__PARAM': parser.ParsedDataItem('MSN__PARAM', 1, None, 3.14)}, None, True),
+         {'MSN__PARAM': parseables.ParsedDataItem('MSN__PARAM', 1, None, 3.14)}, None, True),
         ("""
 <xtce:Comparison xmlns:xtce="http://www.omg.org/space/xtce" 
     comparisonOperator="==" value="3.0" parameterRef="REFERENCE_TO_OWN_RAW_VAL"/>
@@ -214,7 +215,7 @@ def ignored(self, x):
 <xtce:Comparison xmlns:xtce="http://www.omg.org/space/xtce" 
     comparisonOperator="==" value="3.0" parameterRef="REFERENCE_TO_OWN_RAW_VAL"/>
 """,
-         {}, 3, xtcedef.ComparisonError("Fails to parse a float string 3.0 into an int")),
+         {}, 3, ComparisonError("Fails to parse a float string 3.0 into an int")),
     ]
 )
 def test_comparison(xml_string, test_parsed_data, current_parsed_value, expected_comparison_result):
@@ -222,10 +223,10 @@ def test_comparison(xml_string, test_parsed_data, current_parsed_value, expected
     element = ElementTree.fromstring(xml_string)
     if isinstance(expected_comparison_result, Exception):
         with pytest.raises(type(expected_comparison_result)):
-            comparison = xtcedef.Comparison.from_match_criteria_xml_element(element, TEST_NAMESPACE)
+            comparison = comparisons.Comparison.from_match_criteria_xml_element(element, TEST_NAMESPACE)
             comparison.evaluate(test_parsed_data, current_parsed_value)
     else:
-        comparison = xtcedef.Comparison.from_match_criteria_xml_element(element, TEST_NAMESPACE)
+        comparison = comparisons.Comparison.from_match_criteria_xml_element(element, TEST_NAMESPACE)
         assert comparison.evaluate(test_parsed_data, current_parsed_value) == expected_comparison_result
 
 
@@ -239,8 +240,8 @@ def test_comparison(xml_string, test_parsed_data, current_parsed_value, expected
     <xtce:ParameterInstanceRef parameterRef="P2"/>
 </xtce:Condition>
 """,
-         {'P1': parser.ParsedDataItem('P1', 4, None, 700),
-          'P2': parser.ParsedDataItem('P2', 3, None, 678)}, True),
+         {'P1': parseables.ParsedDataItem('P1', 4, None, 700),
+          'P2': parseables.ParsedDataItem('P2', 3, None, 678)}, True),
         ("""
 <xtce:Condition xmlns:xtce="http://www.omg.org/space/xtce">
     <xtce:ParameterInstanceRef parameterRef="P1"/>
@@ -248,7 +249,7 @@ def test_comparison(xml_string, test_parsed_data, current_parsed_value, expected
     <xtce:Value>4</xtce:Value>
 </xtce:Condition>
 """,
-         {'P1': parser.ParsedDataItem('P1', 4, None, 700)}, True),
+         {'P1': parseables.ParsedDataItem('P1', 4, None, 700)}, True),
         ("""
 <xtce:Condition xmlns:xtce="http://www.omg.org/space/xtce">
     <xtce:ParameterInstanceRef parameterRef="P1"/>
@@ -256,8 +257,8 @@ def test_comparison(xml_string, test_parsed_data, current_parsed_value, expected
     <xtce:ParameterInstanceRef parameterRef="P2"/>
 </xtce:Condition>
 """,
-         {'P1': parser.ParsedDataItem('P1', 4, None, 700),
-          'P2': parser.ParsedDataItem('P2', 3, None, 678)}, False),
+         {'P1': parseables.ParsedDataItem('P1', 4, None, 700),
+          'P2': parseables.ParsedDataItem('P2', 3, None, 678)}, False),
         ("""
 <xtce:Condition xmlns:xtce="http://www.omg.org/space/xtce">
     <xtce:ParameterInstanceRef parameterRef="P1" useCalibratedValue="false"/>
@@ -265,8 +266,8 @@ def test_comparison(xml_string, test_parsed_data, current_parsed_value, expected
     <xtce:ParameterInstanceRef parameterRef="P2" useCalibratedValue="false"/>
 </xtce:Condition>
 """,
-         {'P1': parser.ParsedDataItem('P1', 'abcd', None),
-          'P2': parser.ParsedDataItem('P2', 'abcd', None)}, True),
+         {'P1': parseables.ParsedDataItem('P1', 'abcd', None),
+          'P2': parseables.ParsedDataItem('P2', 'abcd', None)}, True),
         ("""
 <xtce:Condition xmlns:xtce="http://www.omg.org/space/xtce">
     <xtce:ParameterInstanceRef parameterRef="P1"/>
@@ -274,14 +275,14 @@ def test_comparison(xml_string, test_parsed_data, current_parsed_value, expected
     <xtce:ParameterInstanceRef parameterRef="P2"/>
 </xtce:Condition>
 """,
-         {'P1': parser.ParsedDataItem('P1', 1, None, 3.14),
-          'P2': parser.ParsedDataItem('P2', 180, None, 3.14)}, True),
+         {'P1': parseables.ParsedDataItem('P1', 1, None, 3.14),
+          'P2': parseables.ParsedDataItem('P2', 180, None, 3.14)}, True),
     ]
 )
 def test_condition(xml_string, test_parsed_data, expected_condition_result):
     """Test Condition object"""
     element = ElementTree.fromstring(xml_string)
-    condition = xtcedef.Condition.from_match_criteria_xml_element(element, TEST_NAMESPACE)
+    condition = comparisons.Condition.from_match_criteria_xml_element(element, TEST_NAMESPACE)
     assert condition.evaluate(test_parsed_data, None) == expected_condition_result
 
 
@@ -311,10 +312,10 @@ def test_condition(xml_string, test_parsed_data, expected_condition_result):
     </xtce:ORedConditions>
 </xtce:BooleanExpression>
 """,
-         {'P': parser.ParsedDataItem('P', 4, None, 0),
-          'P2': parser.ParsedDataItem('P2', 4, None, 700),
-          'P3': parser.ParsedDataItem('P3', 4, None, 701),
-          'P4': parser.ParsedDataItem('P4', 4, None, 98)}, True),
+         {'P': parseables.ParsedDataItem('P', 4, None, 0),
+          'P2': parseables.ParsedDataItem('P2', 4, None, 700),
+          'P3': parseables.ParsedDataItem('P3', 4, None, 701),
+          'P4': parseables.ParsedDataItem('P4', 4, None, 98)}, True),
         ("""
 <xtce:BooleanExpression xmlns:xtce="http://www.omg.org/space/xtce">
     <xtce:ANDedConditions>
@@ -343,12 +344,12 @@ def test_condition(xml_string, test_parsed_data, expected_condition_result):
     </xtce:ANDedConditions>
 </xtce:BooleanExpression>
 """,
-         {'P': parser.ParsedDataItem('P', 4, None, 100),
-          'P0': parser.ParsedDataItem('P0', 4, None, 678),
-          'P1': parser.ParsedDataItem('P1', 4, None, 500),
-          'P2': parser.ParsedDataItem('P2', 4, None, 700),
-          'P3': parser.ParsedDataItem('P3', 4, None, 701),
-          'P4': parser.ParsedDataItem('P4', 4, None, 99)}, True),
+         {'P': parseables.ParsedDataItem('P', 4, None, 100),
+          'P0': parseables.ParsedDataItem('P0', 4, None, 678),
+          'P1': parseables.ParsedDataItem('P1', 4, None, 500),
+          'P2': parseables.ParsedDataItem('P2', 4, None, 700),
+          'P3': parseables.ParsedDataItem('P3', 4, None, 701),
+          'P4': parseables.ParsedDataItem('P4', 4, None, 99)}, True),
     ]
 )
 def test_boolean_expression(xml_string, test_parsed_data, expected_result):
@@ -356,9 +357,9 @@ def test_boolean_expression(xml_string, test_parsed_data, expected_result):
     element = ElementTree.fromstring(xml_string)
     if isinstance(expected_result, Exception):
         with pytest.raises(type(expected_result)):
-            xtcedef.BooleanExpression.from_match_criteria_xml_element(element, TEST_NAMESPACE)
+            comparisons.BooleanExpression.from_match_criteria_xml_element(element, TEST_NAMESPACE)
     else:
-        expression = xtcedef.BooleanExpression.from_match_criteria_xml_element(element, TEST_NAMESPACE)
+        expression = comparisons.BooleanExpression.from_match_criteria_xml_element(element, TEST_NAMESPACE)
         assert expression.evaluate(test_parsed_data, current_parsed_value=None) == expected_result
 
 
@@ -370,13 +371,13 @@ def test_boolean_expression(xml_string, test_parsed_data, expected_result):
     <xtce:Comparison useCalibratedValue="false" parameterRef="P1" value="1"/>
 </xtce:DiscreteLookup>
 """,
-         {'P1': parser.ParsedDataItem('P1', 1, None, 678)}, 10),
+         {'P1': parseables.ParsedDataItem('P1', 1, None, 678)}, 10),
         ("""
 <xtce:DiscreteLookup value="10" xmlns:xtce="http://www.omg.org/space/xtce">
     <xtce:Comparison useCalibratedValue="false" parameterRef="P1" value="1"/>
 </xtce:DiscreteLookup>
 """,
-         {'P1': parser.ParsedDataItem('P1', 0, None, 678)}, None),
+         {'P1': parseables.ParsedDataItem('P1', 0, None, 678)}, None),
         ("""
 <xtce:DiscreteLookup value="11" xmlns:xtce="http://www.omg.org/space/xtce">
     <xtce:ComparisonList>
@@ -386,15 +387,15 @@ def test_boolean_expression(xml_string, test_parsed_data, expected_result):
 </xtce:DiscreteLookup>
 """,
          {
-             'MSN__PARAM1': parser.ParsedDataItem('MSN__PARAM1', 3, None, 680),
-             'MSN__PARAM2': parser.ParsedDataItem('MSN__PARAM2', 3, None, 3000)
+             'MSN__PARAM1': parseables.ParsedDataItem('MSN__PARAM1', 3, None, 680),
+             'MSN__PARAM2': parseables.ParsedDataItem('MSN__PARAM2', 3, None, 3000)
          }, 11),
     ]
 )
 def test_discrete_lookup(xml_string, test_parsed_data, expected_lookup_result):
     """Test DiscreteLookup object"""
     element = ElementTree.fromstring(xml_string)
-    discrete_lookup = xtcedef.DiscreteLookup.from_discrete_lookup_xml_element(element, TEST_NAMESPACE)
+    discrete_lookup = comparisons.DiscreteLookup.from_discrete_lookup_xml_element(element, TEST_NAMESPACE)
     assert discrete_lookup.evaluate(test_parsed_data, current_parsed_value=None) == expected_lookup_result
 
 
@@ -423,19 +424,19 @@ def test_discrete_lookup(xml_string, test_parsed_data, expected_lookup_result):
     </xtce:Calibrator>
 </xtce:ContextCalibrator>
 """,
-         xtcedef.ContextCalibrator(
+         calibrators.ContextCalibrator(
              match_criteria=[
-                 xtcedef.Comparison(required_value='678', referenced_parameter='EXI__FPGAT', operator='>=',
+                 comparisons.Comparison(required_value='678', referenced_parameter='EXI__FPGAT', operator='>=',
                                     use_calibrated_value=True),
-                 xtcedef.Comparison(required_value='4096', referenced_parameter='EXI__FPGAT', operator='<',
+                 comparisons.Comparison(required_value='4096', referenced_parameter='EXI__FPGAT', operator='<',
                                     use_calibrated_value=True),
              ],
-             calibrator=xtcedef.PolynomialCalibrator(coefficients=[
-                 xtcedef.PolynomialCoefficient(coefficient=0.5, exponent=0),
-                 xtcedef.PolynomialCoefficient(coefficient=1.5, exponent=1),
-                 xtcedef.PolynomialCoefficient(coefficient=-0.045, exponent=2),
-                 xtcedef.PolynomialCoefficient(coefficient=1.25, exponent=3),
-                 xtcedef.PolynomialCoefficient(coefficient=0.0025, exponent=4)
+             calibrator=calibrators.PolynomialCalibrator(coefficients=[
+                 calibrators.PolynomialCoefficient(coefficient=0.5, exponent=0),
+                 calibrators.PolynomialCoefficient(coefficient=1.5, exponent=1),
+                 calibrators.PolynomialCoefficient(coefficient=-0.045, exponent=2),
+                 calibrators.PolynomialCoefficient(coefficient=1.25, exponent=3),
+                 calibrators.PolynomialCoefficient(coefficient=0.0025, exponent=4)
              ]))),
         ("""
 <xtce:ContextCalibrator xmlns:xtce="http://www.omg.org/space/xtce">
@@ -453,17 +454,17 @@ def test_discrete_lookup(xml_string, test_parsed_data, expected_lookup_result):
     </xtce:Calibrator>
 </xtce:ContextCalibrator>
 """,
-         xtcedef.ContextCalibrator(
+         calibrators.ContextCalibrator(
              match_criteria=[
-                 xtcedef.Comparison(required_value='3.14', referenced_parameter='EXI__FPGAT', operator='!=',
+                 comparisons.Comparison(required_value='3.14', referenced_parameter='EXI__FPGAT', operator='!=',
                                     use_calibrated_value=True),
              ],
-             calibrator=xtcedef.PolynomialCalibrator(coefficients=[
-                 xtcedef.PolynomialCoefficient(coefficient=0.5, exponent=0),
-                 xtcedef.PolynomialCoefficient(coefficient=1.5, exponent=1),
-                 xtcedef.PolynomialCoefficient(coefficient=-0.045, exponent=2),
-                 xtcedef.PolynomialCoefficient(coefficient=1.25, exponent=3),
-                 xtcedef.PolynomialCoefficient(coefficient=0.0025, exponent=4)
+             calibrator=calibrators.PolynomialCalibrator(coefficients=[
+                 calibrators.PolynomialCoefficient(coefficient=0.5, exponent=0),
+                 calibrators.PolynomialCoefficient(coefficient=1.5, exponent=1),
+                 calibrators.PolynomialCoefficient(coefficient=-0.045, exponent=2),
+                 calibrators.PolynomialCoefficient(coefficient=1.25, exponent=3),
+                 calibrators.PolynomialCoefficient(coefficient=0.0025, exponent=4)
              ]))),
         ("""
 <xtce:ContextCalibrator xmlns:xtce="http://www.omg.org/space/xtce">
@@ -491,23 +492,23 @@ def test_discrete_lookup(xml_string, test_parsed_data, expected_lookup_result):
     </xtce:Calibrator>
 </xtce:ContextCalibrator>
 """,
-         xtcedef.ContextCalibrator(
+         calibrators.ContextCalibrator(
              match_criteria=[
-                 xtcedef.BooleanExpression(
-                     expression=xtcedef.Anded(
+                 comparisons.BooleanExpression(
+                     expression=comparisons.Anded(
                          conditions=[
-                             xtcedef.Condition(left_param='P1', operator='==', right_value='100',
+                             comparisons.Condition(left_param='P1', operator='==', right_value='100',
                                                right_use_calibrated_value=False),
-                             xtcedef.Condition(left_param='P4', operator='!=', right_value='99',
+                             comparisons.Condition(left_param='P4', operator='!=', right_value='99',
                                                right_use_calibrated_value=False)
                          ],
                          ors=[]
                      )
                  ),
              ],
-             calibrator=xtcedef.PolynomialCalibrator(coefficients=[
-                 xtcedef.PolynomialCoefficient(coefficient=0.5, exponent=0),
-                 xtcedef.PolynomialCoefficient(coefficient=1.5, exponent=1),
+             calibrator=calibrators.PolynomialCalibrator(coefficients=[
+                 calibrators.PolynomialCoefficient(coefficient=0.5, exponent=0),
+                 calibrators.PolynomialCoefficient(coefficient=1.5, exponent=1),
              ]))),
     ]
 )
@@ -515,79 +516,79 @@ def test_context_calibrator(xml_string, expectation):
     """Test parsing a ContextCalibrator from an XML element"""
     element = ElementTree.fromstring(xml_string)
 
-    result = xtcedef.ContextCalibrator.from_context_calibrator_xml_element(element, TEST_NAMESPACE)
+    result = calibrators.ContextCalibrator.from_context_calibrator_xml_element(element, TEST_NAMESPACE)
     assert result == expectation
 
 
 @pytest.mark.parametrize(
     ('context_calibrator', 'parsed_data', 'parsed_value', 'match_expectation', 'expectation'),
     [
-        (xtcedef.ContextCalibrator(
+        (calibrators.ContextCalibrator(
              match_criteria=[
-                 xtcedef.Comparison(required_value='678', referenced_parameter='EXI__FPGAT', operator='>=',
+                 comparisons.Comparison(required_value='678', referenced_parameter='EXI__FPGAT', operator='>=',
                                     use_calibrated_value=True),
-                 xtcedef.Comparison(required_value='4096', referenced_parameter='EXI__FPGAT', operator='<',
+                 comparisons.Comparison(required_value='4096', referenced_parameter='EXI__FPGAT', operator='<',
                                     use_calibrated_value=True),
              ],
-             calibrator=xtcedef.PolynomialCalibrator(coefficients=[
-                 xtcedef.PolynomialCoefficient(coefficient=0.5, exponent=0),
-                 xtcedef.PolynomialCoefficient(coefficient=1.5, exponent=1)
+             calibrator=calibrators.PolynomialCalibrator(coefficients=[
+                 calibrators.PolynomialCoefficient(coefficient=0.5, exponent=0),
+                 calibrators.PolynomialCoefficient(coefficient=1.5, exponent=1)
              ])),
-            {"EXI__FPGAT": parser.ParsedDataItem("EXI__FPGAT", 600, derived_value=700)},
+            {"EXI__FPGAT": parseables.ParsedDataItem("EXI__FPGAT", 600, derived_value=700)},
             42, True, 63.5),
-        (xtcedef.ContextCalibrator(
+        (calibrators.ContextCalibrator(
              match_criteria=[
-                 xtcedef.Comparison(required_value='3.14', referenced_parameter='EXI__FPGAT', operator='!=',
+                 comparisons.Comparison(required_value='3.14', referenced_parameter='EXI__FPGAT', operator='!=',
                                     use_calibrated_value=True),
              ],
-             calibrator=xtcedef.PolynomialCalibrator(coefficients=[
-                 xtcedef.PolynomialCoefficient(coefficient=0.5, exponent=0),
-                 xtcedef.PolynomialCoefficient(coefficient=1.5, exponent=1),
+             calibrator=calibrators.PolynomialCalibrator(coefficients=[
+                 calibrators.PolynomialCoefficient(coefficient=0.5, exponent=0),
+                 calibrators.PolynomialCoefficient(coefficient=1.5, exponent=1),
              ])),
-         {"EXI__FPGAT": parser.ParsedDataItem("EXI__FPGAT", 3.14, derived_value=700.0)},
+         {"EXI__FPGAT": parseables.ParsedDataItem("EXI__FPGAT", 3.14, derived_value=700.0)},
          42, True, 63.5),
-        (xtcedef.ContextCalibrator(
+        (calibrators.ContextCalibrator(
              match_criteria=[
-                 xtcedef.BooleanExpression(
-                     expression=xtcedef.Anded(
+                 comparisons.BooleanExpression(
+                     expression=comparisons.Anded(
                          conditions=[
-                             xtcedef.Condition(left_param='P1', operator='==', right_value='700',
+                             comparisons.Condition(left_param='P1', operator='==', right_value='700',
                                                right_use_calibrated_value=False),
-                             xtcedef.Condition(left_param='P2', operator='!=', right_value='99',
+                             comparisons.Condition(left_param='P2', operator='!=', right_value='99',
                                                right_use_calibrated_value=False)
                          ],
                          ors=[]
                      )
                  ),
              ],
-             calibrator=xtcedef.PolynomialCalibrator(coefficients=[
-                 xtcedef.PolynomialCoefficient(coefficient=0.5, exponent=0),
-                 xtcedef.PolynomialCoefficient(coefficient=1.5, exponent=1),
+             calibrator=calibrators.PolynomialCalibrator(coefficients=[
+                 calibrators.PolynomialCoefficient(coefficient=0.5, exponent=0),
+                 calibrators.PolynomialCoefficient(coefficient=1.5, exponent=1),
              ])),
-         {"P1": parser.ParsedDataItem("P1", 100.0, derived_value=700.0),
-          "P2": parser.ParsedDataItem("P2", 99, derived_value=700.0)},
+         {"P1": parseables.ParsedDataItem("P1", 100.0, derived_value=700.0),
+          "P2": parseables.ParsedDataItem("P2", 99, derived_value=700.0)},
          42, True, 63.5),
-        (xtcedef.ContextCalibrator(
+        (calibrators.ContextCalibrator(
              match_criteria=[
-                 xtcedef.BooleanExpression(
-                     expression=xtcedef.Ored(
+                 comparisons.BooleanExpression(
+                     expression=comparisons.Ored(
                          conditions=[  # Neither of these are true given the parsed data so far
-                             xtcedef.Condition(left_param='P1', operator='==', right_value='700',
+                             comparisons.Condition(left_param='P1', operator='==', right_value='700',
                                                left_use_calibrated_value=False,
                                                right_use_calibrated_value=False),
-                             xtcedef.Condition(left_param='P2', operator='!=', right_value='700',
+                             comparisons.Condition(left_param='P2', operator='!=', right_value='700',
                                                right_use_calibrated_value=False)
                          ],
                          ands=[]
                      )
                  ),
              ],
-             calibrator=xtcedef.PolynomialCalibrator(coefficients=[
-                 xtcedef.PolynomialCoefficient(coefficient=0.5, exponent=0),
-                 xtcedef.PolynomialCoefficient(coefficient=1.5, exponent=1),
+             calibrator=calibrators.PolynomialCalibrator(coefficients=[
+                 calibrators.PolynomialCoefficient(coefficient=0.5, exponent=0),
+                 calibrators.PolynomialCoefficient(coefficient=1.5, exponent=1),
              ])),
-         {"P1": parser.ParsedDataItem("P1", 100.0, derived_value=700.0),
-          "P2": parser.ParsedDataItem("P2", 99, derived_value=700.0)},
+         {"P1": parseables.ParsedDataItem("P1", 100.0, derived_value=700.0),
+          "P2": parseables.ParsedDataItem("P2", 99, derived_value=700.0)},
          42, False, 63.5),
     ]
 )
@@ -613,10 +614,10 @@ def test_context_calibrator_calibrate(context_calibrator, parsed_data, parsed_va
     <xtce:SplinePoint raw="3" calibrated="5E2"/>
 </xtce:SplineCalibrator> 
 """,
-         xtcedef.SplineCalibrator(order=0, extrapolate=True, points=[
-             xtcedef.SplinePoint(raw=1, calibrated=10),
-             xtcedef.SplinePoint(raw=2.7, calibrated=100.948),
-             xtcedef.SplinePoint(raw=3, calibrated=500),
+         calibrators.SplineCalibrator(order=0, extrapolate=True, points=[
+             calibrators.SplinePoint(raw=1, calibrated=10),
+             calibrators.SplinePoint(raw=2.7, calibrated=100.948),
+             calibrators.SplinePoint(raw=3, calibrated=500),
          ])),
         ("""
 <xtce:SplineCalibrator xmlns:xtce="http://www.omg.org/space/xtce">
@@ -625,10 +626,10 @@ def test_context_calibrator_calibrate(context_calibrator, parsed_data, parsed_va
     <xtce:SplinePoint raw="3" calibrated="5E2"/>
 </xtce:SplineCalibrator> 
 """,
-         xtcedef.SplineCalibrator(order=0, extrapolate=False, points=[
-             xtcedef.SplinePoint(raw=1, calibrated=10),
-             xtcedef.SplinePoint(raw=2.7, calibrated=100.948),
-             xtcedef.SplinePoint(raw=3, calibrated=500),
+         calibrators.SplineCalibrator(order=0, extrapolate=False, points=[
+             calibrators.SplinePoint(raw=1, calibrated=10),
+             calibrators.SplinePoint(raw=2.7, calibrated=100.948),
+             calibrators.SplinePoint(raw=3, calibrated=500),
          ])),
         ]
 )
@@ -638,9 +639,9 @@ def test_spline_calibrator(xml_string: str, expectation):
 
     if isinstance(expectation, Exception):
         with pytest.raises(type(expectation)):
-            xtcedef.SplineCalibrator.from_calibrator_xml_element(element, TEST_NAMESPACE)
+            calibrators.SplineCalibrator.from_calibrator_xml_element(element, TEST_NAMESPACE)
     else:
-        result = xtcedef.SplineCalibrator.from_calibrator_xml_element(element, TEST_NAMESPACE)
+        result = calibrators.SplineCalibrator.from_calibrator_xml_element(element, TEST_NAMESPACE)
         assert result == expectation
 
 
@@ -649,30 +650,30 @@ def test_spline_calibrator(xml_string: str, expectation):
     [
         # Zero order
         (-10, 0, True, 0.),
-        (-10, 0, False, xtcedef.CalibrationError()),
+        (-10, 0, False, CalibrationError()),
         (-1, 0, True, 0.),
         (-1, 0, False, 0.),
         (1.5, 0, False, 3.),
-        (5., 0, False, xtcedef.CalibrationError()),
+        (5., 0, False, CalibrationError()),
         (5., 0, True, 2.),
         # First order
         (-10, 1, True, -27.),
-        (-10, 1, False, xtcedef.CalibrationError()),
+        (-10, 1, False, CalibrationError()),
         (-1, 1, True, 0.),
         (-1, 1, False, 0.),
         (1.5, 1, False, 2.25),
-        (5., 1, False, xtcedef.CalibrationError()),
+        (5., 1, False, CalibrationError()),
         (5., 1, True, 0.5),
     ],
 )
 def test_spline_calibrator_calibrate(xq, order, extrapolate, expectation):
     """Test spline default_calibrator interpolation routines"""
     spline_points = [
-        xtcedef.SplinePoint(-1., 0.),
-        xtcedef.SplinePoint(0., 3.),
-        xtcedef.SplinePoint(2., 2),
+        calibrators.SplinePoint(-1., 0.),
+        calibrators.SplinePoint(0., 3.),
+        calibrators.SplinePoint(2., 2),
     ]
-    calibrator = xtcedef.SplineCalibrator(spline_points, order=order, extrapolate=extrapolate)
+    calibrator = calibrators.SplineCalibrator(spline_points, order=order, extrapolate=extrapolate)
 
     if isinstance(expectation, Exception):
         with pytest.raises(type(expectation)):
@@ -694,12 +695,12 @@ def test_spline_calibrator_calibrate(xq, order, extrapolate, expectation):
     <xtce:Term exponent="4" coefficient="2.5E-3"/>
 </xtce:PolynomialCalibrator> 
 """,
-         xtcedef.PolynomialCalibrator(coefficients=[
-             xtcedef.PolynomialCoefficient(coefficient=0.5, exponent=0),
-             xtcedef.PolynomialCoefficient(coefficient=1.5, exponent=1),
-             xtcedef.PolynomialCoefficient(coefficient=-0.045, exponent=2),
-             xtcedef.PolynomialCoefficient(coefficient=1.25, exponent=3),
-             xtcedef.PolynomialCoefficient(coefficient=0.0025, exponent=4),
+         calibrators.PolynomialCalibrator(coefficients=[
+             calibrators.PolynomialCoefficient(coefficient=0.5, exponent=0),
+             calibrators.PolynomialCoefficient(coefficient=1.5, exponent=1),
+             calibrators.PolynomialCoefficient(coefficient=-0.045, exponent=2),
+             calibrators.PolynomialCoefficient(coefficient=1.25, exponent=3),
+             calibrators.PolynomialCoefficient(coefficient=0.0025, exponent=4),
          ])),
         ]
 )
@@ -709,9 +710,9 @@ def test_polynomial_calibrator(xml_string: str, expectation):
 
     if isinstance(expectation, Exception):
         with pytest.raises(type(expectation)):
-            xtcedef.PolynomialCalibrator.from_calibrator_xml_element(element, TEST_NAMESPACE)
+            calibrators.PolynomialCalibrator.from_calibrator_xml_element(element, TEST_NAMESPACE)
     else:
-        result = xtcedef.PolynomialCalibrator.from_calibrator_xml_element(element, TEST_NAMESPACE)
+        result = calibrators.PolynomialCalibrator.from_calibrator_xml_element(element, TEST_NAMESPACE)
         assert result == expectation
 
 
@@ -726,11 +727,11 @@ def test_polynomial_calibrator(xml_string: str, expectation):
 def test_polynomial_calibrator_calibrate(xq, expectation):
     """Test polynomial default_calibrator interpolation routines"""
     polynomial_coefficients = [
-        xtcedef.PolynomialCoefficient(1.5, 0),
-        xtcedef.PolynomialCoefficient(0, 1),
-        xtcedef.PolynomialCoefficient(1., 2)
+        calibrators.PolynomialCoefficient(1.5, 0),
+        calibrators.PolynomialCoefficient(0, 1),
+        calibrators.PolynomialCoefficient(1., 2)
     ]
-    calibrator = xtcedef.PolynomialCalibrator(polynomial_coefficients)
+    calibrator = calibrators.PolynomialCalibrator(polynomial_coefficients)
 
     if isinstance(expectation, Exception):
         with pytest.raises(type(expectation)):
@@ -753,7 +754,7 @@ def test_polynomial_calibrator_calibrate(xq, expectation):
     </xtce:SizeInBits>
 </xtce:StringDataEncoding>
 """,
-         xtcedef.StringDataEncoding(termination_character='0058', encoding='UTF-16BE')),
+         encodings.StringDataEncoding(termination_character='0058', encoding='UTF-16BE')),
         ("""
 <xtce:StringDataEncoding xmlns:xtce="http://www.omg.org/space/xtce">
     <xtce:SizeInBits>
@@ -763,7 +764,7 @@ def test_polynomial_calibrator_calibrate(xq, expectation):
     </xtce:SizeInBits>
 </xtce:StringDataEncoding>
 """,
-         xtcedef.StringDataEncoding(fixed_length=17)),
+         encodings.StringDataEncoding(fixed_length=17)),
         ("""
 <xtce:StringDataEncoding xmlns:xtce="http://www.omg.org/space/xtce">
     <xtce:SizeInBits>
@@ -776,7 +777,7 @@ def test_polynomial_calibrator_calibrate(xq, expectation):
     </xtce:SizeInBits>
 </xtce:StringDataEncoding>
 """,
-         xtcedef.StringDataEncoding(dynamic_length_reference='SizeFromThisParameter',
+         encodings.StringDataEncoding(dynamic_length_reference='SizeFromThisParameter',
                                     length_linear_adjuster=object())),
         ("""
 <xtce:StringDataEncoding xmlns:xtce="http://www.omg.org/space/xtce">
@@ -794,10 +795,10 @@ def test_polynomial_calibrator_calibrate(xq, expectation):
     </xtce:SizeInBits>
 </xtce:StringDataEncoding>
 """,
-         xtcedef.StringDataEncoding(
+         encodings.StringDataEncoding(
              discrete_lookup_length=[
-                 xtcedef.DiscreteLookup([xtcedef.Comparison('1', 'P1')], 10),
-                 xtcedef.DiscreteLookup([xtcedef.Comparison('2', 'P1')], 25)
+                 comparisons.DiscreteLookup([comparisons.Comparison('1', 'P1')], 10),
+                 comparisons.DiscreteLookup([comparisons.Comparison('2', 'P1')], 25)
              ])),
         ("""
 <xtce:StringDataEncoding xmlns:xtce="http://www.omg.org/space/xtce">
@@ -808,7 +809,7 @@ def test_polynomial_calibrator_calibrate(xq, expectation):
     </xtce:SizeInBits>
 </xtce:StringDataEncoding>
 """,
-         xtcedef.ElementNotFoundError())
+         definitions.ElementNotFoundError())
         ]
 )
 def test_string_data_encoding(xml_string: str, expectation):
@@ -817,9 +818,9 @@ def test_string_data_encoding(xml_string: str, expectation):
 
     if isinstance(expectation, Exception):
         with pytest.raises(type(expectation)):
-            xtcedef.StringDataEncoding.from_data_encoding_xml_element(element, TEST_NAMESPACE)
+            encodings.StringDataEncoding.from_data_encoding_xml_element(element, TEST_NAMESPACE)
     else:
-        result = xtcedef.StringDataEncoding.from_data_encoding_xml_element(element, TEST_NAMESPACE)
+        result = encodings.StringDataEncoding.from_data_encoding_xml_element(element, TEST_NAMESPACE)
         assert result == expectation
 
 
@@ -829,11 +830,11 @@ def test_string_data_encoding(xml_string: str, expectation):
         ("""
 <xtce:IntegerDataEncoding xmlns:xtce="http://www.omg.org/space/xtce" sizeInBits="4" encoding="unsigned"/>
 """,
-         xtcedef.IntegerDataEncoding(size_in_bits=4, encoding='unsigned')),
+         encodings.IntegerDataEncoding(size_in_bits=4, encoding='unsigned')),
         ("""
 <xtce:IntegerDataEncoding xmlns:xtce="http://www.omg.org/space/xtce" sizeInBits="4"/>
 """,
-         xtcedef.IntegerDataEncoding(size_in_bits=4, encoding='unsigned')),
+         encodings.IntegerDataEncoding(size_in_bits=4, encoding='unsigned')),
         ("""
 <xtce:IntegerDataEncoding xmlns:xtce="http://www.omg.org/space/xtce" sizeInBits="16" encoding="unsigned">
     <xtce:DefaultCalibrator>
@@ -844,10 +845,10 @@ def test_string_data_encoding(xml_string: str, expectation):
     </xtce:DefaultCalibrator>
 </xtce:IntegerDataEncoding>
 """,
-         xtcedef.IntegerDataEncoding(
+         encodings.IntegerDataEncoding(
              size_in_bits=16, encoding='unsigned',
-             default_calibrator=xtcedef.PolynomialCalibrator([
-                 xtcedef.PolynomialCoefficient(0.012155, 1), xtcedef.PolynomialCoefficient(2.54, 0)
+             default_calibrator=calibrators.PolynomialCalibrator([
+                 calibrators.PolynomialCoefficient(0.012155, 1), calibrators.PolynomialCoefficient(2.54, 0)
              ]))),
         ("""
 <xtce:IntegerDataEncoding xmlns:xtce="http://www.omg.org/space/xtce" sizeInBits="12" encoding="unsigned">
@@ -883,25 +884,25 @@ def test_string_data_encoding(xml_string: str, expectation):
     </xtce:ContextCalibratorList>
     </xtce:IntegerDataEncoding>
 """,
-         xtcedef.IntegerDataEncoding(size_in_bits=12, encoding='unsigned',
+         encodings.IntegerDataEncoding(size_in_bits=12, encoding='unsigned',
                                      default_calibrator=None,
                                      context_calibrators=[
-                                         xtcedef.ContextCalibrator(
-                                             match_criteria=[xtcedef.Comparison(required_value='0', operator=">=",
+                                         calibrators.ContextCalibrator(
+                                             match_criteria=[comparisons.Comparison(required_value='0', operator=">=",
                                                                                 referenced_parameter='MSN__PARAM'),
-                                                             xtcedef.Comparison(required_value='678', operator="<",
+                                                             comparisons.Comparison(required_value='678', operator="<",
                                                                                 referenced_parameter='MSN__PARAM')],
-                                             calibrator=xtcedef.PolynomialCalibrator(
-                                                 coefficients=[xtcedef.PolynomialCoefficient(142.998, 0),
-                                                               xtcedef.PolynomialCoefficient(-0.349712, 1)])),
-                                         xtcedef.ContextCalibrator(
-                                             match_criteria=[xtcedef.Comparison(required_value='678', operator=">=",
+                                             calibrator=calibrators.PolynomialCalibrator(
+                                                 coefficients=[calibrators.PolynomialCoefficient(142.998, 0),
+                                                               calibrators.PolynomialCoefficient(-0.349712, 1)])),
+                                         calibrators.ContextCalibrator(
+                                             match_criteria=[comparisons.Comparison(required_value='678', operator=">=",
                                                                                 referenced_parameter='MSN__PARAM'),
-                                                             xtcedef.Comparison(required_value='4096', operator="<=",
+                                                             comparisons.Comparison(required_value='4096', operator="<=",
                                                                                 referenced_parameter='MSN__PARAM')],
-                                             calibrator=xtcedef.PolynomialCalibrator(
-                                                 coefficients=[xtcedef.PolynomialCoefficient(100.488, 0),
-                                                               xtcedef.PolynomialCoefficient(-0.110197, 1)]))
+                                             calibrator=calibrators.PolynomialCalibrator(
+                                                 coefficients=[calibrators.PolynomialCoefficient(100.488, 0),
+                                                               calibrators.PolynomialCoefficient(-0.110197, 1)]))
                                      ])),
     ]
 )
@@ -911,9 +912,9 @@ def test_integer_data_encoding(xml_string: str, expectation):
 
     if isinstance(expectation, Exception):
         with pytest.raises(type(expectation)):
-            xtcedef.IntegerDataEncoding.from_data_encoding_xml_element(element, TEST_NAMESPACE)
+            encodings.IntegerDataEncoding.from_data_encoding_xml_element(element, TEST_NAMESPACE)
     else:
-        result = xtcedef.IntegerDataEncoding.from_data_encoding_xml_element(element, TEST_NAMESPACE)
+        result = encodings.IntegerDataEncoding.from_data_encoding_xml_element(element, TEST_NAMESPACE)
         assert result == expectation
 
 
@@ -934,10 +935,10 @@ def test_integer_data_encoding(xml_string: str, expectation):
     </xtce:DefaultCalibrator>
 </xtce:FloatDataEncoding>
 """,
-         xtcedef.FloatDataEncoding(
+         encodings.FloatDataEncoding(
              size_in_bits=16, encoding='IEEE-754',
-             default_calibrator=xtcedef.PolynomialCalibrator([
-                 xtcedef.PolynomialCoefficient(0.012155, 1), xtcedef.PolynomialCoefficient(2.54, 0)
+             default_calibrator=calibrators.PolynomialCalibrator([
+                 calibrators.PolynomialCoefficient(0.012155, 1), calibrators.PolynomialCoefficient(2.54, 0)
              ]))),
         ("""
 <xtce:FloatDataEncoding xmlns:xtce="http://www.omg.org/space/xtce" sizeInBits="16">
@@ -979,28 +980,28 @@ def test_integer_data_encoding(xml_string: str, expectation):
     </xtce:DefaultCalibrator>
 </xtce:FloatDataEncoding>
 """,
-         xtcedef.FloatDataEncoding(
+         encodings.FloatDataEncoding(
              size_in_bits=16, encoding='IEEE-754',
-             default_calibrator=xtcedef.PolynomialCalibrator([
-                 xtcedef.PolynomialCoefficient(0.012155, 1), xtcedef.PolynomialCoefficient(2.54, 0)
+             default_calibrator=calibrators.PolynomialCalibrator([
+                 calibrators.PolynomialCoefficient(0.012155, 1), calibrators.PolynomialCoefficient(2.54, 0)
              ]),
              context_calibrators=[
-                 xtcedef.ContextCalibrator(
-                     match_criteria=[xtcedef.Comparison(required_value='0', operator=">=",
+                 calibrators.ContextCalibrator(
+                     match_criteria=[comparisons.Comparison(required_value='0', operator=">=",
                                                         referenced_parameter='MSN__PARAM'),
-                                     xtcedef.Comparison(required_value='678', operator="<",
+                                     comparisons.Comparison(required_value='678', operator="<",
                                                         referenced_parameter='MSN__PARAM')],
-                     calibrator=xtcedef.PolynomialCalibrator(
-                         coefficients=[xtcedef.PolynomialCoefficient(142.998, 0),
-                                       xtcedef.PolynomialCoefficient(-0.349712, 1)])),
-                 xtcedef.ContextCalibrator(
-                     match_criteria=[xtcedef.Comparison(required_value='678', operator=">=",
+                     calibrator=calibrators.PolynomialCalibrator(
+                         coefficients=[calibrators.PolynomialCoefficient(142.998, 0),
+                                       calibrators.PolynomialCoefficient(-0.349712, 1)])),
+                 calibrators.ContextCalibrator(
+                     match_criteria=[comparisons.Comparison(required_value='678', operator=">=",
                                                         referenced_parameter='MSN__PARAM'),
-                                     xtcedef.Comparison(required_value='4096', operator="<=",
+                                     comparisons.Comparison(required_value='4096', operator="<=",
                                                         referenced_parameter='MSN__PARAM')],
-                     calibrator=xtcedef.PolynomialCalibrator(
-                         coefficients=[xtcedef.PolynomialCoefficient(100.488, 0),
-                                       xtcedef.PolynomialCoefficient(-0.110197, 1)]))
+                     calibrator=calibrators.PolynomialCalibrator(
+                         coefficients=[calibrators.PolynomialCoefficient(100.488, 0),
+                                       calibrators.PolynomialCoefficient(-0.110197, 1)]))
              ]
          )),
     ]
@@ -1011,9 +1012,9 @@ def test_float_data_encoding(xml_string: str, expectation):
 
     if isinstance(expectation, Exception):
         with pytest.raises(type(expectation)):
-            xtcedef.FloatDataEncoding.from_data_encoding_xml_element(element, TEST_NAMESPACE)
+            encodings.FloatDataEncoding.from_data_encoding_xml_element(element, TEST_NAMESPACE)
     else:
-        result = xtcedef.FloatDataEncoding.from_data_encoding_xml_element(element, TEST_NAMESPACE)
+        result = encodings.FloatDataEncoding.from_data_encoding_xml_element(element, TEST_NAMESPACE)
         assert result == expectation
 
 
@@ -1027,7 +1028,7 @@ def test_float_data_encoding(xml_string: str, expectation):
     </xtce:SizeInBits>
 </xtce:BinaryDataEncoding>
 """,
-         xtcedef.BinaryDataEncoding(fixed_size_in_bits=256)),
+         encodings.BinaryDataEncoding(fixed_size_in_bits=256)),
         ("""
 <xtce:BinaryDataEncoding xmlns:xtce="http://www.omg.org/space/xtce">
     <xtce:SizeInBits>
@@ -1038,7 +1039,7 @@ def test_float_data_encoding(xml_string: str, expectation):
     </xtce:SizeInBits>
 </xtce:BinaryDataEncoding>
 """,
-         xtcedef.BinaryDataEncoding(
+         encodings.BinaryDataEncoding(
              size_reference_parameter='SizeFromThisParameter',
              linear_adjuster=lambda x: 25 + 8*x)),
         ("""
@@ -1055,9 +1056,9 @@ def test_float_data_encoding(xml_string: str, expectation):
     </xtce:SizeInBits>
 </xtce:BinaryDataEncoding>
 """,
-         xtcedef.BinaryDataEncoding(size_discrete_lookup_list=[
-                 xtcedef.DiscreteLookup([xtcedef.Comparison('1', 'P1')], 10),
-                 xtcedef.DiscreteLookup([xtcedef.Comparison('2', 'P1')], 25)
+         encodings.BinaryDataEncoding(size_discrete_lookup_list=[
+                 comparisons.DiscreteLookup([comparisons.Comparison('1', 'P1')], 10),
+                 comparisons.DiscreteLookup([comparisons.Comparison('2', 'P1')], 25)
              ])),
     ]
 )
@@ -1067,9 +1068,9 @@ def test_binary_data_encoding(xml_string: str, expectation):
 
     if isinstance(expectation, Exception):
         with pytest.raises(type(expectation)):
-            xtcedef.BinaryDataEncoding.from_data_encoding_xml_element(element, TEST_NAMESPACE)
+            encodings.BinaryDataEncoding.from_data_encoding_xml_element(element, TEST_NAMESPACE)
     else:
-        result = xtcedef.BinaryDataEncoding.from_data_encoding_xml_element(element, TEST_NAMESPACE)
+        result = encodings.BinaryDataEncoding.from_data_encoding_xml_element(element, TEST_NAMESPACE)
         assert result == expectation
 
 
@@ -1091,8 +1092,8 @@ def test_binary_data_encoding(xml_string: str, expectation):
     </xtce:StringDataEncoding>
 </xtce:StringParameterType> 
 """,
-         xtcedef.StringParameterType(name='TEST_STRING_Type',
-                                     encoding=xtcedef.StringDataEncoding(fixed_length=40))),
+         parameters.StringParameterType(name='TEST_STRING_Type',
+                                     encoding=encodings.StringDataEncoding(fixed_length=40))),
         ("""
 <xtce:StringParameterType xmlns:xtce="http://www.omg.org/space/xtce" name="TEST_STRING_Type">
     <xtce:StringDataEncoding>
@@ -1102,8 +1103,8 @@ def test_binary_data_encoding(xml_string: str, expectation):
     </xtce:StringDataEncoding>
 </xtce:StringParameterType> 
 """,
-         xtcedef.StringParameterType(name='TEST_STRING_Type',
-                                     encoding=xtcedef.StringDataEncoding(leading_length_size=17))),
+         parameters.StringParameterType(name='TEST_STRING_Type',
+                                     encoding=encodings.StringDataEncoding(leading_length_size=17))),
         ("""
 <xtce:StringParameterType xmlns:xtce="http://www.omg.org/space/xtce" name="TEST_STRING_Type">
     <xtce:StringDataEncoding>
@@ -1113,8 +1114,8 @@ def test_binary_data_encoding(xml_string: str, expectation):
     </xtce:StringDataEncoding>
 </xtce:StringParameterType> 
 """,
-         xtcedef.StringParameterType(name='TEST_STRING_Type',
-                                     encoding=xtcedef.StringDataEncoding(termination_character='00'))),
+         parameters.StringParameterType(name='TEST_STRING_Type',
+                                     encoding=encodings.StringDataEncoding(termination_character='00'))),
     ]
 )
 def test_string_parameter_type(xml_string: str, expectation):
@@ -1123,9 +1124,9 @@ def test_string_parameter_type(xml_string: str, expectation):
 
     if isinstance(expectation, Exception):
         with pytest.raises(type(expectation)):
-            xtcedef.StringParameterType.from_parameter_type_xml_element(element, TEST_NAMESPACE)
+            parameters.StringParameterType.from_parameter_type_xml_element(element, TEST_NAMESPACE)
     else:
-        result = xtcedef.StringParameterType.from_parameter_type_xml_element(element, TEST_NAMESPACE)
+        result = parameters.StringParameterType.from_parameter_type_xml_element(element, TEST_NAMESPACE)
         assert result == expectation
 
 
@@ -1133,89 +1134,89 @@ def test_string_parameter_type(xml_string: str, expectation):
     ('parameter_type', 'packet', 'expected'),
     [
         # Fixed length test
-        (xtcedef.StringParameterType(
+        (parameters.StringParameterType(
             'TEST_STRING',
-            xtcedef.StringDataEncoding(fixed_length=3,  # Giving length in bytes
+            encodings.StringDataEncoding(fixed_length=3,  # Giving length in bytes
                                        length_linear_adjuster=lambda x: 8*x)),
          # This still 123X456
-         xtcedef.Packet(b'123X456'),
+         parseables.Packet(b'123X456'),
          '123'),
         # Dynamic reference length
-        (xtcedef.StringParameterType(
+        (parameters.StringParameterType(
             'TEST_STRING',
-            xtcedef.StringDataEncoding(dynamic_length_reference='STR_LEN',
+            encodings.StringDataEncoding(dynamic_length_reference='STR_LEN',
                                        use_calibrated_value=False,
                                        length_linear_adjuster=lambda x: 8*x)),
-         xtcedef.Packet(b'BAD WOLF', parsed_data={
-             'STR_LEN': parser.ParsedDataItem('STR_LEN', 8, None)}),
+         parseables.Packet(b'BAD WOLF', parsed_data={
+             'STR_LEN': parseables.ParsedDataItem('STR_LEN', 8, None)}),
          'BAD WOLF'),
         # Discrete lookup test
-        (xtcedef.StringParameterType(
+        (parameters.StringParameterType(
             'TEST_STRING',
-            xtcedef.StringDataEncoding(discrete_lookup_length=[
-                xtcedef.DiscreteLookup([
-                    xtcedef.Comparison(7, 'P1', '>'),
-                    xtcedef.Comparison(99, 'P2', '==', use_calibrated_value=False)
+            encodings.StringDataEncoding(discrete_lookup_length=[
+                comparisons.DiscreteLookup([
+                    comparisons.Comparison(7, 'P1', '>'),
+                    comparisons.Comparison(99, 'P2', '==', use_calibrated_value=False)
                 ], lookup_value=8)
             ], length_linear_adjuster=lambda x: 8*x)),
-         xtcedef.Packet(b'BAD WOLF', parsed_data={
-             'P1': parser.ParsedDataItem('P1', 7, None, 7.55), 'P2': parser.ParsedDataItem('P2', 99, None, 100)},),
+         parseables.Packet(b'BAD WOLF', parsed_data={
+             'P1': parseables.ParsedDataItem('P1', 7, None, 7.55), 'P2': parseables.ParsedDataItem('P2', 99, None, 100)},),
          'BAD WOLF'),
         # Termination character tests
-        (xtcedef.StringParameterType(
+        (parameters.StringParameterType(
             'TEST_STRING',
-            xtcedef.StringDataEncoding(encoding='UTF-8',
+            encodings.StringDataEncoding(encoding='UTF-8',
                                        termination_character='58')),
          # 123X456 + extra characters, termination character is X
-         xtcedef.Packet(b'123X456000000000000000000000000000000000000000000000'),
+         parseables.Packet(b'123X456000000000000000000000000000000000000000000000'),
          '123'),
-        (xtcedef.StringParameterType(
+        (parameters.StringParameterType(
             'TEST_STRING',
-            xtcedef.StringDataEncoding(encoding='UTF-8',
+            encodings.StringDataEncoding(encoding='UTF-8',
                                        termination_character='58')),
          # 56bits + 123X456 + extra characters, termination character is X
-         xtcedef.Packet(b'9090909123X456000000000000000000000000000000000000000000000', pos=56),
+         parseables.Packet(b'9090909123X456000000000000000000000000000000000000000000000', pos=56),
          '123'),
-        (xtcedef.StringParameterType(
+        (parameters.StringParameterType(
             'TEST_STRING',
-            xtcedef.StringDataEncoding(encoding='UTF-8',
+            encodings.StringDataEncoding(encoding='UTF-8',
                                        termination_character='58')),
          # 53bits + 123X456 + extra characters, termination character is X
          # This is the same string as above but bit-shifted left by 3 bits
-         xtcedef.Packet(b'\x03K;s{\x93)\x89\x91\x9a\xc1\xa1\xa9\xb3K;s{\x93(', pos=53),
+         parseables.Packet(b'\x03K;s{\x93)\x89\x91\x9a\xc1\xa1\xa9\xb3K;s{\x93(', pos=53),
          '123'),
-        (xtcedef.StringParameterType(
+        (parameters.StringParameterType(
             "TEST_STRING",
-            xtcedef.StringDataEncoding(encoding="UTF-8",
+            encodings.StringDataEncoding(encoding="UTF-8",
                                        termination_character='00')),
-         xtcedef.Packet("false_is_truthy".encode("UTF-8") + b'\x00ABCD'),
+         parseables.Packet("false_is_truthy".encode("UTF-8") + b'\x00ABCD'),
          'false_is_truthy'),
-        (xtcedef.StringParameterType(
+        (parameters.StringParameterType(
             "TEST_STRING",
-            xtcedef.StringDataEncoding(encoding="UTF-16BE",
+            encodings.StringDataEncoding(encoding="UTF-16BE",
                                        termination_character='0021')),
-         xtcedef.Packet("false_is_truthy".encode("UTF-16BE") + b'\x00\x21ignoreme'),
+         parseables.Packet("false_is_truthy".encode("UTF-16BE") + b'\x00\x21ignoreme'),
          'false_is_truthy'),
-        (xtcedef.StringParameterType(
+        (parameters.StringParameterType(
             'TEST_STRING',
-            xtcedef.StringDataEncoding(encoding='UTF-16LE',
+            encodings.StringDataEncoding(encoding='UTF-16LE',
                                        termination_character='5800')),
          # 123X456, termination character is X
-         xtcedef.Packet('123X456'.encode('UTF-16LE')),
+         parseables.Packet('123X456'.encode('UTF-16LE')),
          '123'),
-        (xtcedef.StringParameterType(
+        (parameters.StringParameterType(
             'TEST_STRING',
-            xtcedef.StringDataEncoding(encoding='UTF-16BE',
+            encodings.StringDataEncoding(encoding='UTF-16BE',
                                        termination_character='0058')),
-         xtcedef.Packet('123X456'.encode('UTF-16BE')),
+         parseables.Packet('123X456'.encode('UTF-16BE')),
          '123'),
         # Leading length test
-        (xtcedef.StringParameterType(
+        (parameters.StringParameterType(
             'TEST_STRING',
-            xtcedef.StringDataEncoding(leading_length_size=5)),
+            encodings.StringDataEncoding(leading_length_size=5)),
          # This is still 123X456 but with 11000 prepended (a 5-bit representation of the number 24)
          # This represents a string length (in bits) of 24 bits.
-         xtcedef.Packet(0b1100000110001001100100011001101011000001101000011010100110110000.to_bytes(8, byteorder="big")),
+         parseables.Packet(0b1100000110001001100100011001101011000001101000011010100110110000.to_bytes(8, byteorder="big")),
          '123'),
     ]
 )
@@ -1236,8 +1237,8 @@ def test_string_parameter_parsing(parameter_type, packet, expected):
     <xtce:IntegerDataEncoding sizeInBits="16" encoding="unsigned"/>
 </xtce:IntegerParameterType>
 """,
-         xtcedef.IntegerParameterType(name='TEST_INT_Type', unit='smoot',
-                                      encoding=xtcedef.IntegerDataEncoding(size_in_bits=16, encoding='unsigned'))),
+         parameters.IntegerParameterType(name='TEST_INT_Type', unit='smoot',
+                                      encoding=encodings.IntegerDataEncoding(size_in_bits=16, encoding='unsigned'))),
         ("""
 <xtce:IntegerParameterType xmlns:xtce="http://www.omg.org/space/xtce" name="TEST_INT_Type">
     <xtce:UnitSet>
@@ -1254,13 +1255,13 @@ def test_string_parameter_parsing(parameter_type, packet, expected):
     </xtce:IntegerDataEncoding>
 </xtce:IntegerParameterType>
 """,
-         xtcedef.IntegerParameterType(name='TEST_INT_Type', unit='smoot',
-                                      encoding=xtcedef.IntegerDataEncoding(
+         parameters.IntegerParameterType(name='TEST_INT_Type', unit='smoot',
+                                      encoding=encodings.IntegerDataEncoding(
                                        size_in_bits=16, encoding='unsigned',
-                                       default_calibrator=xtcedef.PolynomialCalibrator([
-                                           xtcedef.PolynomialCoefficient(2772.24, 0),
-                                           xtcedef.PolynomialCoefficient(-41.6338, 1),
-                                           xtcedef.PolynomialCoefficient(-0.185486, 2)
+                                       default_calibrator=calibrators.PolynomialCalibrator([
+                                           calibrators.PolynomialCoefficient(2772.24, 0),
+                                           calibrators.PolynomialCoefficient(-41.6338, 1),
+                                           calibrators.PolynomialCoefficient(-0.185486, 2)
                                        ])
                                    ))),
         ("""
@@ -1279,15 +1280,15 @@ def test_string_parameter_parsing(parameter_type, packet, expected):
     </xtce:IntegerDataEncoding>
 </xtce:IntegerParameterType>
 """,
-         xtcedef.IntegerParameterType(name='TEST_INT_Type', unit='smoot',
-                                      encoding=xtcedef.IntegerDataEncoding(
+         parameters.IntegerParameterType(name='TEST_INT_Type', unit='smoot',
+                                      encoding=encodings.IntegerDataEncoding(
                                        size_in_bits=16, encoding='unsigned',
-                                       default_calibrator=xtcedef.SplineCalibrator(
+                                       default_calibrator=calibrators.SplineCalibrator(
                                            order=0, extrapolate=False,
                                            points=[
-                                               xtcedef.SplinePoint(raw=1, calibrated=10),
-                                               xtcedef.SplinePoint(raw=2, calibrated=100),
-                                               xtcedef.SplinePoint(raw=3, calibrated=500),
+                                               calibrators.SplinePoint(raw=1, calibrated=10),
+                                               calibrators.SplinePoint(raw=2, calibrated=100),
+                                               calibrators.SplinePoint(raw=3, calibrated=500),
                                            ]
                                        )))),
     ]
@@ -1298,9 +1299,9 @@ def test_integer_parameter_type(xml_string: str, expectation):
 
     if isinstance(expectation, Exception):
         with pytest.raises(type(expectation)):
-            xtcedef.IntegerParameterType.from_parameter_type_xml_element(element, TEST_NAMESPACE)
+            parameters.IntegerParameterType.from_parameter_type_xml_element(element, TEST_NAMESPACE)
     else:
-        result = xtcedef.IntegerParameterType.from_parameter_type_xml_element(element, TEST_NAMESPACE)
+        result = parameters.IntegerParameterType.from_parameter_type_xml_element(element, TEST_NAMESPACE)
         assert result == expectation
 
 
@@ -1308,87 +1309,87 @@ def test_integer_parameter_type(xml_string: str, expectation):
     ('parameter_type', 'packet', 'expected'),
     [
         # 16-bit unsigned starting at byte boundary
-        (xtcedef.IntegerParameterType('TEST_INT', xtcedef.IntegerDataEncoding(16, 'unsigned')),
-         xtcedef.Packet(0b1000000000000000.to_bytes(length=2, byteorder='big')),
+        (parameters.IntegerParameterType('TEST_INT', encodings.IntegerDataEncoding(16, 'unsigned')),
+         parseables.Packet(0b1000000000000000.to_bytes(length=2, byteorder='big')),
          32768),
         # 16-bit unsigned little endian at byte boundary
-        (xtcedef.IntegerParameterType(
+        (parameters.IntegerParameterType(
             'TEST_INT',
-            xtcedef.IntegerDataEncoding(16, 'unsigned', byte_order="leastSignificantByteFirst")),
-         xtcedef.Packet(0b1000000000000000.to_bytes(length=2, byteorder='big')),
+            encodings.IntegerDataEncoding(16, 'unsigned', byte_order="leastSignificantByteFirst")),
+         parseables.Packet(0b1000000000000000.to_bytes(length=2, byteorder='big')),
          128),
         # 16-bit signed starting at byte boundary
-        (xtcedef.IntegerParameterType('TEST_INT', xtcedef.IntegerDataEncoding(16, 'signed')),
-         xtcedef.Packet(0b1111111111010110.to_bytes(length=2, byteorder='big')),
+        (parameters.IntegerParameterType('TEST_INT', encodings.IntegerDataEncoding(16, 'signed')),
+         parseables.Packet(0b1111111111010110.to_bytes(length=2, byteorder='big')),
          -42),
         # 16-bit signed little endian starting at byte boundary
-        (xtcedef.IntegerParameterType(
+        (parameters.IntegerParameterType(
             'TEST_INT',
-            xtcedef.IntegerDataEncoding(16, 'signed', byte_order="leastSignificantByteFirst")),
-         xtcedef.Packet(0b1101011011111111.to_bytes(length=2, byteorder='big')),
+            encodings.IntegerDataEncoding(16, 'signed', byte_order="leastSignificantByteFirst")),
+         parseables.Packet(0b1101011011111111.to_bytes(length=2, byteorder='big')),
          -42),
         # 16-bit signed integer starting at a byte boundary,
         # calibrated by a polynomial y = (x*2 + 5); x = -42; y = -84 + 5 = -79
-        (xtcedef.IntegerParameterType(
+        (parameters.IntegerParameterType(
             'TEST_INT',
-            xtcedef.IntegerDataEncoding(
+            encodings.IntegerDataEncoding(
                 16, 'signed',
                 context_calibrators=[
-                    xtcedef.ContextCalibrator([
-                        xtcedef.Condition(left_param='PKT_APID', operator='==',
+                    calibrators.ContextCalibrator([
+                        comparisons.Condition(left_param='PKT_APID', operator='==',
                                           right_value=1101, left_use_calibrated_value=False,
                                           right_use_calibrated_value=False)],
-                        xtcedef.PolynomialCalibrator([xtcedef.PolynomialCoefficient(5, 0),
-                                                      xtcedef.PolynomialCoefficient(2, 1)]))
+                        calibrators.PolynomialCalibrator([calibrators.PolynomialCoefficient(5, 0),
+                                                      calibrators.PolynomialCoefficient(2, 1)]))
                 ])),
-         xtcedef.Packet(0b1111111111010110.to_bytes(length=2, byteorder='big'),
-                            parsed_data={'PKT_APID': parser.ParsedDataItem('PKT_APID', 1101)},),
+         parseables.Packet(0b1111111111010110.to_bytes(length=2, byteorder='big'),
+                            parsed_data={'PKT_APID': parseables.ParsedDataItem('PKT_APID', 1101)},),
          -79),
         # 12-bit unsigned integer starting at bit 4 of the first byte
-        (xtcedef.IntegerParameterType('TEST_INT', xtcedef.IntegerDataEncoding(12, 'unsigned')),
+        (parameters.IntegerParameterType('TEST_INT', encodings.IntegerDataEncoding(12, 'unsigned')),
          # 11111000 00000000
          #     |--uint:12--|
-         xtcedef.Packet(0b1111100000000000.to_bytes(length=2, byteorder='big'), pos=4),
+         parseables.Packet(0b1111100000000000.to_bytes(length=2, byteorder='big'), pos=4),
          2048),
         # 13-bit unsigned integer starting on bit 2 of the second byte
-        (xtcedef.IntegerParameterType('TEST_INT', xtcedef.IntegerDataEncoding(13, 'unsigned')),
+        (parameters.IntegerParameterType('TEST_INT', encodings.IntegerDataEncoding(13, 'unsigned')),
          # 10101010 11100000 00000001
          #            |--uint:13---|
-         xtcedef.Packet(0b101010101110000000000001.to_bytes(length=3, byteorder='big'), pos=10),
+         parseables.Packet(0b101010101110000000000001.to_bytes(length=3, byteorder='big'), pos=10),
          4096),
         # 16-bit unsigned integer starting on bit 2 of the first byte
-        (xtcedef.IntegerParameterType('TEST_INT', xtcedef.IntegerDataEncoding(16, 'unsigned')),
+        (parameters.IntegerParameterType('TEST_INT', encodings.IntegerDataEncoding(16, 'unsigned')),
          # 10101010 11100000 00000001
          #   |----uint:16-----|
-         xtcedef.Packet(0b101010101110000000000001.to_bytes(length=3, byteorder='big'), pos=2),
+         parseables.Packet(0b101010101110000000000001.to_bytes(length=3, byteorder='big'), pos=2),
          43904),
         # 12-bit signed integer starting on bit 4 of the first byte
-        (xtcedef.IntegerParameterType('TEST_INT', xtcedef.IntegerDataEncoding(12, 'signed')),
+        (parameters.IntegerParameterType('TEST_INT', encodings.IntegerDataEncoding(12, 'signed')),
          # 11111000 00000000
          #     |---int:12--|
-         xtcedef.Packet(0b1111100000000000.to_bytes(length=2, byteorder='big'), pos=4),
+         parseables.Packet(0b1111100000000000.to_bytes(length=2, byteorder='big'), pos=4),
          -2048),
         # 12-bit signed integer starting on bit 6 of the first byte
-        (xtcedef.IntegerParameterType('TEST_INT', xtcedef.IntegerDataEncoding(12, 'signed')),
+        (parameters.IntegerParameterType('TEST_INT', encodings.IntegerDataEncoding(12, 'signed')),
          # 12-bit signed integer starting on bit 4 of the first byte
          #  11111110 00000000 00111111 10101010
          #        |---int:12---|
-         xtcedef.Packet(0b11111110000000000011111110101010.to_bytes(length=4, byteorder='big'), pos=6),
+         parseables.Packet(0b11111110000000000011111110101010.to_bytes(length=4, byteorder='big'), pos=6),
          -2048),
         # 12-bit signed little endian integer starting on bit 6 of the first byte
-        (xtcedef.IntegerParameterType(
+        (parameters.IntegerParameterType(
             'TEST_INT',
-            xtcedef.IntegerDataEncoding(12, 'signed', byte_order='leastSignificantByteFirst')),
+            encodings.IntegerDataEncoding(12, 'signed', byte_order='leastSignificantByteFirst')),
          # 12-bit signed little endian integer starting on bit 4 of the first byte. The LSB of the integer comes first
          #  11111100 00000010 00111111 10101010
          #        |---int:12---|
-         xtcedef.Packet(0b11111100000000100011111110101010.to_bytes(length=4, byteorder='big'), pos=6),
+         parseables.Packet(0b11111100000000100011111110101010.to_bytes(length=4, byteorder='big'), pos=6),
          -2048),
-        (xtcedef.IntegerParameterType('TEST_INT', xtcedef.IntegerDataEncoding(3, 'twosComplement')),
+        (parameters.IntegerParameterType('TEST_INT', encodings.IntegerDataEncoding(3, 'twosComplement')),
          # 3-bit signed integer starting at bit 7 of the first byte
          #  00000001      11000000       00000000
          #         |-int:3-|
-         xtcedef.Packet(0b000000011100000000000000.to_bytes(length=3, byteorder='big'), pos=7),
+         parseables.Packet(0b000000011100000000000000.to_bytes(length=3, byteorder='big'), pos=7),
          -1),
     ]
 )
@@ -1412,8 +1413,8 @@ def test_integer_parameter_parsing(parameter_type, packet, expected):
     <xtce:FloatDataEncoding sizeInBits="16"/>
 </xtce:FloatParameterType>
 """,
-         xtcedef.FloatParameterType(name='TEST_INT_Type', unit='smoot',
-                                    encoding=xtcedef.FloatDataEncoding(size_in_bits=16, encoding='IEEE-754'))),
+         parameters.FloatParameterType(name='TEST_INT_Type', unit='smoot',
+                                    encoding=encodings.FloatDataEncoding(size_in_bits=16, encoding='IEEE-754'))),
         ("""
 <xtce:FloatParameterType xmlns:xtce="http://www.omg.org/space/xtce" name="TEST_INT_Type">
     <xtce:UnitSet>
@@ -1422,8 +1423,8 @@ def test_integer_parameter_parsing(parameter_type, packet, expected):
     <xtce:IntegerDataEncoding sizeInBits="16" encoding="unsigned"/>
 </xtce:FloatParameterType>
 """,
-         xtcedef.FloatParameterType(name='TEST_INT_Type', unit='smoot',
-                                    encoding=xtcedef.IntegerDataEncoding(size_in_bits=16, encoding='unsigned'))),
+         parameters.FloatParameterType(name='TEST_INT_Type', unit='smoot',
+                                    encoding=encodings.IntegerDataEncoding(size_in_bits=16, encoding='unsigned'))),
         ("""
 <xtce:FloatParameterType xmlns:xtce="http://www.omg.org/space/xtce" name="TEST_INT_Type">
     <xtce:UnitSet>
@@ -1440,13 +1441,13 @@ def test_integer_parameter_parsing(parameter_type, packet, expected):
     </xtce:IntegerDataEncoding>
 </xtce:FloatParameterType>
 """,
-         xtcedef.FloatParameterType(name='TEST_INT_Type', unit='smoot',
-                                    encoding=xtcedef.IntegerDataEncoding(
+         parameters.FloatParameterType(name='TEST_INT_Type', unit='smoot',
+                                    encoding=encodings.IntegerDataEncoding(
                                      size_in_bits=16, encoding='unsigned',
-                                     default_calibrator=xtcedef.PolynomialCalibrator([
-                                         xtcedef.PolynomialCoefficient(2772.24, 0),
-                                         xtcedef.PolynomialCoefficient(-41.6338, 1),
-                                         xtcedef.PolynomialCoefficient(-0.185486, 2)
+                                     default_calibrator=calibrators.PolynomialCalibrator([
+                                         calibrators.PolynomialCoefficient(2772.24, 0),
+                                         calibrators.PolynomialCoefficient(-41.6338, 1),
+                                         calibrators.PolynomialCoefficient(-0.185486, 2)
                                      ])
                                     ))),
         ("""
@@ -1465,15 +1466,15 @@ def test_integer_parameter_parsing(parameter_type, packet, expected):
     </xtce:IntegerDataEncoding>
 </xtce:FloatParameterType>
 """,
-         xtcedef.FloatParameterType(name='TEST_INT_Type', unit='smoot',
-                                    encoding=xtcedef.IntegerDataEncoding(
+         parameters.FloatParameterType(name='TEST_INT_Type', unit='smoot',
+                                    encoding=encodings.IntegerDataEncoding(
                                      size_in_bits=16, encoding='unsigned',
-                                     default_calibrator=xtcedef.SplineCalibrator(
+                                     default_calibrator=calibrators.SplineCalibrator(
                                          order=0, extrapolate=False,
                                          points=[
-                                             xtcedef.SplinePoint(raw=1, calibrated=10.),
-                                             xtcedef.SplinePoint(raw=2, calibrated=100.),
-                                             xtcedef.SplinePoint(raw=3, calibrated=500.),
+                                             calibrators.SplinePoint(raw=1, calibrated=10.),
+                                             calibrators.SplinePoint(raw=2, calibrated=100.),
+                                             calibrators.SplinePoint(raw=3, calibrated=500.),
                                          ]
                                      )))),
     ]
@@ -1484,9 +1485,9 @@ def test_float_parameter_type(xml_string: str, expectation):
 
     if isinstance(expectation, Exception):
         with pytest.raises(type(expectation)):
-            xtcedef.FloatParameterType.from_parameter_type_xml_element(element, TEST_NAMESPACE)
+            parameters.FloatParameterType.from_parameter_type_xml_element(element, TEST_NAMESPACE)
     else:
-        result = xtcedef.FloatParameterType.from_parameter_type_xml_element(element, TEST_NAMESPACE)
+        result = parameters.FloatParameterType.from_parameter_type_xml_element(element, TEST_NAMESPACE)
         assert result == expectation
 
 
@@ -1494,97 +1495,97 @@ def test_float_parameter_type(xml_string: str, expectation):
     ('parameter_type', 'packet', 'expected'),
     [
         # Test big endion 32-bit IEEE float
-        (xtcedef.FloatParameterType('TEST_FLOAT', xtcedef.FloatDataEncoding(32)),
-         xtcedef.Packet(0b01000000010010010000111111010000.to_bytes(length=4, byteorder='big')),
+        (parameters.FloatParameterType('TEST_FLOAT', encodings.FloatDataEncoding(32)),
+         parseables.Packet(0b01000000010010010000111111010000.to_bytes(length=4, byteorder='big')),
          3.14159),
         # Test little endian 32-bit IEEE float
-        (xtcedef.FloatParameterType(
+        (parameters.FloatParameterType(
             'TEST_FLOAT',
-            xtcedef.FloatDataEncoding(32, byte_order='leastSignificantByteFirst')),
-         xtcedef.Packet(0b01000000010010010000111111010000.to_bytes(length=4, byteorder='little')),
+            encodings.FloatDataEncoding(32, byte_order='leastSignificantByteFirst')),
+         parseables.Packet(0b01000000010010010000111111010000.to_bytes(length=4, byteorder='little')),
          3.14159),
         # Test big endian 64-bit float
-        (xtcedef.FloatParameterType('TEST_FLOAT', xtcedef.FloatDataEncoding(64)),
-         xtcedef.Packet(b'\x3F\xF9\xE3\x77\x9B\x97\xF4\xA8'),  # 64-bit IEEE 754 value of Phi
+        (parameters.FloatParameterType('TEST_FLOAT', encodings.FloatDataEncoding(64)),
+         parseables.Packet(b'\x3F\xF9\xE3\x77\x9B\x97\xF4\xA8'),  # 64-bit IEEE 754 value of Phi
          1.6180339),
         # Test float parameter type encoded as big endian 16-bit integer with contextual polynomial calibrator
-        (xtcedef.FloatParameterType(
+        (parameters.FloatParameterType(
             'TEST_FLOAT',
-            xtcedef.IntegerDataEncoding(
+            encodings.IntegerDataEncoding(
                 16, 'signed',
                 context_calibrators=[
-                    xtcedef.ContextCalibrator([
-                        xtcedef.Condition(left_param='PKT_APID', operator='==',
+                    calibrators.ContextCalibrator([
+                        comparisons.Condition(left_param='PKT_APID', operator='==',
                                           right_value=1101, left_use_calibrated_value=False,
                                           right_use_calibrated_value=False)],
-                        xtcedef.PolynomialCalibrator([xtcedef.PolynomialCoefficient(5.6, 0),
-                                                      xtcedef.PolynomialCoefficient(2.1, 1)]))
+                        calibrators.PolynomialCalibrator([calibrators.PolynomialCoefficient(5.6, 0),
+                                                      calibrators.PolynomialCoefficient(2.1, 1)]))
                 ])),
-         xtcedef.Packet(0b1111111111010110.to_bytes(length=2, byteorder='big'),
-                            parsed_data={'PKT_APID': parser.ParsedDataItem('PKT_APID', 1101)}),
+         parseables.Packet(0b1111111111010110.to_bytes(length=2, byteorder='big'),
+                            parsed_data={'PKT_APID': parseables.ParsedDataItem('PKT_APID', 1101)}),
          -82.600000),
         # Test MIL 1750A encoded floats.
         # Test values taken from: https://www.xgc-tek.com/manuals/mil-std-1750a/c191.html#AEN324
-        (xtcedef.FloatParameterType(
+        (parameters.FloatParameterType(
             'MIL_1750A_FLOAT',
-            xtcedef.FloatDataEncoding(32, encoding="MIL-1750A")),
-         xtcedef.Packet(b'\x7f\xff\xff\x7f'),
+            encodings.FloatDataEncoding(32, encoding="MIL-1750A")),
+         parseables.Packet(b'\x7f\xff\xff\x7f'),
          0.9999998 * (2 ** 127)),
-        (xtcedef.FloatParameterType(
+        (parameters.FloatParameterType(
             'MIL_1750A_FLOAT',
-            xtcedef.FloatDataEncoding(32, encoding="MIL-1750A")),
-         xtcedef.Packet(b'\x40\x00\x00\x7f'),
+            encodings.FloatDataEncoding(32, encoding="MIL-1750A")),
+         parseables.Packet(b'\x40\x00\x00\x7f'),
          0.5 * (2 ** 127)),
-        (xtcedef.FloatParameterType(
+        (parameters.FloatParameterType(
             'MIL_1750A_FLOAT',
-            xtcedef.FloatDataEncoding(32, encoding="MIL-1750A")),
-         xtcedef.Packet(b'\x50\x00\x00\x04'),
+            encodings.FloatDataEncoding(32, encoding="MIL-1750A")),
+         parseables.Packet(b'\x50\x00\x00\x04'),
          0.625 * (2 ** 4)),
-        (xtcedef.FloatParameterType(
+        (parameters.FloatParameterType(
             'MIL_1750A_FLOAT',
-            xtcedef.FloatDataEncoding(32, encoding="MIL-1750A")),
-         xtcedef.Packet(b'\x40\x00\x00\x01'),
+            encodings.FloatDataEncoding(32, encoding="MIL-1750A")),
+         parseables.Packet(b'\x40\x00\x00\x01'),
          0.5 * (2 ** 1)),
-        (xtcedef.FloatParameterType(
+        (parameters.FloatParameterType(
             'MIL_1750A_FLOAT',
-            xtcedef.FloatDataEncoding(32, encoding="MIL-1750A")),
-         xtcedef.Packet(b'\x40\x00\x00\x00'),
+            encodings.FloatDataEncoding(32, encoding="MIL-1750A")),
+         parseables.Packet(b'\x40\x00\x00\x00'),
          0.5 * (2 ** 0)),
-        (xtcedef.FloatParameterType(
+        (parameters.FloatParameterType(
             'MIL_1750A_FLOAT',
-            xtcedef.FloatDataEncoding(32, encoding="MIL-1750A")),
-         xtcedef.Packet(b'\x40\x00\x00\xff'),
+            encodings.FloatDataEncoding(32, encoding="MIL-1750A")),
+         parseables.Packet(b'\x40\x00\x00\xff'),
          0.5 * (2 ** -1)),
-        (xtcedef.FloatParameterType(
+        (parameters.FloatParameterType(
             'MIL_1750A_FLOAT',
-            xtcedef.FloatDataEncoding(32, encoding="MIL-1750A")),
-         xtcedef.Packet(b'\x40\x00\x00\x80'),
+            encodings.FloatDataEncoding(32, encoding="MIL-1750A")),
+         parseables.Packet(b'\x40\x00\x00\x80'),
          0.5 * (2 ** -128)),
-        (xtcedef.FloatParameterType(
+        (parameters.FloatParameterType(
             'MIL_1750A_FLOAT',
-            xtcedef.FloatDataEncoding(32, encoding="MIL-1750A")),
-         xtcedef.Packet(b'\x00\x00\x00\x00'),
+            encodings.FloatDataEncoding(32, encoding="MIL-1750A")),
+         parseables.Packet(b'\x00\x00\x00\x00'),
          0.0 * (2 ** 0)),
-        (xtcedef.FloatParameterType(
+        (parameters.FloatParameterType(
             'MIL_1750A_FLOAT',
-            xtcedef.FloatDataEncoding(32, encoding="MIL-1750A")),
-         xtcedef.Packet(b'\x80\x00\x00\x00'),
+            encodings.FloatDataEncoding(32, encoding="MIL-1750A")),
+         parseables.Packet(b'\x80\x00\x00\x00'),
          -1.0 * (2 ** 0)),
-        (xtcedef.FloatParameterType(
+        (parameters.FloatParameterType(
             'MIL_1750A_FLOAT',
-            xtcedef.FloatDataEncoding(32, encoding="MIL-1750A")),
-         xtcedef.Packet(b'\xBF\xFF\xFF\x80'),
+            encodings.FloatDataEncoding(32, encoding="MIL-1750A")),
+         parseables.Packet(b'\xBF\xFF\xFF\x80'),
          -0.5000001 * (2 ** -128)),
-        (xtcedef.FloatParameterType(
+        (parameters.FloatParameterType(
             'MIL_1750A_FLOAT',
-            xtcedef.FloatDataEncoding(32, encoding="MIL-1750A")),
-         xtcedef.Packet(b'\x9F\xFF\xFF\x04'),
+            encodings.FloatDataEncoding(32, encoding="MIL-1750A")),
+         parseables.Packet(b'\x9F\xFF\xFF\x04'),
          -0.7500001 * (2 ** 4)),
         # Little endian version of previous test
-        (xtcedef.FloatParameterType(
+        (parameters.FloatParameterType(
             'MIL_1750A_FLOAT',
-            xtcedef.FloatDataEncoding(32, encoding="MIL-1750A", byte_order="leastSignificantByteFirst")),
-         xtcedef.Packet(b'\x04\xFF\xFF\x9F'),
+            encodings.FloatDataEncoding(32, encoding="MIL-1750A", byte_order="leastSignificantByteFirst")),
+         parseables.Packet(b'\x04\xFF\xFF\x9F'),
          -0.7500001 * (2 ** 4)),
     ]
 )
@@ -1614,8 +1615,8 @@ def test_float_parameter_parsing(parameter_type, packet, expected):
     </xtce:EnumerationList>
 </xtce:EnumeratedParameterType>
 """,
-         xtcedef.EnumeratedParameterType(name='TEST_ENUM_Type',
-                                         encoding=xtcedef.IntegerDataEncoding(size_in_bits=2, encoding='unsigned'),
+         parameters.EnumeratedParameterType(name='TEST_ENUM_Type',
+                                         encoding=encodings.IntegerDataEncoding(size_in_bits=2, encoding='unsigned'),
                                          # NOTE: Duplicate final value is on purpose to make sure we handle that case
                                          enumeration={0: 'BOOT_POR', 1: 'BOOT_RETURN', 2: 'OP_LOW', 3: 'OP_HIGH', 4: 'OP_HIGH'})),
     ]
@@ -1626,24 +1627,24 @@ def test_enumerated_parameter_type(xml_string: str, expectation):
 
     if isinstance(expectation, Exception):
         with pytest.raises(type(expectation)):
-            xtcedef.EnumeratedParameterType.from_parameter_type_xml_element(element, TEST_NAMESPACE)
+            parameters.EnumeratedParameterType.from_parameter_type_xml_element(element, TEST_NAMESPACE)
     else:
-        result = xtcedef.EnumeratedParameterType.from_parameter_type_xml_element(element, TEST_NAMESPACE)
+        result = parameters.EnumeratedParameterType.from_parameter_type_xml_element(element, TEST_NAMESPACE)
         assert result == expectation
 
 
 @pytest.mark.parametrize(
     ('parameter_type', 'packet', 'expected'),
     [
-        (xtcedef.EnumeratedParameterType(
+        (parameters.EnumeratedParameterType(
             'TEST_ENUM',
-            xtcedef.IntegerDataEncoding(16, 'unsigned'), {32768: 'NOMINAL'}),
-         xtcedef.Packet(0b1000000000000000.to_bytes(length=2, byteorder='big')),
+            encodings.IntegerDataEncoding(16, 'unsigned'), {32768: 'NOMINAL'}),
+         parseables.Packet(0b1000000000000000.to_bytes(length=2, byteorder='big')),
          'NOMINAL'),
-        (xtcedef.EnumeratedParameterType(
+        (parameters.EnumeratedParameterType(
             'TEST_FLOAT',
-            xtcedef.IntegerDataEncoding(16, 'signed'),  {-42: 'VAL_LOW'}),
-         xtcedef.Packet(0b1111111111010110.to_bytes(length=2, byteorder='big')),
+            encodings.IntegerDataEncoding(16, 'signed'),  {-42: 'VAL_LOW'}),
+         parseables.Packet(0b1111111111010110.to_bytes(length=2, byteorder='big')),
          'VAL_LOW'),
     ]
 )
@@ -1671,8 +1672,8 @@ def test_enumerated_parameter_parsing(parameter_type, packet, expected):
     </xtce:BinaryDataEncoding>
 </xtce:BinaryParameterType>
 """,
-         xtcedef.BinaryParameterType(name='TEST_PARAM_Type', unit='smoot',
-                                     encoding=xtcedef.BinaryDataEncoding(fixed_size_in_bits=256))),
+         parameters.BinaryParameterType(name='TEST_PARAM_Type', unit='smoot',
+                                     encoding=encodings.BinaryDataEncoding(fixed_size_in_bits=256))),
         ("""
 <xtce:BinaryParameterType xmlns:xtce="http://www.omg.org/space/xtce" name="TEST_PARAM_Type">
     <xtce:UnitSet/>
@@ -1683,8 +1684,8 @@ def test_enumerated_parameter_parsing(parameter_type, packet, expected):
     </xtce:BinaryDataEncoding>
 </xtce:BinaryParameterType>
 """,
-         xtcedef.BinaryParameterType(name='TEST_PARAM_Type', unit=None,
-                                     encoding=xtcedef.BinaryDataEncoding(fixed_size_in_bits=128))),
+         parameters.BinaryParameterType(name='TEST_PARAM_Type', unit=None,
+                                     encoding=encodings.BinaryDataEncoding(fixed_size_in_bits=128))),
         ("""
 <xtce:BinaryParameterType xmlns:xtce="http://www.omg.org/space/xtce" name="TEST_PARAM_Type">
     <xtce:UnitSet/>
@@ -1698,8 +1699,8 @@ def test_enumerated_parameter_parsing(parameter_type, packet, expected):
     </xtce:BinaryDataEncoding>
 </xtce:BinaryParameterType>
 """,
-         xtcedef.BinaryParameterType(name='TEST_PARAM_Type',
-                                     encoding=xtcedef.BinaryDataEncoding(
+         parameters.BinaryParameterType(name='TEST_PARAM_Type',
+                                     encoding=encodings.BinaryDataEncoding(
                                          size_reference_parameter='SizeFromThisParameter',
                                          use_calibrated_value=False,
                                          linear_adjuster=lambda x: x))),
@@ -1715,8 +1716,8 @@ def test_enumerated_parameter_parsing(parameter_type, packet, expected):
     </xtce:BinaryDataEncoding>
 </xtce:BinaryParameterType>
 """,
-         xtcedef.BinaryParameterType(name='TEST_PARAM_Type', unit=None,
-                                     encoding=xtcedef.BinaryDataEncoding(
+         parameters.BinaryParameterType(name='TEST_PARAM_Type', unit=None,
+                                     encoding=encodings.BinaryDataEncoding(
                                          size_reference_parameter='SizeFromThisParameter'))),
     ]
 )
@@ -1726,9 +1727,9 @@ def test_binary_parameter_type(xml_string: str, expectation):
 
     if isinstance(expectation, Exception):
         with pytest.raises(type(expectation)):
-            xtcedef.BinaryParameterType.from_parameter_type_xml_element(element, TEST_NAMESPACE)
+            parameters.BinaryParameterType.from_parameter_type_xml_element(element, TEST_NAMESPACE)
     else:
-        result = xtcedef.BinaryParameterType.from_parameter_type_xml_element(element, TEST_NAMESPACE)
+        result = parameters.BinaryParameterType.from_parameter_type_xml_element(element, TEST_NAMESPACE)
         assert result == expectation
 
 
@@ -1736,30 +1737,30 @@ def test_binary_parameter_type(xml_string: str, expectation):
     ('parameter_type', 'packet', 'expected'),
     [
         # fixed size
-        (xtcedef.BinaryParameterType(
+        (parameters.BinaryParameterType(
             'TEST_BIN',
-            xtcedef.BinaryDataEncoding(fixed_size_in_bits=16)),
-         xtcedef.Packet(0b0011010000110010010100110000000001001011000000000100100100000000.to_bytes(length=8, byteorder='big')),
+            encodings.BinaryDataEncoding(fixed_size_in_bits=16)),
+         parseables.Packet(0b0011010000110010010100110000000001001011000000000100100100000000.to_bytes(length=8, byteorder='big')),
          b'42'),
         # discrete lookup list size
-        (xtcedef.BinaryParameterType(
+        (parameters.BinaryParameterType(
             'TEST_BIN',
-            xtcedef.BinaryDataEncoding(size_discrete_lookup_list=[
-                xtcedef.DiscreteLookup([
-                    xtcedef.Comparison(required_value=7.4, referenced_parameter='P1',
+            encodings.BinaryDataEncoding(size_discrete_lookup_list=[
+                comparisons.DiscreteLookup([
+                    comparisons.Comparison(required_value=7.4, referenced_parameter='P1',
                                        operator='==', use_calibrated_value=True)
                 ], lookup_value=2)
             ], linear_adjuster=lambda x: 8*x)),
-         xtcedef.Packet(0b0011010000110010010100110000000001001011000000000100100100000000.to_bytes(length=8, byteorder='big'),
-                            parsed_data={'P1': parser.ParsedDataItem('P1', 1, None, 7.4)}),
+         parseables.Packet(0b0011010000110010010100110000000001001011000000000100100100000000.to_bytes(length=8, byteorder='big'),
+                            parsed_data={'P1': parseables.ParsedDataItem('P1', 1, None, 7.4)}),
          b'42'),
         # dynamic size reference to other parameter
-        (xtcedef.BinaryParameterType(
+        (parameters.BinaryParameterType(
             'TEST_BIN',
-            xtcedef.BinaryDataEncoding(size_reference_parameter='BIN_LEN',
+            encodings.BinaryDataEncoding(size_reference_parameter='BIN_LEN',
                                        use_calibrated_value=False, linear_adjuster=lambda x: 8*x)),
-         xtcedef.Packet(0b0011010000110010010100110000000001001011000000000100100100000000.to_bytes(length=8, byteorder='big'),
-                            parsed_data={'BIN_LEN': parser.ParsedDataItem('BIN_LEN', 2, None)}),
+         parseables.Packet(0b0011010000110010010100110000000001001011000000000100100100000000.to_bytes(length=8, byteorder='big'),
+                            parsed_data={'BIN_LEN': parseables.ParsedDataItem('BIN_LEN', 2, None)}),
          b'42'),
     ]
 )
@@ -1784,8 +1785,8 @@ def test_binary_parameter_parsing(parameter_type, packet, expected):
     </xtce:BinaryDataEncoding>
 </xtce:BooleanParameterType>
 """,
-         xtcedef.BooleanParameterType(name='TEST_PARAM_Type', unit='smoot',
-                                      encoding=xtcedef.BinaryDataEncoding(fixed_size_in_bits=1))),
+         parameters.BooleanParameterType(name='TEST_PARAM_Type', unit='smoot',
+                                      encoding=encodings.BinaryDataEncoding(fixed_size_in_bits=1))),
         ("""
 <xtce:BooleanParameterType xmlns:xtce="http://www.omg.org/space/xtce" name="TEST_PARAM_Type">
     <xtce:UnitSet>
@@ -1794,8 +1795,8 @@ def test_binary_parameter_parsing(parameter_type, packet, expected):
     <xtce:IntegerDataEncoding encoding="unsigned" sizeInBits="1"/>
 </xtce:BooleanParameterType>
 """,
-         xtcedef.BooleanParameterType(name='TEST_PARAM_Type', unit='smoot',
-                                      encoding=xtcedef.IntegerDataEncoding(size_in_bits=1, encoding="unsigned"))),
+         parameters.BooleanParameterType(name='TEST_PARAM_Type', unit='smoot',
+                                      encoding=encodings.IntegerDataEncoding(size_in_bits=1, encoding="unsigned"))),
         ("""
 <xtce:BooleanParameterType xmlns:xtce="http://www.omg.org/space/xtce" name="TEST_PARAM_Type">
     <xtce:UnitSet>
@@ -1808,8 +1809,8 @@ def test_binary_parameter_parsing(parameter_type, packet, expected):
     </xtce:StringDataEncoding>
 </xtce:BooleanParameterType>
 """,
-         xtcedef.BooleanParameterType(name='TEST_PARAM_Type', unit='smoot',
-                                      encoding=xtcedef.StringDataEncoding(termination_character='00'))),
+         parameters.BooleanParameterType(name='TEST_PARAM_Type', unit='smoot',
+                                      encoding=encodings.StringDataEncoding(termination_character='00'))),
     ]
 )
 def test_boolean_parameter_type(xml_string, expectation):
@@ -1818,44 +1819,44 @@ def test_boolean_parameter_type(xml_string, expectation):
 
     if isinstance(expectation, Exception):
         with pytest.raises(type(expectation)):
-            xtcedef.BooleanParameterType.from_parameter_type_xml_element(element, TEST_NAMESPACE)
+            parameters.BooleanParameterType.from_parameter_type_xml_element(element, TEST_NAMESPACE)
     else:
-        result = xtcedef.BooleanParameterType.from_parameter_type_xml_element(element, TEST_NAMESPACE)
+        result = parameters.BooleanParameterType.from_parameter_type_xml_element(element, TEST_NAMESPACE)
         assert result == expectation
 
 
 @pytest.mark.parametrize(
     ('parameter_type', 'packet', 'expected_raw', 'expected_derived'),
     [
-        (xtcedef.BooleanParameterType(
+        (parameters.BooleanParameterType(
             'TEST_BOOL',
-            xtcedef.BinaryDataEncoding(fixed_size_in_bits=1)),
-         xtcedef.Packet(0b0011010000110010010100110000000001001011000000000100100100000000.to_bytes(length=64, byteorder='big')),
+            encodings.BinaryDataEncoding(fixed_size_in_bits=1)),
+         parseables.Packet(0b0011010000110010010100110000000001001011000000000100100100000000.to_bytes(length=64, byteorder='big')),
          b'\x00', True),
-        (xtcedef.BooleanParameterType(
+        (parameters.BooleanParameterType(
             'TEST_BOOL',
-            xtcedef.StringDataEncoding(encoding="UTF-8", termination_character='00')),
-         xtcedef.Packet(0b011001100110000101101100011100110110010101011111011010010111001101011111011101000111001001110101011101000110100001111001000000000010101101010111.to_bytes(length=18, byteorder='big')),
+            encodings.StringDataEncoding(encoding="UTF-8", termination_character='00')),
+         parseables.Packet(0b011001100110000101101100011100110110010101011111011010010111001101011111011101000111001001110101011101000110100001111001000000000010101101010111.to_bytes(length=18, byteorder='big')),
          'false_is_truthy', True),
-        (xtcedef.BooleanParameterType(
+        (parameters.BooleanParameterType(
             'TEST_BOOL',
-            xtcedef.IntegerDataEncoding(size_in_bits=2, encoding="unsigned")),
-         xtcedef.Packet(0b0011.to_bytes(length=1, byteorder='big')),
+            encodings.IntegerDataEncoding(size_in_bits=2, encoding="unsigned")),
+         parseables.Packet(0b0011.to_bytes(length=1, byteorder='big')),
          0, False),
-        (xtcedef.BooleanParameterType(
+        (parameters.BooleanParameterType(
             'TEST_BOOL',
-            xtcedef.IntegerDataEncoding(size_in_bits=2, encoding="unsigned")),
-         xtcedef.Packet(0b00001111.to_bytes(length=1, byteorder='big'), pos=4),
+            encodings.IntegerDataEncoding(size_in_bits=2, encoding="unsigned")),
+         parseables.Packet(0b00001111.to_bytes(length=1, byteorder='big'), pos=4),
          3, True),
-        (xtcedef.BooleanParameterType(
+        (parameters.BooleanParameterType(
             'TEST_BOOL',
-            xtcedef.FloatDataEncoding(size_in_bits=16)),
-         xtcedef.Packet(0b01010001010000001111111110000000.to_bytes(length=4, byteorder='big')),
+            encodings.FloatDataEncoding(size_in_bits=16)),
+         parseables.Packet(0b01010001010000001111111110000000.to_bytes(length=4, byteorder='big')),
          42.0, True),
-        (xtcedef.BooleanParameterType(
+        (parameters.BooleanParameterType(
             'TEST_BOOL',
-            xtcedef.FloatDataEncoding(size_in_bits=16)),
-         xtcedef.Packet(0b00000000101000101000000111111111.to_bytes(length=4, byteorder='big'), pos=7),
+            encodings.FloatDataEncoding(size_in_bits=16)),
+         parseables.Packet(0b00000000101000101000000111111111.to_bytes(length=4, byteorder='big'), pos=7),
          42.0, True),
     ]
 )
@@ -1880,8 +1881,8 @@ def test_boolean_parameter_parsing(parameter_type, packet, expected_raw, expecte
     </xtce:ReferenceTime>
 </xtce:AbsoluteTimeParameterType>
 """,
-         xtcedef.AbsoluteTimeParameterType(name='TEST_PARAM_Type', unit='seconds',
-                                           encoding=xtcedef.IntegerDataEncoding(size_in_bits=32, encoding="unsigned"),
+         parameters.AbsoluteTimeParameterType(name='TEST_PARAM_Type', unit='seconds',
+                                           encoding=encodings.IntegerDataEncoding(size_in_bits=32, encoding="unsigned"),
                                            epoch="TAI", offset_from="MilliSeconds")),
         ("""
 <xtce:AbsoluteTimeParameterType xmlns:xtce="http://www.omg.org/space/xtce" name="TEST_PARAM_Type">
@@ -1894,14 +1895,14 @@ def test_boolean_parameter_parsing(parameter_type, packet, expected_raw, expecte
     </xtce:ReferenceTime>
 </xtce:AbsoluteTimeParameterType>
 """,
-         xtcedef.AbsoluteTimeParameterType(
+         parameters.AbsoluteTimeParameterType(
              name='TEST_PARAM_Type', unit='s',
-             encoding=xtcedef.IntegerDataEncoding(
+             encoding=encodings.IntegerDataEncoding(
                  size_in_bits=32, encoding="unsigned",
-                 default_calibrator=xtcedef.PolynomialCalibrator(
+                 default_calibrator=calibrators.PolynomialCalibrator(
                      coefficients=[
-                         xtcedef.PolynomialCoefficient(0, 0),
-                         xtcedef.PolynomialCoefficient(1E-6, 1)
+                         calibrators.PolynomialCoefficient(0, 0),
+                         calibrators.PolynomialCoefficient(1E-6, 1)
                      ])),
              epoch="2009-10-10T12:00:00-05:00", offset_from="MilliSeconds")),
         ("""
@@ -1911,13 +1912,13 @@ def test_boolean_parameter_parsing(parameter_type, packet, expected_raw, expecte
     </xtce:Encoding>
 </xtce:AbsoluteTimeParameterType>
 """,
-         xtcedef.AbsoluteTimeParameterType(
+         parameters.AbsoluteTimeParameterType(
              name='TEST_PARAM_Type', unit='s',
-             encoding=xtcedef.IntegerDataEncoding(
+             encoding=encodings.IntegerDataEncoding(
                  size_in_bits=32, encoding="unsigned",
-                 default_calibrator=xtcedef.PolynomialCalibrator(
+                 default_calibrator=calibrators.PolynomialCalibrator(
                      coefficients=[
-                         xtcedef.PolynomialCoefficient(1.31E-6, 1)
+                         calibrators.PolynomialCoefficient(1.31E-6, 1)
                      ]))
          )),
         ("""
@@ -1927,14 +1928,14 @@ def test_boolean_parameter_parsing(parameter_type, packet, expected_raw, expecte
     </xtce:Encoding>
 </xtce:AbsoluteTimeParameterType>
 """,
-         xtcedef.AbsoluteTimeParameterType(
+         parameters.AbsoluteTimeParameterType(
              name='TEST_PARAM_Type', unit='s',
-             encoding=xtcedef.IntegerDataEncoding(
+             encoding=encodings.IntegerDataEncoding(
                  size_in_bits=32, encoding="unsigned",
-                 default_calibrator=xtcedef.PolynomialCalibrator(
+                 default_calibrator=calibrators.PolynomialCalibrator(
                      coefficients=[
-                         xtcedef.PolynomialCoefficient(147.884, 0),
-                         xtcedef.PolynomialCoefficient(1, 1)
+                         calibrators.PolynomialCoefficient(147.884, 0),
+                         calibrators.PolynomialCoefficient(1, 1)
                      ]))
          )),
         ("""
@@ -1944,14 +1945,14 @@ def test_boolean_parameter_parsing(parameter_type, packet, expected_raw, expecte
     </xtce:Encoding>
 </xtce:AbsoluteTimeParameterType>
 """,
-         xtcedef.AbsoluteTimeParameterType(
+         parameters.AbsoluteTimeParameterType(
              name='TEST_PARAM_Type', unit='s',
-             encoding=xtcedef.FloatDataEncoding(
+             encoding=encodings.FloatDataEncoding(
                  size_in_bits=32, encoding="IEEE-754",
-                 default_calibrator=xtcedef.PolynomialCalibrator(
+                 default_calibrator=calibrators.PolynomialCalibrator(
                      coefficients=[
-                         xtcedef.PolynomialCoefficient(147.884, 0),
-                         xtcedef.PolynomialCoefficient(1, 1)
+                         calibrators.PolynomialCoefficient(147.884, 0),
+                         calibrators.PolynomialCoefficient(1, 1)
                      ]))
          )),
     ]
@@ -1962,46 +1963,46 @@ def test_absolute_time_parameter_type(xml_string, expectation):
 
     if isinstance(expectation, Exception):
         with pytest.raises(type(expectation)):
-            xtcedef.AbsoluteTimeParameterType.from_parameter_type_xml_element(element, TEST_NAMESPACE)
+            parameters.AbsoluteTimeParameterType.from_parameter_type_xml_element(element, TEST_NAMESPACE)
     else:
-        result = xtcedef.AbsoluteTimeParameterType.from_parameter_type_xml_element(element, TEST_NAMESPACE)
+        result = parameters.AbsoluteTimeParameterType.from_parameter_type_xml_element(element, TEST_NAMESPACE)
         assert result == expectation
 
 
 @pytest.mark.parametrize(
     ('parameter_type', 'packet', 'expected_raw', 'expected_derived'),
     [
-        (xtcedef.AbsoluteTimeParameterType(name='TEST_PARAM_Type', unit='seconds',
-                                           encoding=xtcedef.IntegerDataEncoding(size_in_bits=32, encoding="unsigned"),
+        (parameters.AbsoluteTimeParameterType(name='TEST_PARAM_Type', unit='seconds',
+                                           encoding=encodings.IntegerDataEncoding(size_in_bits=32, encoding="unsigned"),
                                            epoch="TAI", offset_from="MilliSeconds"),
          # Exactly 64 bits so neatly goes into a bytes object without padding
-         xtcedef.Packet(0b0011010000110010010100110000000001001011000000000100100100000000.to_bytes(length=8, byteorder='big')),
+         parseables.Packet(0b0011010000110010010100110000000001001011000000000100100100000000.to_bytes(length=8, byteorder='big')),
          875713280, 875713280),
-        (xtcedef.AbsoluteTimeParameterType(
+        (parameters.AbsoluteTimeParameterType(
              name='TEST_PARAM_Type', unit='s',
-             encoding=xtcedef.IntegerDataEncoding(
+             encoding=encodings.IntegerDataEncoding(
                  size_in_bits=32, encoding="unsigned",
-                 default_calibrator=xtcedef.PolynomialCalibrator(
+                 default_calibrator=calibrators.PolynomialCalibrator(
                      coefficients=[
-                         xtcedef.PolynomialCoefficient(0, 0),
-                         xtcedef.PolynomialCoefficient(1E-6, 1)
+                         calibrators.PolynomialCoefficient(0, 0),
+                         calibrators.PolynomialCoefficient(1E-6, 1)
                      ])),
              epoch="2009-10-10T12:00:00-05:00", offset_from="MilliSeconds"),
          # Exactly 64 bits so neatly goes into a bytes object without padding
-         xtcedef.Packet(0b0011010000110010010100110000000001001011000000000100100100000000.to_bytes(length=8, byteorder='big')),
+         parseables.Packet(0b0011010000110010010100110000000001001011000000000100100100000000.to_bytes(length=8, byteorder='big')),
          875713280, 875.7132799999999),
-        (xtcedef.AbsoluteTimeParameterType(
+        (parameters.AbsoluteTimeParameterType(
              name='TEST_PARAM_Type', unit='s',
-             encoding=xtcedef.FloatDataEncoding(
+             encoding=encodings.FloatDataEncoding(
                  size_in_bits=32, encoding="IEEE-754",
-                 default_calibrator=xtcedef.PolynomialCalibrator(
+                 default_calibrator=calibrators.PolynomialCalibrator(
                      coefficients=[
-                         xtcedef.PolynomialCoefficient(147.884, 0),
-                         xtcedef.PolynomialCoefficient(1, 1)
+                         calibrators.PolynomialCoefficient(147.884, 0),
+                         calibrators.PolynomialCoefficient(1, 1)
                      ]))),
          # 65 bits, so we need a 9th byte with 7 bits of padding to hold it,
          # which means we need to be starting at pos=7
-         xtcedef.Packet(0b01000000010010010000111111011011001001011000000000100100100000000.to_bytes(length=9, byteorder='big'), pos=7),
+         parseables.Packet(0b01000000010010010000111111011011001001011000000000100100100000000.to_bytes(length=9, byteorder='big'), pos=7),
          3.1415927, 151.02559269999998),
     ]
 )
@@ -2017,11 +2018,11 @@ def test_absolute_time_parameter_parsing(parameter_type, packet, expected_raw, e
 # ---------------
 def test_parameter():
     """Test Parameter"""
-    xtcedef.Parameter(name='TEST_INT',
-                      parameter_type=xtcedef.IntegerParameterType(
+    parameters.Parameter(name='TEST_INT',
+                      parameter_type=parameters.IntegerParameterType(
                        name='TEST_INT_Type',
                        unit='floops',
-                       encoding=xtcedef.IntegerDataEncoding(size_in_bits=16, encoding='unsigned')),
+                       encoding=encodings.IntegerDataEncoding(size_in_bits=16, encoding='unsigned')),
                       short_description="Param short desc",
                       long_description="This is a long description of the parameter")
 
@@ -2032,14 +2033,14 @@ def test_parameter():
 def test_parsing_xtce_document(test_data_dir):
     """Tests parsing an entire XTCE document and makes assertions about the contents"""
     with open(test_data_dir / "test_xtce.xml") as x:
-        xdef = xtcedef.XtcePacketDefinition(x, ns=TEST_NAMESPACE)
+        xdef = definitions.XtcePacketDefinition(x, ns=TEST_NAMESPACE)
 
     # Test Parameter Types
     ptname = "USEC_Type"
     pt = xdef.named_parameter_types[ptname]
     assert pt.name == ptname
     assert pt.unit == "us"
-    assert isinstance(pt.encoding, xtcedef.IntegerDataEncoding)
+    assert isinstance(pt.encoding, encodings.IntegerDataEncoding)
 
     # Test Parameters
     pname = "ADAET1DAY"  # Named parameter
@@ -2058,14 +2059,14 @@ def test_parsing_xtce_document(test_data_dir):
     scname = "SecondaryHeaderContainer"
     sc = xdef.named_containers[scname]
     assert sc.name == scname
-    assert sc == xtcedef.SequenceContainer(
+    assert sc == parseables.SequenceContainer(
         name=scname,
         entry_list=[
-            xtcedef.Parameter(
+            parameters.Parameter(
                 name="DOY",
-                parameter_type=xtcedef.FloatParameterType(
+                parameter_type=parameters.FloatParameterType(
                     name="DOY_Type",
-                    encoding=xtcedef.IntegerDataEncoding(
+                    encoding=encodings.IntegerDataEncoding(
                         size_in_bits=16, encoding="unsigned"
                     ),
                     unit="day"
@@ -2073,11 +2074,11 @@ def test_parsing_xtce_document(test_data_dir):
                 short_description="Secondary Header Day of Year",
                 long_description="CCSDS Packet 2nd Header Day of Year in days."
             ),
-            xtcedef.Parameter(
+            parameters.Parameter(
                 name="MSEC",
-                parameter_type=xtcedef.FloatParameterType(
+                parameter_type=parameters.FloatParameterType(
                     name="MSEC_Type",
-                    encoding=xtcedef.IntegerDataEncoding(
+                    encoding=encodings.IntegerDataEncoding(
                         size_in_bits=32, encoding="unsigned"
                     ),
                     unit="ms"
@@ -2085,11 +2086,11 @@ def test_parsing_xtce_document(test_data_dir):
                 short_description="Secondary Header Coarse Time (millisecond)",
                 long_description="CCSDS Packet 2nd Header Coarse Time in milliseconds."
             ),
-            xtcedef.Parameter(
+            parameters.Parameter(
                 name="USEC",
-                parameter_type=xtcedef.FloatParameterType(
+                parameter_type=parameters.FloatParameterType(
                     name="USEC_Type",
-                    encoding=xtcedef.IntegerDataEncoding(
+                    encoding=encodings.IntegerDataEncoding(
                         size_in_bits=16, encoding="unsigned"
                     ),
                     unit="us"
@@ -2117,4 +2118,4 @@ def test__extract_bits(start, nbits):
     s = '0000111100001111'
     data = int(s, 2).to_bytes(2, byteorder="big")
 
-    assert xtcedef._extract_bits(data, start, nbits) == int(s[start:start+nbits], 2)
\ No newline at end of file
+    assert parseables._extract_bits(data, start, nbits) == int(s[start:start+nbits], 2)
\ No newline at end of file