Source code for triplets.export.cimxml_utils
# -------------------------------------------------------------------------------
# Name: export/cimxml_utils.py
# Purpose: Shared helpers for CIM XML export engines (python_lxml, cython_pugixml)
# -------------------------------------------------------------------------------
import uuid
import logging
from triplets.tools import get_namespace_map
from triplets._engine_detect import flavor
from triplets._header import ( # noqa: F401 — load_rdf_map re-exported for the cimxml engines
PROFILE_KEYS, PROFILE_URL_MAP, load_rdf_map, _profile_identity_index)
logger = logging.getLogger(__name__)
# Namespace for internal/undefined structures when export_undefined=True
TRIPLETS_NS = "http://triplets#"
def _values_for_key(instance_data, key):
"""VALUEs of rows with the given KEY (pandas or polars frame), nulls dropped."""
if flavor(instance_data) == "polars":
import polars
values = instance_data.filter(polars.col("KEY") == key)["VALUE"].to_list()
else:
values = instance_data.loc[instance_data["KEY"] == key, "VALUE"].tolist()
return [value for value in values if value is not None]
def _instance_profile_hints(instance_data):
"""Profile references the instance header may carry, in priority order:
old header messageType, new dcat:Dataset keyword, then the URI fields
(both can repeat — e.g. multiple Model.profile rows)."""
hints = []
for key in PROFILE_KEYS:
hints.extend(str(value) for value in _values_for_key(instance_data, key))
return hints
def _first_value(instance_data, key):
"""VALUE of the first row with the given KEY, or None."""
values = _values_for_key(instance_data, key)
return values[0] if values else None
[docs]
def resolve_instance_config(instance_data, rdf_map, namespace_map=None):
"""Resolve per-instance export config shared by all cimxml engines.
Returns
-------
tuple (file_name, namespace_map, instance_rdf_map)
file_name : from the instance 'label' (source filename) or a new UUID
namespace_map : given > instance NamespaceMap > schema ProfileNamespaceMap
instance_rdf_map : profile section matched by the schema's own identity
metadata (section key / keyword / versionIRI / conformsTo) against
the instance header (Model.messageType, keyword, Model.profile,
conformsTo); legacy URL-substring fallback for 2.4.15-era URLs;
schema root when nothing matches
"""
if not namespace_map:
namespace_map, xml_base = get_namespace_map(instance_data)
# Filename is kept under label
file_name = _first_value(instance_data, "label") or f"{uuid.uuid4()}.xml"
identity_index = _profile_identity_index(rdf_map)
instance_section = None
hints = _instance_profile_hints(instance_data)
for hint in hints:
if hint in identity_index:
instance_section = identity_index[hint]
break
if instance_section is None:
# legacy 2.4.15 profile URLs carry no exact schema identity — substring map
for hint in hints:
for url_part, section in PROFILE_URL_MAP.items():
if url_part in hint:
instance_section = section
break
if instance_section:
break
if instance_section is None and hints:
logger.warning("No schema profile matched instance header hints %s — using schema root", hints[:4])
instance_rdf_map = rdf_map.get(instance_section, rdf_map)
# No map in function call, nor in instance data, use profile map
if not namespace_map and instance_rdf_map:
namespace_map = instance_rdf_map.get("ProfileNamespaceMap")
return file_name, namespace_map, instance_rdf_map