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
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,21 @@ The repository ships two NuGet packages that version independently: **ReqIFSharp

### Added

- Methods to build a document in code. Each one adds the new element to its owner and sets its back-references; all except `CreateAttributeValue` also give it a valid identifier and a `LastChange` of now. So the written document is valid against the ReqIF schema (unless a relation group has values, see below) and the ReqIFSharp.Extensions queries work on it ([#85](https://github.com/STARIONGROUP/ReqIFSharp/issues/85)):
- `ReqIF.Create(title)` creates a document with a header and a content.
- `ReqIFContent.CreateDatatypeDefinitionBoolean`, `…Date`, `…Enumeration`, `…Integer`, `…Real`, `…String` and `…XHTML`, `CreateSpecObjectType`, `CreateSpecificationType`, `CreateSpecRelationType`, `CreateRelationGroupType`, `CreateSpecObject`, `CreateSpecification`, `CreateSpecRelation` and `CreateRelationGroup`.
- `DatatypeDefinitionEnumeration.CreateEnumValue` and `SpecType.CreateAttributeDefinition`, with an overload for each kind of datatype.
- `CreateAttributeDefinition`, `CreateEnumValue` and `CreateRelationGroup` require a long name, which the ReqIF specification makes mandatory for these elements; they throw when it is null or empty.
- `Specification.AddChild` and `SpecHierarchy.AddChild` add a spec object to the hierarchy.
- The methods throw an `ArgumentException` when a type, spec object, specification or datatype passed to them belongs to another document, so that a document only refers to its own elements.
- `SpecElementWithAttributes.CreateAttributeValue`, with an overload for each kind of attribute definition. It throws an `InvalidOperationException` when the element has no type, and an `ArgumentException` when the definition has no datatype, is not an attribute of the element's type, the element already has a value for it, several enumeration literals are passed for a definition that is not multi-valued, an enumeration literal does not belong to the definition's enumeration, an integer or real value is outside the `Min` and `Max` of its datatype (or a real is not a number), a string is longer than the `MaxLength` of its datatype, or the XHTML is not well-formed with a single `xhtml:div` or `xhtml:p` root element (an XML declaration is not allowed either). A null string throws an `ArgumentNullException`, as the schema requires a value; use an empty string for no text. When the XHTML uses the `xhtml` prefix without declaring it, `xmlns:xhtml` is added to its root element, so the value is complete on its own, as a value read from a document is. An XHTML value gets an `ExternalObject` for each `xhtml:object` it contains, as when it is read. An attribute value has no identifier or `LastChange`, and the `LastChange` of the element is left as it is. A relation group can have values, as the ReqIF metamodel allows, but the ReqIF XML schema has no `VALUES` on a `RELATION-GROUP`, so a document with them is not valid against the schema.
- `Identifiable.CreateIdentifier()` returns a new identifier that is a valid `xsd:ID`: an underscore followed by a GUID. `Identifiable.MarkAsChanged()` sets `LastChange` to now; call it after changing an element ([#85](https://github.com/STARIONGROUP/ReqIFSharp/issues/85)).
- `AttributeValueXHTML.ExtractNormalizedTextFromValue(bool keepLineBreaks = false)` returns the text of an XHTML value as plain text: runs of spaces, tabs and line breaks are collapsed to one space (non-breaking spaces are kept), no space is added around inline elements, and block elements such as paragraphs and list items are separated by a space or, with `keepLineBreaks`, by a line feed. The text of a `pre` element is kept as written, and HTML named entities such as ` ` are turned into their characters. `ExtractUnformattedTextFromValue` is unchanged ([#92](https://github.com/STARIONGROUP/ReqIFSharp/issues/92)).
- `AccessControlledElement.IsEditableSpecified` tells whether the `IS-EDITABLE` attribute is written. It is true when the attribute was present in the document that was read or when `IsEditable` was set; set it to false to leave out an `IS-EDITABLE` that is false ([#86](https://github.com/STARIONGROUP/ReqIFSharp/issues/86)).

### Changed

- The `TheValue` and `TheOriginalValue` of an XHTML attribute value are read as they are in the document. Before, whitespace at the start and the end was removed, so a document that was read and written again could differ ([#85](https://github.com/STARIONGROUP/ReqIFSharp/issues/85)).
- Setting `IsEditable` to false on a spec hierarchy or attribute definition now writes `IS-EDITABLE="false"`; before, the attribute was left out. Both mean that the element is read-only. Set `IsEditableSpecified` to false to leave the attribute out ([#86](https://github.com/STARIONGROUP/ReqIFSharp/issues/86)).
- The source files carry the licence in short SPDX form (`SPDX-FileCopyrightText: 2017-2026 Starion Group S.A.`, `SPDX-License-Identifier: Apache-2.0`) instead of the full Apache-2.0 notice; the licence itself is unchanged, and `NOTICE` now gives the same years, 2017-2026 ([#103](https://github.com/STARIONGROUP/ReqIFSharp/issues/103)).
- 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)).
Expand All @@ -23,6 +33,7 @@ The repository ships two NuGet packages that version independently: **ReqIFSharp

### Fixed

- An infinite real attribute value was written as `Infinity` or `-Infinity`, which is not a valid `xsd:double`, so the document failed schema validation and could not be read back. It is now written as `INF` or `-INF`, as the `MIN` and `MAX` of a real datatype already were ([#85](https://github.com/STARIONGROUP/ReqIFSharp/issues/85)).
- 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)).
- `AttributeValueXHTML.TheOriginalValue` was not read from `THE-ORIGINAL-VALUE`, so it was always `null` after reading, and a document that was read and written again lost the original values of its simplified XHTML attribute values ([#95](https://github.com/STARIONGROUP/ReqIFSharp/issues/95)).
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
// -------------------------------------------------------------------------------------------------
// <copyright file="CreatedDocumentQueriesTestFixture.cs" company="Starion Group S.A.">
//
// SPDX-FileCopyrightText: 2017-2026 Starion Group S.A.
// SPDX-License-Identifier: Apache-2.0
//
// </copyright>
// ------------------------------------------------------------------------------------------------

namespace ReqIFSharp.Extensions.Tests.ReqIFExtensions
{
using System;
using System.Linq;

using NUnit.Framework;

using ReqIFSharp;
using ReqIFSharp.Extensions.ReqIFExtensions;

/// <summary>
/// Verifies that the query extension methods work on a document built in code with the create methods of the
/// ReqIFSharp model, without setting any reference by hand
/// </summary>
[TestFixture]
public class CreatedDocumentQueriesTestFixture
{
private ReqIFContent content;

private DatatypeDefinitionString stringType;

private DatatypeDefinitionXHTML xhtmlType;

private DatatypeDefinitionEnumeration statusType;

private EnumValue approved;

private SpecObjectType requirementType;

private AttributeDefinitionString idAttribute;

private AttributeDefinitionXHTML textAttribute;

private AttributeDefinitionEnumeration statusAttribute;

private SpecificationType specificationType;

private SpecRelationType refinesType;

private RelationGroupType traceType;

private SpecObject brew;

private SpecObject heat;

private SpecObject tank;

private Specification specification;

private SpecHierarchy brewNode;

private SpecRelation relation;

private RelationGroup relationGroup;

[SetUp]
public void SetUp()
{
this.content = ReqIF.Create("Coffee machine requirements").CoreContent;

this.stringType = this.content.CreateDatatypeDefinitionString("String", 255);
this.xhtmlType = this.content.CreateDatatypeDefinitionXHTML("Formatted text");
this.statusType = this.content.CreateDatatypeDefinitionEnumeration("Status");
var draft = this.statusType.CreateEnumValue("Draft", 0, "draft");
this.approved = this.statusType.CreateEnumValue("Approved", 1, "approved");

this.requirementType = this.content.CreateSpecObjectType("Requirement");
this.idAttribute = this.requirementType.CreateAttributeDefinition(this.stringType, "ID");
this.textAttribute = this.requirementType.CreateAttributeDefinition(this.xhtmlType, "Text");
this.statusAttribute = this.requirementType.CreateAttributeDefinition(this.statusType, "Status");

this.specificationType = this.content.CreateSpecificationType("Document");
this.refinesType = this.content.CreateSpecRelationType("refines");
this.traceType = this.content.CreateRelationGroupType("trace");

SpecObject CreateRequirement(string id, EnumValue status)
{
var requirement = this.content.CreateSpecObject(this.requirementType, id);
requirement.CreateAttributeValue(this.idAttribute, id);
requirement.CreateAttributeValue(this.textAttribute, $"<xhtml:div>The text of {id}</xhtml:div>");
requirement.CreateAttributeValue(this.statusAttribute, status);
return requirement;
}

this.brew = CreateRequirement("REQ-1", this.approved);
this.heat = CreateRequirement("REQ-2", draft);
this.tank = CreateRequirement("REQ-3", draft);

this.specification = this.content.CreateSpecification(this.specificationType, "Coffee machine");
this.brewNode = this.specification.AddChild(this.brew);
this.brewNode.AddChild(this.heat);
this.specification.AddChild(this.tank);

this.relation = this.content.CreateSpecRelation(this.refinesType, this.heat, this.brew);

this.relationGroup = this.content.CreateRelationGroup(this.traceType, this.specification, this.specification, "Trace");
this.relationGroup.SpecRelations.Add(this.relation);
}

[Test]
public void Verify_that_the_type_queries_find_the_elements_of_each_type()
{
Assert.Multiple(() =>
{
Assert.That(this.requirementType.QueryReferencingSpecObjects(), Is.EquivalentTo(new[] { this.brew, this.heat, this.tank }));
Assert.That(this.specificationType.QueryReferencingSpecifications(), Is.EquivalentTo(new[] { this.specification }));
Assert.That(this.refinesType.QueryReferencingSpecRelations(), Is.EquivalentTo(new[] { this.relation }));
Assert.That(this.traceType.QueryReferencingRelationGroups(), Is.EquivalentTo(new[] { this.relationGroup }));
});
}

[Test]
public void Verify_that_the_datatype_queries_work()
{
Assert.Multiple(() =>
{
Assert.That(this.stringType.QueryReferencingAttributeDefinitions(), Is.EquivalentTo(new[] { this.idAttribute }));
Assert.That(this.statusType.QueryReferencingAttributeDefinitions(), Is.EquivalentTo(new[] { this.statusAttribute }));
Assert.That(this.stringType.QueryDatatypeName(), Is.EqualTo("String"));
Assert.That(this.idAttribute.QueryDatatypeName(), Is.EqualTo("String"));
Assert.That(this.statusAttribute.QueryDatatypeName(), Is.EqualTo("Enumeration"));
Assert.That(this.idAttribute.QueryDefaultValueAsFormattedString(), Is.EqualTo("NOT SET"));
});
}

[Test]
public void Verify_that_the_spec_object_queries_work()
{
Assert.Multiple(() =>
{
Assert.That(this.heat.QuerySpecRelations(), Is.EquivalentTo(new[] { this.relation }));
Assert.That(this.brew.QuerySpecRelations(), Is.EquivalentTo(new[] { this.relation }));
Assert.That(this.tank.QuerySpecRelations(), Is.Empty);
Assert.That(this.heat.QueryContainerSpecifications(), Is.EquivalentTo(new[] { this.specification }));
});
}

[Test]
public void Verify_that_the_specification_queries_work()
{
Assert.Multiple(() =>
{
Assert.That(this.specification.QueryAllContainedSpecObjects(), Is.EquivalentTo(new[] { this.brew, this.heat, this.tank }));
Assert.That(this.specification.QueryAllContainedSpecHierarchies().Select(x => x.Object), Is.EquivalentTo(new[] { this.brew, this.heat, this.tank }));
Assert.That(this.brewNode.QueryAllContainedSpecHierarchies().Select(x => x.Object), Is.EquivalentTo(new[] { this.heat }));
Assert.That(this.specification.QueryAttributeDefinitions(), Is.EquivalentTo(new AttributeDefinition[] { this.idAttribute, this.textAttribute, this.statusAttribute }));
});
}

[Test]
public void Verify_that_the_value_queries_work()
{
var values = this.brew.Values;

Assert.Multiple(() =>
{
Assert.That(values.Select(x => x.QueryFormattedValue()), Is.EqualTo(new[] { "REQ-1", "<xhtml:div xmlns:xhtml=\"http://www.w3.org/1999/xhtml\">The text of REQ-1</xhtml:div>", "approved" }));

Check warning on line 166 in ReqIFSharp.Extensions.Tests/ReqIFExtensions/CreatedDocumentQueriesTestFixture.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer 'static readonly' fields over constant array arguments if the called method is called repeatedly and is not mutating the passed array

See more on https://sonarcloud.io/project/issues?id=STARIONGROUP_reqifsharp&issues=AaFn-WBSqFk13z1NNMiy&open=AaFn-WBSqFk13z1NNMiy&pullRequest=109
Assert.That(this.brew.QueryExternalObjects(), Is.Empty);
Assert.That(this.content.QueryExternalObjects(), Is.Empty);
Assert.That(this.brew.QueryTypeDisplayName(), Is.EqualTo("Spec Object"));
});
}

[Test]
public void Verify_that_QueryExternalObjects_finds_an_object_in_a_created_xhtml_value()
{
var pump = this.content.CreateSpecObject(this.requirementType, "REQ-4");
pump.CreateAttributeValue(this.textAttribute, "<xhtml:div>The pump <xhtml:object data=\"files/pump.png\" type=\"image/png\"/></xhtml:div>");

Assert.Multiple(() =>
{
Assert.That(pump.QueryExternalObjects().Select(x => x.Uri), Is.EqualTo(new[] { "files/pump.png" }));

Check warning on line 181 in ReqIFSharp.Extensions.Tests/ReqIFExtensions/CreatedDocumentQueriesTestFixture.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer 'static readonly' fields over constant array arguments if the called method is called repeatedly and is not mutating the passed array

See more on https://sonarcloud.io/project/issues?id=STARIONGROUP_reqifsharp&issues=AaFn-WBSqFk13z1NNMiz&open=AaFn-WBSqFk13z1NNMiz&pullRequest=109
Assert.That(this.content.QueryExternalObjects().Select(x => x.Uri), Is.EqualTo(new[] { "files/pump.png" }));

Check warning on line 182 in ReqIFSharp.Extensions.Tests/ReqIFExtensions/CreatedDocumentQueriesTestFixture.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer 'static readonly' fields over constant array arguments if the called method is called repeatedly and is not mutating the passed array

See more on https://sonarcloud.io/project/issues?id=STARIONGROUP_reqifsharp&issues=AaFn-WBSqFk13z1NNMi0&open=AaFn-WBSqFk13z1NNMi0&pullRequest=109
});
}
}
}
Loading
Loading