Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ The repository ships two NuGet packages that version independently: **ReqIFSharp
### Changed

- A `REQ-IF` root element in a namespace other than the ReqIF namespace (`http://www.omg.org/spec/ReqIF/20110401/reqif.xsd`) is no longer read as a ReqIF document; it is skipped and a warning is logged. Before, such an element was read when it had no prefix. A root in no namespace is still read ([#87](https://github.com/STARIONGROUP/ReqIFSharp/issues/87)).
- An element other than `SPEC-RELATION-REF` inside the `SPEC-RELATIONS` of a relation group is now skipped with a warning, and the references after it are read. Before, it caused an `XmlException` ([#94](https://github.com/STARIONGROUP/ReqIFSharp/issues/94)).

### Fixed

- A document that binds the ReqIF namespace to a prefix (`<reqif:REQ-IF xmlns:reqif="...">`) was read as zero documents, and type, source/target and relation-group references in it were not read. Elements are now matched on their local name and namespace ([#87](https://github.com/STARIONGROUP/ReqIFSharp/issues/87)).
- A document without whitespace between its elements (minified XML) lost data without an error: every other header field (such as `CREATION-TIME` and `REQ-IF-TOOL-ID`) and every other enumeration value of an attribute value was skipped, and the spec relations of a relation group were not read or caused an `XmlException` ([#94](https://github.com/STARIONGROUP/ReqIFSharp/issues/94)).

## [10.1.2] - 2026-10-18

Expand Down
373 changes: 373 additions & 0 deletions ReqIFSharp.Tests/MinifiedDocumentTestFixture.cs

Large diffs are not rendered by default.

134 changes: 28 additions & 106 deletions ReqIFSharp.Tests/PrefixedNamespaceTestFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,7 @@

namespace ReqIFSharp.Tests
{
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;
Expand All @@ -46,26 +42,12 @@ namespace ReqIFSharp.Tests
[TestFixture]
public class PrefixedNamespaceTestFixture
{
private static readonly XNamespace ReqIFNamespace = DefaultXmlAttributeFactory.ReqIFSchemaUri;

// output.reqif and testreqif.reqif are left out: they contain unresolved type references,
// so their object graphs cannot be serialized for the comparison
private static readonly string[] TestFiles =
{
"Datatype-Demo.reqif",
"DefaultValueDemo.reqif",
"ProR_Traceability-Template-v1.0.reqif",
"reqifsharpgenerated.reqif"
};

private static readonly string[] ForeignNamespaceDocuments =
{
"<other:REQ-IF xmlns:other=\"http://example.com/not-reqif\"><other:THE-HEADER/></other:REQ-IF>",
"<REQ-IF xmlns=\"http://example.com/not-reqif\"><THE-HEADER/></REQ-IF>"
};

private static readonly SupportedFileExtensionKind[] Kinds = { SupportedFileExtensionKind.Reqif, SupportedFileExtensionKind.Reqifz };

private TestLogEventSink testLogEventSink;

private ILoggerFactory loggerFactory;
Expand All @@ -89,71 +71,71 @@ public void TearDown()
this.loggerFactory.Dispose();
}

[TestCaseSource(nameof(TestFiles))]
[TestCaseSource(typeof(TestDocuments), nameof(TestDocuments.ComparableFiles))]
public void Verify_that_a_prefixed_document_is_read_the_same_as_the_original(string fileName)
{
var original = ReadTestFile(fileName);
var original = TestDocuments.Read(fileName);
var prefixed = CreatePrefixedCopy(original);

foreach (var kind in Kinds)
foreach (var kind in TestDocuments.Kinds)
{
var deserializer = new ReqIFDeserializer();

var expected = deserializer.Deserialize(ToStream(original, kind), kind).ToList();
var actual = deserializer.Deserialize(ToStream(prefixed, kind), kind).ToList();
var expected = deserializer.Deserialize(TestDocuments.ToStream(original, kind), kind).ToList();
var actual = deserializer.Deserialize(TestDocuments.ToStream(prefixed, kind), kind).ToList();

AssertSameGraph(expected, actual);
TestDocuments.AssertSameGraph(expected, actual);
}
}

[TestCaseSource(nameof(TestFiles))]
[TestCaseSource(typeof(TestDocuments), nameof(TestDocuments.ComparableFiles))]
public async Task Verify_that_a_prefixed_document_is_read_the_same_as_the_original_async(string fileName)
{
var original = ReadTestFile(fileName);
var original = TestDocuments.Read(fileName);
var prefixed = CreatePrefixedCopy(original);

foreach (var kind in Kinds)
foreach (var kind in TestDocuments.Kinds)
{
var deserializer = new ReqIFDeserializer();

var expected = (await deserializer.DeserializeAsync(ToStream(original, kind), kind, CancellationToken.None)).ToList();
var actual = (await deserializer.DeserializeAsync(ToStream(prefixed, kind), kind, CancellationToken.None)).ToList();
var expected = (await deserializer.DeserializeAsync(TestDocuments.ToStream(original, kind), kind, CancellationToken.None)).ToList();
var actual = (await deserializer.DeserializeAsync(TestDocuments.ToStream(prefixed, kind), kind, CancellationToken.None)).ToList();

AssertSameGraph(expected, actual);
TestDocuments.AssertSameGraph(expected, actual);
}
}

[Test]
public void Verify_that_a_document_without_namespace_is_read_the_same_as_the_original()
{
var original = ReadTestFile("reqifsharpgenerated.reqif");
var original = TestDocuments.Read("reqifsharpgenerated.reqif");
var withoutNamespace = CreateCopyWithoutNamespace(original);

foreach (var kind in Kinds)
foreach (var kind in TestDocuments.Kinds)
{
var deserializer = new ReqIFDeserializer();

var expected = deserializer.Deserialize(ToStream(original, kind), kind).ToList();
var actual = deserializer.Deserialize(ToStream(withoutNamespace, kind), kind).ToList();
var expected = deserializer.Deserialize(TestDocuments.ToStream(original, kind), kind).ToList();
var actual = deserializer.Deserialize(TestDocuments.ToStream(withoutNamespace, kind), kind).ToList();

AssertSameGraph(expected, actual);
TestDocuments.AssertSameGraph(expected, actual);
}
}

[Test]
public async Task Verify_that_a_document_without_namespace_is_read_the_same_as_the_original_async()
{
var original = ReadTestFile("reqifsharpgenerated.reqif");
var original = TestDocuments.Read("reqifsharpgenerated.reqif");
var withoutNamespace = CreateCopyWithoutNamespace(original);

foreach (var kind in Kinds)
foreach (var kind in TestDocuments.Kinds)
{
var deserializer = new ReqIFDeserializer();

var expected = (await deserializer.DeserializeAsync(ToStream(original, kind), kind, CancellationToken.None)).ToList();
var actual = (await deserializer.DeserializeAsync(ToStream(withoutNamespace, kind), kind, CancellationToken.None)).ToList();
var expected = (await deserializer.DeserializeAsync(TestDocuments.ToStream(original, kind), kind, CancellationToken.None)).ToList();
var actual = (await deserializer.DeserializeAsync(TestDocuments.ToStream(withoutNamespace, kind), kind, CancellationToken.None)).ToList();

AssertSameGraph(expected, actual);
TestDocuments.AssertSameGraph(expected, actual);
}
}

Expand All @@ -162,7 +144,7 @@ public void Verify_that_a_root_element_in_another_namespace_is_skipped_with_a_wa
{
var deserializer = new ReqIFDeserializer(this.loggerFactory);

var reqifs = deserializer.Deserialize(ToStream(xml, SupportedFileExtensionKind.Reqif), SupportedFileExtensionKind.Reqif);
var reqifs = deserializer.Deserialize(TestDocuments.ToStream(xml, SupportedFileExtensionKind.Reqif), SupportedFileExtensionKind.Reqif);

Assert.That(reqifs, Is.Empty);
this.AssertSkippedWarningLogged();
Expand All @@ -173,7 +155,7 @@ public async Task Verify_that_a_root_element_in_another_namespace_is_skipped_wit
{
var deserializer = new ReqIFDeserializer(this.loggerFactory);

var reqifs = await deserializer.DeserializeAsync(ToStream(xml, SupportedFileExtensionKind.Reqif), SupportedFileExtensionKind.Reqif, CancellationToken.None);
var reqifs = await deserializer.DeserializeAsync(TestDocuments.ToStream(xml, SupportedFileExtensionKind.Reqif), SupportedFileExtensionKind.Reqif, CancellationToken.None);

Assert.That(reqifs, Is.Empty);
this.AssertSkippedWarningLogged();
Expand All @@ -189,11 +171,6 @@ private void AssertSkippedWarningLogged()
Assert.That(warnings[0].RenderMessage(), Does.Contain("http://example.com/not-reqif"));
}

private static string ReadTestFile(string fileName)
{
return File.ReadAllText(Path.Combine(TestContext.CurrentContext.TestDirectory, "TestData", fileName));
}

/// <summary>
/// Returns a copy of the document in which every ReqIF element is written as reqif:...
/// </summary>
Expand All @@ -204,7 +181,7 @@ private static string CreatePrefixedCopy(string original)

RemoveReqIFNamespaceDeclarations(document);

document.Root!.SetAttributeValue(XNamespace.Xmlns + "reqif", ReqIFNamespace.NamespaceName);
document.Root!.SetAttributeValue(XNamespace.Xmlns + "reqif", TestDocuments.ReqIFNamespace.NamespaceName);

var prefixed = document.ToString(SaveOptions.DisableFormatting);

Expand All @@ -223,15 +200,15 @@ private static string CreateCopyWithoutNamespace(string original)

RemoveReqIFNamespaceDeclarations(document);

foreach (var element in document.Descendants().Where(e => e.Name.Namespace == ReqIFNamespace))
foreach (var element in document.Descendants().Where(e => e.Name.Namespace == TestDocuments.ReqIFNamespace))
{
element.Name = XNamespace.None + element.Name.LocalName;
}

var withoutNamespace = document.ToString(SaveOptions.DisableFormatting);

Assert.That(withoutNamespace, Does.Contain("<REQ-IF "), "the copy must contain an unprefixed root");
Assert.That(withoutNamespace, Does.Not.Contain(ReqIFNamespace.NamespaceName), "the copy must not declare the ReqIF namespace");
Assert.That(withoutNamespace, Does.Not.Contain(TestDocuments.ReqIFNamespace.NamespaceName), "the copy must not declare the ReqIF namespace");

return withoutNamespace;
}
Expand All @@ -241,64 +218,9 @@ private static void RemoveReqIFNamespaceDeclarations(XDocument document)
foreach (var element in document.Descendants())
{
element.Attributes()
.Where(a => a.IsNamespaceDeclaration && a.Value == ReqIFNamespace.NamespaceName)
.Where(a => a.IsNamespaceDeclaration && a.Value == TestDocuments.ReqIFNamespace.NamespaceName)
.Remove();
}
}

private static MemoryStream ToStream(string xml, SupportedFileExtensionKind kind)
{
var bytes = Encoding.UTF8.GetBytes(xml);

if (kind == SupportedFileExtensionKind.Reqif)
{
return new MemoryStream(bytes);
}

var zipStream = new MemoryStream();

using (var archive = new ZipArchive(zipStream, ZipArchiveMode.Create, true))
{
using var entryStream = archive.CreateEntry("document.reqif").Open();
entryStream.Write(bytes, 0, bytes.Length);
}

zipStream.Position = 0;
return zipStream;
}

/// <summary>
/// Compares two object graphs by serializing both and comparing the XML, ignoring namespace declarations
/// (which differ by design between the original and its copy).
/// </summary>
private static void AssertSameGraph(IReadOnlyList<ReqIF> expected, IReadOnlyList<ReqIF> actual)
{
Assert.That(expected, Has.Count.EqualTo(1));
Assert.That(actual, Has.Count.EqualTo(expected.Count), "the copy must be read as the same number of documents");
Assert.That(actual[0].CoreContent.SpecObjects, Has.Count.EqualTo(expected[0].CoreContent.SpecObjects.Count));

var expectedXml = Normalize(expected);
var actualXml = Normalize(actual);

Assert.That(actualXml.ToString(), Is.EqualTo(expectedXml.ToString()));
}

private static XDocument Normalize(IEnumerable<ReqIF> reqifs)
{
using var stream = new MemoryStream();

new ReqIFSerializer().Serialize(reqifs, stream, SupportedFileExtensionKind.Reqif);

stream.Position = 0;

var document = XDocument.Load(stream);

foreach (var element in document.Descendants())
{
element.Attributes().Where(a => a.IsNamespaceDeclaration).Remove();
}

return document;
}
}
}
127 changes: 127 additions & 0 deletions ReqIFSharp.Tests/TestDocuments.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// -------------------------------------------------------------------------------------------------
// <copyright file="TestDocuments.cs" company="Starion Group S.A.">
//
// Copyright 2017-2026 Starion Group S.A.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// </copyright>
// ------------------------------------------------------------------------------------------------

namespace ReqIFSharp.Tests
{
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Xml.Linq;

using NUnit.Framework;

using ReqIFSharp;

/// <summary>
/// Helpers for tests that read a modified copy of a test document and compare it with the original.
/// </summary>
internal static class TestDocuments
{
/// <summary>
/// The ReqIF namespace
/// </summary>
public static readonly XNamespace ReqIFNamespace = DefaultXmlAttributeFactory.ReqIFSchemaUri;

/// <summary>
/// Test documents whose object graphs can be serialized again, so they can be compared.
/// output.reqif and testreqif.reqif are left out: they contain unresolved type references.
/// </summary>
public static readonly string[] ComparableFiles =
{
"Datatype-Demo.reqif",
"DefaultValueDemo.reqif",
"ProR_Traceability-Template-v1.0.reqif",
"reqifsharpgenerated.reqif"
};

/// <summary>
/// Both file kinds the deserializer reads
/// </summary>
public static readonly SupportedFileExtensionKind[] Kinds = { SupportedFileExtensionKind.Reqif, SupportedFileExtensionKind.Reqifz };

/// <summary>
/// Reads a file from the TestData folder
/// </summary>
public static string Read(string fileName)
{
return File.ReadAllText(Path.Combine(TestContext.CurrentContext.TestDirectory, "TestData", fileName));
}

/// <summary>
/// Returns the XML as a stream, either as is or as the only entry of a reqifz archive
/// </summary>
public static MemoryStream ToStream(string xml, SupportedFileExtensionKind kind)
{
var bytes = Encoding.UTF8.GetBytes(xml);

if (kind == SupportedFileExtensionKind.Reqif)
{
return new MemoryStream(bytes);
}

var zipStream = new MemoryStream();

using (var archive = new ZipArchive(zipStream, ZipArchiveMode.Create, true))
{
using var entryStream = archive.CreateEntry("document.reqif").Open();
entryStream.Write(bytes, 0, bytes.Length);
}

zipStream.Position = 0;
return zipStream;
}

/// <summary>
/// Compares two object graphs by serializing both and comparing the XML, ignoring namespace declarations
/// (which differ by design between an original and some of its copies).
/// </summary>
public static void AssertSameGraph(IReadOnlyList<ReqIF> expected, IReadOnlyList<ReqIF> actual)
{
Assert.That(expected, Has.Count.EqualTo(1));
Assert.That(actual, Has.Count.EqualTo(expected.Count), "the copy must be read as the same number of documents");
Assert.That(actual[0].CoreContent.SpecObjects, Has.Count.EqualTo(expected[0].CoreContent.SpecObjects.Count));

var expectedXml = Normalize(expected);
var actualXml = Normalize(actual);

Assert.That(actualXml.ToString(), Is.EqualTo(expectedXml.ToString()));
}

private static XDocument Normalize(IEnumerable<ReqIF> reqifs)
{
using var stream = new MemoryStream();

new ReqIFSerializer().Serialize(reqifs, stream, SupportedFileExtensionKind.Reqif);

stream.Position = 0;

var document = XDocument.Load(stream);

foreach (var element in document.Descendants())
{
element.Attributes().Where(a => a.IsNamespaceDeclaration).Remove();
}

return document;
}
}
}
Loading
Loading