diff --git a/openmc/_xml.py b/openmc/_xml.py index 758d8052557..1c86acfc16f 100644 --- a/openmc/_xml.py +++ b/openmc/_xml.py @@ -1,3 +1,31 @@ +_LXML_MAX_TEXT_SIZE = 2 * 1024**3 # 2 GB + + +def _check_text_size(text, context=""): + """Raise an error if *text* exceeds the lxml 2 GB limit. + + Parameters + ---------- + text : str + Text that will be assigned to an XML element or attribute. + context : str + Human-readable description of what is being written, used in the + error message. + + Raises + ------ + ValueError + If len(text) >= 2 GB. + """ + if len(text) >= _LXML_MAX_TEXT_SIZE: + size_gb = len(text) / 1024**3 + raise ValueError( + f"The text content for {context!r} is ~{size_gb:.1f} GB, which " + f"exceeds the lxml limit of 2 GB. lxml will silently produce an " + f"empty element. Consider reducing the number of entries." + ) + + def clean_indentation(element, level=0, spaces_per_level=2, trailing_indent=True): """Set indentation of XML element and its sub-elements. Copied and pasted from https://effbot.org/zone/element-lib.htm#prettyprint. diff --git a/openmc/cell.py b/openmc/cell.py index 499cf950429..3c246e1c2d1 100644 --- a/openmc/cell.py +++ b/openmc/cell.py @@ -8,7 +8,7 @@ import openmc import openmc.checkvalue as cv -from ._xml import get_elem_list, get_text +from ._xml import _check_text_size, get_elem_list, get_text from .mixin import IDManagerMixin from .plots import add_plot_params from .region import Region, Complement @@ -640,6 +640,7 @@ def create_xml_subelement(self, xml_element, memo=None): matlist_str = " ".join( ["void" if m is None else str(m.id) for m in self.fill] ) + _check_text_size(matlist_str, f"cell {self.id} material") material_subelement.text = matlist_str elif self.fill_type in ('universe', 'lattice'): @@ -681,14 +682,18 @@ def create_surface_elements(node, element, memo=None): if self.temperature is not None: if isinstance(self.temperature, Iterable): temperature_subelement= ET.SubElement(element, "temperature") - temperature_subelement.text = ' '.join(str(t) for t in self.temperature) + text = ' '.join(str(t) for t in self.temperature) + _check_text_size(text, f"cell {self.id} temperature") + temperature_subelement.text = text else: element.set("temperature", str(self.temperature)) if self.density is not None: if isinstance(self.density, Iterable): density_subelement= ET.SubElement(element, "density") - density_subelement.text = ' '.join(str(d) for d in self.density) + text = ' '.join(str(d) for d in self.density) + _check_text_size(text, f"cell {self.id} density") + density_subelement.text = text else: element.set("density", str(self.density)) diff --git a/tests/unit_tests/test_cell.py b/tests/unit_tests/test_cell.py index 8d39c071b7b..93253806e72 100644 --- a/tests/unit_tests/test_cell.py +++ b/tests/unit_tests/test_cell.py @@ -433,3 +433,9 @@ def test_plot(run_in_tmpdir): # ensure that calling the plot method doesn't # affect the universe ID space assert u_before.id + 1 == u_after.id + + +def test_xml_text_size_check(): + from openmc._xml import _check_text_size, _LXML_MAX_TEXT_SIZE + with pytest.raises(ValueError, match="exceeds the lxml limit"): + _check_text_size("x" * _LXML_MAX_TEXT_SIZE, "test element")