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)