Exchange API Reference
ReqIF Exporter — converts .rqtr YAML requirement files to ReqIF XML.
Produces a ReqIF 1.1 document with: - One SpecType (SPEC-OBJECT-TYPE) with Title and Description attribute definitions - One SpecObject per requirement - SpecRelations for derived_from links
build_reqif(requirements, tool_id='reqtrace-exchange')
Build a ReqIF XML ElementTree from a list of requirement dicts.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
requirements
|
List[Dict[str, Any]]
|
List of dicts with keys: id, title, description (opt), derived_from (opt). |
required |
tool_id
|
str
|
Identifier string embedded in the ReqIF header. |
'reqtrace-exchange'
|
Returns:
| Type | Description |
|---|---|
ElementTree
|
An ElementTree representing the ReqIF document. |
Source code in reqtrace/exchange/reqif_exporter.py
def build_reqif(requirements: List[Dict[str, Any]], tool_id: str = "reqtrace-exchange") -> ET.ElementTree:
"""
Build a ReqIF XML ElementTree from a list of requirement dicts.
Args:
requirements: List of dicts with keys: id, title, description (opt),
derived_from (opt).
tool_id: Identifier string embedded in the ReqIF header.
Returns:
An ElementTree representing the ReqIF document.
"""
ET.register_namespace("", _NS)
ET.register_namespace("xhtml", _XHTML_NS)
root = ET.Element(f"{{{_NS}}}REQ-IF")
# Note: do NOT call root.set("xmlns", ...) — ElementTree adds the xmlns
# declaration automatically from register_namespace when serialising.
# --- THE-HEADER ---
header_el = _el(root, "THE-HEADER", {})
_el(
header_el,
"REQ-IF-HEADER",
{
"IDENTIFIER": _uid(),
"CREATION-TIME": _now(),
"REPOSITORY-ID": tool_id,
"REQ-IF-TOOL-ID": tool_id,
"REQ-IF-VERSION": "1.1",
"SOURCE-TOOL-ID": tool_id,
"TITLE": "reqtrace export",
},
)
# --- CORE-CONTENT ---
core = _el(root, "CORE-CONTENT", {})
content = _el(core, "REQ-IF-CONTENT", {})
# DATATYPES
datatypes_el = _el(content, "DATATYPES", {})
dt_string_id = _uid()
_el(
datatypes_el,
"DATATYPE-DEFINITION-STRING",
{
"IDENTIFIER": dt_string_id,
"LAST-CHANGE": _now(),
"LONG-NAME": "String",
"MAX-LENGTH": "10000",
},
)
# SPEC-TYPES — one generic object type
spec_types_el = _el(content, "SPEC-TYPES", {})
spec_obj_type_id = _uid()
sot = _el(
spec_types_el,
"SPEC-OBJECT-TYPE",
{
"IDENTIFIER": spec_obj_type_id,
"LAST-CHANGE": _now(),
"LONG-NAME": "Requirement",
},
)
attrs_el = _el(sot, "SPEC-ATTRIBUTES", {})
# Title and Description attribute definitions
title_attr_id = _uid()
desc_attr_id = _uid()
for attr_id, attr_name in [(title_attr_id, "Title"), (desc_attr_id, "Description")]:
ad = _el(
attrs_el,
"ATTRIBUTE-DEFINITION-STRING",
{
"IDENTIFIER": attr_id,
"LAST-CHANGE": _now(),
"LONG-NAME": attr_name,
},
)
td = _el(ad, "TYPE", {})
ref = ET.SubElement(td, f"{{{_NS}}}DATATYPE-DEFINITION-STRING-REF")
ref.text = dt_string_id
# SPEC-OBJECTS
spec_objects_el = _el(content, "SPEC-OBJECTS", {})
id_to_identifier: Dict[str, str] = {}
for req in requirements:
req_id: str = req.get("id", "")
title: str = req.get("title", "")
description: str = req.get("description", "")
obj_identifier = req_id # use the reqtrace ID directly as the ReqIF IDENTIFIER
id_to_identifier[req_id] = obj_identifier
so = _el(
spec_objects_el,
"SPEC-OBJECT",
{
"IDENTIFIER": obj_identifier,
"LAST-CHANGE": _now(),
"LONG-NAME": title,
},
)
# Type reference
tp = _el(so, "TYPE", {})
ref = ET.SubElement(tp, f"{{{_NS}}}SPEC-OBJECT-TYPE-REF")
ref.text = spec_obj_type_id
# Values
vals = _el(so, "VALUES", {})
# Title attribute value
av_title = _el(vals, "ATTRIBUTE-VALUE-STRING", {"THE-VALUE": title})
defn_t = _el(av_title, "DEFINITION", {})
r = ET.SubElement(defn_t, f"{{{_NS}}}ATTRIBUTE-DEFINITION-STRING-REF")
r.text = title_attr_id
# Description attribute value (optional)
if description:
av_desc = _el(vals, "ATTRIBUTE-VALUE-STRING", {"THE-VALUE": description})
defn_d = _el(av_desc, "DEFINITION", {})
rd = ET.SubElement(defn_d, f"{{{_NS}}}ATTRIBUTE-DEFINITION-STRING-REF")
rd.text = desc_attr_id
# SPEC-RELATION-GROUPS (empty placeholder)
_el(content, "SPEC-RELATION-GROUPS", {})
# SPEC-RELATIONS — derived_from links
spec_relations_el = _el(content, "SPEC-RELATIONS", {})
for req in requirements:
req_id = req.get("id", "")
for parent_id in req.get("derived_from", []):
rel = _el(
spec_relations_el,
"SPEC-RELATION",
{
"IDENTIFIER": _uid(),
"LAST-CHANGE": _now(),
},
)
_el(rel, "TYPE", {})
src_el = _el(rel, "SOURCE", {})
src_ref = ET.SubElement(src_el, f"{{{_NS}}}SPEC-OBJECT-REF")
src_ref.text = id_to_identifier.get(req_id, req_id)
tgt_el = _el(rel, "TARGET", {})
tgt_ref = ET.SubElement(tgt_el, f"{{{_NS}}}SPEC-OBJECT-REF")
tgt_ref.text = id_to_identifier.get(parent_id, parent_id)
# SPECIFICATIONS (empty)
_el(content, "SPECIFICATIONS", {})
ET.indent(root, space=" ")
return ET.ElementTree(root)
export_reqif(rqtr_path, output_path)
Export a .rqtr YAML file to a ReqIF XML file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rqtr_path
|
Union[str, Path]
|
Path to the source .rqtr file. |
required |
output_path
|
Union[str, Path]
|
Path to write the output .reqif file. |
required |
Source code in reqtrace/exchange/reqif_exporter.py
def export_reqif(rqtr_path: Union[str, Path], output_path: Union[str, Path]) -> None:
"""
Export a .rqtr YAML file to a ReqIF XML file.
Args:
rqtr_path: Path to the source .rqtr file.
output_path: Path to write the output .reqif file.
"""
# @trace-start: REQ-EXCHANGE-EXPORT
rqtr = Path(rqtr_path)
with open(rqtr, "r", encoding="utf-8") as f:
requirements: List[Dict[str, Any]] = yaml.safe_load(f)
if not isinstance(requirements, list):
raise ValueError(f"Expected a list of requirements in '{rqtr}', got {type(requirements).__name__}")
tree = build_reqif(requirements)
out = Path(output_path)
out.parent.mkdir(parents=True, exist_ok=True)
# Write with XML declaration
tree.write(out, encoding="utf-8", xml_declaration=True)
ReqIF Importer — parses a .reqif XML file and produces a .rqtr YAML file.
Supports the core ReqIF 1.0 / 1.1 subset: - SpecObjects → requirements (id, title, description) - SpecRelations → derived_from links
import_reqif(reqif_path, output_path)
Import a ReqIF file and write the resulting requirements as a .rqtr YAML file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
reqif_path
|
Union[str, Path]
|
Path to the source .reqif file. |
required |
output_path
|
Union[str, Path]
|
Path to write the output .rqtr file. |
required |
Source code in reqtrace/exchange/reqif_importer.py
def import_reqif(reqif_path: Union[str, Path], output_path: Union[str, Path]) -> None:
"""
Import a ReqIF file and write the resulting requirements as a .rqtr YAML file.
Args:
reqif_path: Path to the source .reqif file.
output_path: Path to write the output .rqtr file.
"""
reqs = parse_reqif(reqif_path)
out = Path(output_path)
out.parent.mkdir(parents=True, exist_ok=True)
with open(out, "w", encoding="utf-8") as f:
yaml.dump(reqs, f, allow_unicode=True, sort_keys=False, default_flow_style=False)
parse_reqif(reqif_path)
Parse a ReqIF file and return a list of requirement dicts suitable for writing as a .rqtr YAML file.
Returns:
| Type | Description |
|---|---|
List[Dict[str, Any]]
|
List of dicts with keys: id, title, description (optional), |
List[Dict[str, Any]]
|
derived_from (optional). |
Source code in reqtrace/exchange/reqif_importer.py
def parse_reqif(reqif_path: Union[str, Path]) -> List[Dict[str, Any]]:
"""
Parse a ReqIF file and return a list of requirement dicts suitable for
writing as a .rqtr YAML file.
Returns:
List of dicts with keys: id, title, description (optional),
derived_from (optional).
"""
# @trace-start: REQ-EXCHANGE-IMPORT
path = Path(reqif_path)
tree = ET.parse(path)
root = tree.getroot()
# Strip namespace from root tag if needed (ElementTree keeps them)
core_content = root.find(_tag("CORE-CONTENT"))
if core_content is None:
# Try without namespace (some tools omit it)
core_content = root.find("CORE-CONTENT")
if core_content is None:
raise ValueError(f"No CORE-CONTENT element found in '{path}'")
req_if_content = core_content.find(_tag("REQ-IF-CONTENT"))
if req_if_content is None:
req_if_content = core_content.find("REQ-IF-CONTENT")
if req_if_content is None:
raise ValueError(f"No REQ-IF-CONTENT element found in '{path}'")
spec_objects_el = req_if_content.find(_tag("SPEC-OBJECTS"))
spec_relations_el = req_if_content.find(_tag("SPEC-RELATIONS"))
# Build id → requirement dict
requirements: Dict[str, Dict[str, Any]] = {}
if spec_objects_el is not None:
for spec_obj in spec_objects_el.findall(_tag("SPEC-OBJECT")):
obj_id = spec_obj.get("IDENTIFIER", "").strip()
long_name = spec_obj.get("LONG-NAME", "").strip()
req: Dict[str, Any] = {"id": obj_id, "title": long_name}
# Try to extract ATTRIBUTE-VALUEs for description
values_el = spec_obj.find(_tag("VALUES"))
if values_el is not None:
desc = _find_value(values_el)
if desc:
req["description"] = desc
requirements[obj_id] = req
# Process SpecRelations: SOURCE derives from TARGET
if spec_relations_el is not None:
for relation in spec_relations_el.findall(_tag("SPEC-RELATION")):
source_el = relation.find(f".//{_tag('SOURCE')}/{_tag('SPEC-OBJECT-REF')}")
target_el = relation.find(f".//{_tag('TARGET')}/{_tag('SPEC-OBJECT-REF')}")
if source_el is not None and target_el is not None:
src_id = source_el.text.strip() if source_el.text else ""
tgt_id = target_el.text.strip() if target_el.text else ""
if src_id in requirements and tgt_id:
derived = requirements[src_id].setdefault("derived_from", [])
if tgt_id not in derived:
derived.append(tgt_id)
return list(requirements.values())
CLI for reqtrace-exchange: import ReqIF → .rqtr and export .rqtr → ReqIF.
main(args=None)
CLI entrypoint for reqtrace-exchange.
Source code in reqtrace/exchange/exchange_cli.py
def main(args: Optional[List[str]] = None) -> None:
"""CLI entrypoint for reqtrace-exchange."""
parser = argparse.ArgumentParser(
prog="reqtrace-exchange",
description="Import and export reqtrace (.rqtr) files to/from ReqIF format.",
)
_add_verbose(parser)
subparsers = parser.add_subparsers(dest="command", metavar="COMMAND")
subparsers.required = True
# --- import sub-command ---
import_parser = subparsers.add_parser(
"import",
help="Import a ReqIF file and write a .rqtr YAML file.",
)
import_parser.add_argument("input", metavar="INPUT.reqif", help="Path to the source .reqif file.")
import_parser.add_argument(
"-o",
"--output",
metavar="OUTPUT.rqtr",
required=True,
help="Path for the output .rqtr file.",
)
_add_verbose(import_parser)
# --- export sub-command ---
export_parser = subparsers.add_parser(
"export",
help="Export a .rqtr file as a ReqIF XML file.",
)
export_parser.add_argument("input", metavar="INPUT.rqtr", help="Path to the source .rqtr file.")
export_parser.add_argument(
"-o",
"--output",
metavar="OUTPUT.reqif",
required=True,
help="Path for the output .reqif file.",
)
_add_verbose(export_parser)
parsed = parser.parse_args(args)
logging.basicConfig(
level=logging.DEBUG if parsed.verbose else logging.INFO,
format="%(message)s",
)
try:
# @trace-start: SYS-EXCHANGE
if parsed.command == "import":
input_path = Path(parsed.input)
output_path = Path(parsed.output)
log.info("Importing '%s' → '%s' ...", input_path, output_path)
import_reqif(input_path, output_path)
log.info("✅ Import complete: %s", output_path)
elif parsed.command == "export":
input_path = Path(parsed.input)
output_path = Path(parsed.output)
log.info("Exporting '%s' → '%s' ...", input_path, output_path)
export_reqif(input_path, output_path)
log.info("✅ Export complete: %s", output_path)
# @trace-end: SYS-EXCHANGE
except Exception as exc: # pylint: disable=broad-exception-caught
log.error("Error during %s: %s", parsed.command, exc)
sys.exit(1)
sys.exit(0)