Repository navigation
Reading ReqIF
Reading is done by ReqIFDeserializer, which implements IReqIFDeSerializer. It turns a .reqif file, a .reqifz / .zip archive or a stream into ReqIF objects. See ReqIF Concepts for what those objects contain.
IEnumerable<ReqIF> Deserialize(string fileUri, bool validate = false, ValidationEventHandler validationEventHandler = null);
IEnumerable<ReqIF> Deserialize(Stream stream, SupportedFileExtensionKind fileExtensionKind, bool validate = false, ValidationEventHandler validationEventHandler = null);
Task<IEnumerable<ReqIF>> DeserializeAsync(string fileUri, CancellationToken token, bool validate = false, ValidationEventHandler validationEventHandler = null);
Task<IEnumerable<ReqIF>> DeserializeAsync(Stream stream, SupportedFileExtensionKind fileExtensionKind, CancellationToken token, bool validate = false, ValidationEventHandler validationEventHandler = null);All four return a list of ReqIF objects, because an archive can contain several ReqIF documents. A .reqif file gives a list with one element.
var deserializer = new ReqIFDeserializer();
var reqif = deserializer.Deserialize("requirements.reqif").First();The extension of the path picks the format: .reqif is read as XML, .reqifz and .zip as a zip archive. Other extensions throw an ArgumentException. The check is case-sensitive, see the FAQ.
With a stream, you say what the stream contains:
await using var stream = File.OpenRead("requirements.reqifz");
var reqifs = await deserializer.DeserializeAsync(stream, SupportedFileExtensionKind.Reqifz, cancellationToken);The stream must be seekable. To read from a network or upload stream, copy it into a MemoryStream first:
using var buffer = new MemoryStream();
await uploadStream.CopyToAsync(buffer, cancellationToken);
var reqifs = await deserializer.DeserializeAsync(buffer, SupportedFileExtensionKind.Reqif, cancellationToken);A .reqifz archive can contain several .reqif files. The deserializer reads all of them, in archive order:
foreach (var reqif in deserializer.Deserialize("project.reqifz"))
{
Console.WriteLine($"{reqif.TheHeader.Title}: {reqif.CoreContent.SpecObjects.Count} objects");
}Pass validate: true to check the document against the official ReqIF XML schema, including the XHTML modules. The schemas are embedded in the library, so validation does not need network access.
Collect the problems with a handler:
var problems = new List<string>();
var reqif = deserializer.Deserialize(
"requirements.reqif",
validate: true,
validationEventHandler: (sender, e) => problems.Add($"{e.Severity} at line {e.Exception?.LineNumber}: {e.Message}"))
.First();
problems.ForEach(Console.WriteLine);With a handler, reading continues after a schema error, so you get the document and the list of problems. Without a handler, the first schema error throws an XmlSchemaValidationException:
try
{
var reqif = deserializer.Deserialize("requirements.reqif", validate: true).First();
}
catch (XmlSchemaValidationException e)
{
Console.WriteLine($"Not a valid ReqIF document: {e.Message} (line {e.LineNumber})");
}Validation is off by default. Many tools export files that are not strictly valid, and ReqIFSharp reads most of them anyway. Passing a handler while validate is false throws an ArgumentException.
Each SpecObject (and Specification, SpecRelation and RelationGroup) has a list of Values. Each value knows its AttributeDefinition. Switch on the type to get a typed value:
foreach (var value in specObject.Values)
{
var name = value.AttributeDefinition.LongName;
switch (value)
{
case AttributeValueString s:
Console.WriteLine($"{name}: {s.TheValue}");
break;
case AttributeValueXHTML x:
Console.WriteLine($"{name}: {x.ExtractUnformattedTextFromValue()}");
break;
case AttributeValueEnumeration e:
Console.WriteLine($"{name}: {string.Join(", ", e.Values.Select(v => v.LongName))}");
break;
case AttributeValueInteger i:
Console.WriteLine($"{name}: {i.TheValue}");
break;
case AttributeValueReal r:
Console.WriteLine($"{name}: {r.TheValue}");
break;
case AttributeValueBoolean b:
Console.WriteLine($"{name}: {b.TheValue}");
break;
case AttributeValueDate d:
Console.WriteLine($"{name}: {d.TheValue:yyyy-MM-dd}");
break;
}
}To find a value by attribute name:
var status = specObject.Values
.OfType<AttributeValueEnumeration>()
.FirstOrDefault(v => v.AttributeDefinition.LongName == "Status")
?.Values.FirstOrDefault()?.LongName;Attribute names are chosen by the tool that exported the file. Many tools use the names from the prostep ivip ReqIF Implementation Guide, such as ReqIF.Text, ReqIF.ChapterName and ReqIF.ForeignID. Look at the SpecTypes of a sample file to see which names your source tool uses.
ReqIFSharp.Extensions has QueryFormattedValue(), which formats any value as a string. See ReqIFSharp.Extensions.
AttributeValueXHTML has three ways to look at the value:
| Member | Returns |
|---|---|
TheValue |
the XHTML content, for example <xhtml:div xmlns:xhtml="http://www.w3.org/1999/xhtml">Some <xhtml:b>bold</xhtml:b> text</xhtml:div>
|
ExtractUnformattedTextFromValue() |
the text without markup: Some bold text. Whitespace between elements is kept, so trim or normalize it for display. |
TheOriginalValue |
the original XHTML when the tool also exported an unsimplified version (IsSimplified is true) |
A Specification is a tree of SpecHierarchy nodes. Each node points to a SpecObject and can have children. To print a document with indentation:
void Print(IEnumerable<SpecHierarchy> nodes, int depth)
{
foreach (var node in nodes)
{
Console.WriteLine($"{new string(' ', depth * 2)}{node.Object.LongName}");
Print(node.Children, depth + 1);
}
}
foreach (var specification in reqif.CoreContent.Specifications)
{
Console.WriteLine(specification.LongName);
Print(specification.Children, 1);
}To get all spec objects of a specification as a flat list in document order, use specification.QueryAllContainedSpecObjects() from ReqIFSharp.Extensions.
foreach (var relation in reqif.CoreContent.SpecRelations)
{
Console.WriteLine($"{relation.Source.LongName} --{relation.Type.LongName}--> {relation.Target.LongName}");
}XHTML values can embed images and other files. Each one is an ExternalObject in AttributeValueXHTML.ExternalObjects, with a Uri, a MimeType and optional Width and Height. When the document came from a .reqifz archive, the file is stored in the archive under the Uri. QueryLocalData copies it to a stream:
const string archive = "requirements.reqifz";
var reqif = new ReqIFDeserializer().Deserialize(archive).First();
Directory.CreateDirectory("attachments");
foreach (var xhtml in reqif.CoreContent.SpecObjects.SelectMany(o => o.Values).OfType<AttributeValueXHTML>())
{
foreach (var externalObject in xhtml.ExternalObjects.Where(e => e.IsDataLocal()))
{
using var output = File.Create(Path.Combine("attachments", Path.GetFileName(externalObject.Uri)));
externalObject.QueryLocalData(archive, output);
}
}IsDataLocal() is false when the Uri is an http or https address; download those yourself. The QueryLocalData(Stream, Stream) overload reads from an archive stream, and disposes it, so open a new stream for each call. To do this with less code, and with caching, use the ReqIFLoaderService from ReqIFSharp.Extensions.
ReqIFDeserializer takes an optional ILoggerFactory from Microsoft.Extensions.Logging. Without one, nothing is logged. With one, you see timings at Trace level and warnings for content that ReqIFSharp skips:
using Microsoft.Extensions.Logging;
using var loggerFactory = LoggerFactory.Create(builder => builder
.AddConsole() // package Microsoft.Extensions.Logging.Console
.SetMinimumLevel(LogLevel.Debug));
var deserializer = new ReqIFDeserializer(loggerFactory);warn: ReqIFSharp.SpecHierarchy[0]
The SPEC-HIERARCHY element at line:position 365:26 is not supported
Any logging library that plugs into Microsoft.Extensions.Logging works, such as Serilog or NLog. In an ASP.NET Core or other hosted application, register the deserializer in the service container and it gets the application's ILoggerFactory:
builder.Services.AddSingleton<IReqIFDeSerializer, ReqIFDeserializer>();A ReqIFDeserializer keeps no state between calls, so one instance can be reused and shared. The ReqIF objects it returns are plain objects that are not thread safe: don't change them from several threads at once.
copyright @ Starion Group S.A.