Skip to content

Repository files navigation

reqif

A faithful, read-only parser for ReqIF (Requirements Interchange Format, OMG) in TypeScript.

Parses .reqif and .reqifz into a typed model that mirrors the specification — and stops there.

npm install reqif

Status: 0.x, import only. The API will change. There is no writer, and one is not planned — see Scope.


Why this exists

Python has reqif, Java has Eclipse RMF. JavaScript and TypeScript had nothing, so every Node service that needed to read a ReqIF file either shelled out to Python or hand-rolled an XML walk.

Quick start

import { readFile } from 'node:fs/promises';
import { parseReqIf, ReqIfIndex, walkSpecification, xhtmlToPlainText } from 'reqif';

const doc = parseReqIf(await readFile('example.reqif'));
const index = new ReqIfIndex(doc);

console.log(doc.header?.title, '—', doc.content.specObjects.length, 'requirements');

for (const specification of doc.content.specifications) {
  for (const node of walkSpecification(specification, index)) {
    const values = node.specObject ? index.valuesByName(node.specObject) : undefined;
    const text = values?.get('ReqIF.Text');
    console.log('  '.repeat(node.level - 1) + xhtmlToPlainText(text?.value));
  }
}

Scope

Drawing this line is the main design decision in the package. Everything opinionated lives on your side of it.

In scope — mechanical, spec-defined, identical for everyone:

  • XML → a typed ReqIfDocument, structurally faithful, no interpretation
  • .reqifz unpacking and embedded object (attachment) access
  • IDENTIFIER and LAST-CHANGE preserved on every element
  • Tool extensions preserved verbatim as raw XML
  • Streaming parse, for the enterprise specifications that run to hundreds of megabytes
  • Reference resolution and dangling-reference reporting, as an explicit step

Out of scope — your application's decisions, not this library's:

  • Mapping SPEC-OBJECT-TYPEs onto your own requirement types
  • Deciding which attribute holds "the requirement text"
  • Flattening hierarchies into levels, or numbering them
  • Mapping relation types onto your link model
  • Writing ReqIF. Round-trip export is roughly triple the work of import, because it must preserve foreign tools' proprietary attributes faithfully or corrupt customers' data. Read-only is a deliberate, stated boundary.

Design notes

Nothing is renamed or inferred. If the XML says SPEC-OBJECT-TYPE, the model says specObjectType. Element and attribute names are camel-cased and otherwise left alone.

References stay as strings. A real file can reference an identifier it does not contain, so resolving during parse would force the parser to either throw or invent. Resolution is a separate step via ReqIfIndex, which can also tell you what did not resolve:

for (const problem of new ReqIfIndex(doc).danglingReferences()) {
  console.warn(problem.message);
}

Worth running before an import: a dangling SPEC-OBJECT reference in a hierarchy means a requirement will silently go missing, and it is much better to say so up front than to let a user discover it by counting rows.

Numbers stay as strings. ReqIF integers are unbounded and reals carry an exporter-chosen precision. 9007199254740993 does not survive a round trip through a JavaScript number, so value is the verbatim string and any conversion is yours to make deliberately.

Dates stay as written. LAST-CHANGE and date values are kept as the original xsd:dateTime strings, never re-formatted through a Date, which would silently rewrite time zone offsets.

XHTML stays as markup. ATTRIBUTE-VALUE-XHTML values are handed back as serialized XHTML with namespace prefixes intact, because the markup is the requirement — tables and embedded object references carry meaning. xhtmlToPlainText is available when you actually want text, and is the one function here that makes a judgement call.

Parsing is lenient by default. Real exports violate the schema routinely, and a parser that fails on the first violation is useless against them. Recoverable problems become diagnostics and parsing continues:

const doc = parseReqIf(bytes);
for (const d of doc.diagnostics) console.warn(`${d.severity}: ${d.message}`);

parseReqIf(bytes, { strict: true }); // throws ReqIfParseError on error-severity problems

Large files

Enterprise specifications are routinely hundreds of megabytes. parseReqIfStream never holds the source text in memory, and with collectSpecObjects: false it hands you each requirement once and then drops it, keeping peak memory proportional to the largest single requirement rather than to the file:

import { createReadStream } from 'node:fs';
import { parseReqIfStream } from 'reqif';

const doc = await parseReqIfStream(createReadStream('huge.reqif'), {
  collectSpecObjects: false,
  onSpecObject: (specObject) => insert(specObject),
});
// doc still carries the header, types and specification structure

Archives (.reqifz)

A .reqifz is a ZIP holding one or more documents plus the binary objects their XHTML references. Attachments are described but not read by default, because an export whose images outweigh its requirements by two orders of magnitude is normal:

import { ReqIfArchive, parseReqIfz } from 'reqif';

const { documents, attachments } = await parseReqIfz('export.reqifz');

// Reach for the bytes explicitly
const archive = await ReqIfArchive.open('export.reqifz');
try {
  const png = await archive.readAttachment('images/diagram.png');
} finally {
  await archive.close();
}

Encoding

Most ReqIF is UTF-8, but not all of it. The format is used heavily by European automotive and aerospace suppliers, and older exporters still emit ISO-8859-1 or UTF-16 — decoding those as UTF-8 corrupts exactly the umlauts and accents that appear in the requirement text. When given bytes, this library reads the BOM or the XML declaration before decoding. Given a string, it assumes you have already decoded correctly.

API

Export Purpose
parseReqIf(input, options?) Parse a document from a string or bytes
parseReqIfStream(source, options?) Parse from any async iterable of strings/bytes
parseReqIfz(input, options?) Parse every document in an archive
ReqIfArchive Lazy access to an archive's documents and attachments
ReqIfIndex Identifier lookup, relation traversal, dangling-reference reporting
walkSpecification(spec, index?) Depth-first walk in document order, with levels
xhtmlToPlainText(xhtml, options?) Opt-in flattening of rich text
ReqIfParseError Thrown only in strict mode

Full types are published with the package.

Testing

npm test                  # unit tests
npm run test:differential # diff against the Python reqif implementation

The differential harness parses the same fixtures with the mature Python reqif package and diffs a normalized projection of both results. Hand-written fixtures only test what their author already understood about the format; an independent implementation disagrees precisely where that understanding is wrong. It skips cleanly when Python or the package is unavailable, so it is not a dependency for contributors.

Where the two implementations legitimately differ, the harness normalizes rather than chases — and where the oracle is simply less complete (it does not model SPEC-ATTRIBUTES on SPECIFICATION-TYPE), that comparison is excluded with a comment rather than "fixed" to match.

Fixtures are synthetic and hand-built. Real ReqIF files belong to customers and cannot be shipped.

Compatibility

Node 18+. Covers ReqIF 1.0, 1.1 and 1.2, which share one namespace and one schema shape.

License

Apache-2.0

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages