From 06f77024434f97f81ec3165413e466be5c986a53 Mon Sep 17 00:00:00 2001 From: samatstarion Date: Mon, 19 Oct 2026 07:31:26 +0200 Subject: [PATCH] [Fix] read THE-ORIGINAL-VALUE of XHTML attribute values; fixes #95 AttributeValueXHTML.ReadXml and ReadXmlAsync never read THE-ORIGINAL-VALUE, so TheOriginalValue was always null after reading, and a document that was read and written again lost the original values of its simplified XHTML attribute values. - Read only the direct children (DEFINITION, THE-VALUE, THE-ORIGINAL-VALUE) in any order. ReadInnerXml and Skip already move to the next node, so the loop does not call Read after them and no sibling is skipped in minified documents (see #94). Other children are skipped. - Read the definition reference through a subtree of DEFINITION; an unknown reference still throws. - Add AttributeValueXHTMLReadTestFixture: original values read from the test data, kept on read-write-read, and four child orders (also with an unknown child) read correctly, indented and minified, reqif and reqifz, sync and async. Add a cancellation test for ReadXmlAsync. - Move the minify helper into TestDocuments. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 1 + .../AttributeValueXHTMLReadTestFixture.cs | 235 ++++++++++++++++++ .../AttributeValueXHTMLTestFixture.cs | 21 ++ .../MinifiedDocumentTestFixture.cs | 42 +--- ReqIFSharp.Tests/TestDocuments.cs | 29 +++ .../AttributeValue/AttributeValueXHTML.cs | 155 +++++++++--- 6 files changed, 418 insertions(+), 65 deletions(-) create mode 100644 ReqIFSharp.Tests/AttributeValueTests/AttributeValueXHTMLReadTestFixture.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ea700e..7eebd8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ The repository ships two NuGet packages that version independently: **ReqIFSharp - A document that binds the ReqIF namespace to a prefix (``) 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)). ## [10.1.2] - 2026-10-18 diff --git a/ReqIFSharp.Tests/AttributeValueTests/AttributeValueXHTMLReadTestFixture.cs b/ReqIFSharp.Tests/AttributeValueTests/AttributeValueXHTMLReadTestFixture.cs new file mode 100644 index 0000000..e701f61 --- /dev/null +++ b/ReqIFSharp.Tests/AttributeValueTests/AttributeValueXHTMLReadTestFixture.cs @@ -0,0 +1,235 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// 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. +// +// +// ------------------------------------------------------------------------------------------------ + +namespace ReqIFSharp.Tests +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Text.RegularExpressions; + using System.Threading; + using System.Threading.Tasks; + using System.Xml.Linq; + + using NUnit.Framework; + + using ReqIFSharp; + + /// + /// Verifies that reads THE-VALUE, THE-ORIGINAL-VALUE and DEFINITION + /// in any order, in indented and minified documents, see GH-95. + /// + [TestFixture] + public class AttributeValueXHTMLReadTestFixture + { + private const string FileName = "reqifsharpgenerated.reqif"; + + private static readonly XNamespace Xhtml = "http://www.w3.org/1999/xhtml"; + + private static readonly object[] ChildOrders = + { + new object[] { new[] { "DEFINITION", "THE-VALUE", "THE-ORIGINAL-VALUE" } }, + new object[] { new[] { "DEFINITION", "THE-ORIGINAL-VALUE", "THE-VALUE" } }, + new object[] { new[] { "THE-VALUE", "THE-ORIGINAL-VALUE", "DEFINITION" } }, + new object[] { new[] { "THE-ORIGINAL-VALUE", "THE-VALUE", "DEFINITION" } } + }; + + /// + /// Orders with an element that is not part of the schema. It contains a THE-VALUE and a DEFINITION with an + /// unknown reference: reading either would change the value or throw. + /// + private static readonly object[] ChildOrdersWithUnknownElement = + { + new object[] { new[] { "UNKNOWN", "DEFINITION", "THE-VALUE", "THE-ORIGINAL-VALUE" } }, + new object[] { new[] { "DEFINITION", "THE-VALUE", "UNKNOWN", "THE-ORIGINAL-VALUE" } } + }; + + [TestCase(false)] + [TestCase(true)] + public async Task Verify_that_the_original_value_is_read(bool async) + { + var original = TestDocuments.Read(FileName); + var expectedCount = XDocument.Parse(original).Descendants(TestDocuments.ReqIFNamespace + "THE-ORIGINAL-VALUE").Count(); + + Assert.That(expectedCount, Is.GreaterThan(0), "the test document must contain THE-ORIGINAL-VALUE elements"); + + var values = XhtmlValues(await Deserialize(original, SupportedFileExtensionKind.Reqif, async)); + var withOriginal = values.Where(x => x.TheOriginalValue != null).ToList(); + + Assert.Multiple(() => + { + Assert.That(withOriginal, Has.Count.EqualTo(expectedCount)); + Assert.That(withOriginal.Select(x => x.TheOriginalValue), Has.All.StartWith(" x.IsSimplified), Has.All.True); + }); + } + + [TestCase(false)] + [TestCase(true)] + public async Task Verify_that_the_original_value_is_kept_when_a_document_is_read_written_and_read_again(bool async) + { + var first = await Deserialize(TestDocuments.Read(FileName), SupportedFileExtensionKind.Reqif, async); + + string written; + + using (var stream = new MemoryStream()) + { + var serializer = new ReqIFSerializer(); + + if (async) + { + await serializer.SerializeAsync(first, stream, SupportedFileExtensionKind.Reqif, CancellationToken.None); + } + else + { + serializer.Serialize(first, stream, SupportedFileExtensionKind.Reqif); + } + + stream.Position = 0; + written = new StreamReader(stream).ReadToEnd(); + } + + var second = await Deserialize(written, SupportedFileExtensionKind.Reqif, async); + + var expected = XhtmlValues(first).Select(x => (x.IsSimplified, x.TheOriginalValue)).ToList(); + var actual = XhtmlValues(second).Select(x => (x.IsSimplified, x.TheOriginalValue)).ToList(); + + Assert.That(expected.Count(x => x.TheOriginalValue != null), Is.GreaterThan(0)); + Assert.That(actual, Is.EqualTo(expected)); + } + + [TestCaseSource(nameof(ChildOrders))] + [TestCaseSource(nameof(ChildOrdersWithUnknownElement))] + public async Task Verify_that_the_children_are_read_in_any_order(string[] order) + { + var (xml, expected) = CreateDocumentWithChildOrder(order); + + foreach (var document in new[] { xml, TestDocuments.Minify(xml) }) + { + foreach (var kind in TestDocuments.Kinds) + { + foreach (var async in new[] { false, true }) + { + var values = XhtmlValues(await Deserialize(document, kind, async)); + + var actual = values + .Select(x => (Definition: x.Definition?.Identifier, Value: Marker(x.TheValue, "value"), Original: Marker(x.TheOriginalValue, "original"))) + .ToList(); + + Assert.That(actual, Is.EquivalentTo(expected), $"order {string.Join(", ", order)}, {kind}, async: {async}, minified: {document != xml}"); + } + } + } + } + + [Test] + public void Verify_that_an_unknown_definition_reference_throws() + { + var ns = TestDocuments.ReqIFNamespace; + var document = XDocument.Parse(TestDocuments.Read(FileName)); + + document.Descendants(ns + "ATTRIBUTE-VALUE-XHTML").First() + .Element(ns + "DEFINITION")! + .Element(ns + "ATTRIBUTE-DEFINITION-XHTML-REF")!.Value = "unknown-xhtml-definition"; + + var xml = document.ToString(); + + foreach (var copy in new[] { xml, TestDocuments.Minify(xml) }) + { + foreach (var kind in TestDocuments.Kinds) + { + foreach (var async in new[] { false, true }) + { + Assert.That(async () => await Deserialize(copy, kind, async), + Throws.InstanceOf().With.Message.Contains("The attribute-definition XHTML unknown-xhtml-definition could not be found"), + $"{kind}, async: {async}, minified: {copy != xml}"); + } + } + } + } + + /// + /// Returns a copy of the test document in which every XHTML value has a THE-VALUE "value i" and a + /// THE-ORIGINAL-VALUE "original i", with its children in the given order, and the values expected after reading it. + /// + private static (string Xml, List<(string Definition, string Value, string Original)> Expected) CreateDocumentWithChildOrder(string[] order) + { + var ns = TestDocuments.ReqIFNamespace; + var document = XDocument.Parse(TestDocuments.Read(FileName)); + var expected = new List<(string Definition, string Value, string Original)>(); + + var index = 0; + + foreach (var attributeValue in document.Descendants(ns + "ATTRIBUTE-VALUE-XHTML").ToList()) + { + var definition = attributeValue.Element(ns + "DEFINITION"); + var definitionReference = definition!.Element(ns + "ATTRIBUTE-DEFINITION-XHTML-REF")!.Value; + + var children = new Dictionary + { + ["DEFINITION"] = definition, + ["THE-VALUE"] = new XElement(ns + "THE-VALUE", new XElement(Xhtml + "p", $"value {index}")), + ["THE-ORIGINAL-VALUE"] = new XElement(ns + "THE-ORIGINAL-VALUE", new XElement(Xhtml + "p", $"original {index}")), + ["UNKNOWN"] = new XElement(ns + "UNKNOWN", + new XElement(ns + "THE-VALUE", new XElement(Xhtml + "p", "value of the unknown element")), + new XElement(ns + "DEFINITION", new XElement(ns + "ATTRIBUTE-DEFINITION-XHTML-REF", "unknown-xhtml-definition"))) + }; + + attributeValue.RemoveNodes(); + attributeValue.Add(order.Select(name => children[name])); + + expected.Add((definitionReference, $"value {index}", $"original {index}")); + index++; + } + + Assert.That(expected, Is.Not.Empty, "the test document must contain XHTML values"); + + return (document.ToString(), expected); + } + + private static string Marker(string xhtml, string word) + { + return xhtml == null ? null : Regex.Match(xhtml, $@"{word} \d+").Value; + } + + private static List XhtmlValues(IEnumerable reqifs) + { + var content = reqifs.Single().CoreContent; + + return content.SpecObjects.SelectMany(x => x.Values) + .Concat(content.SpecRelations.SelectMany(x => x.Values)) + .Concat(content.Specifications.SelectMany(x => x.Values)) + .Concat(content.SpecRelationGroups.SelectMany(x => x.Values)) + .OfType() + .ToList(); + } + + private static async Task> Deserialize(string xml, SupportedFileExtensionKind kind, bool async) + { + var deserializer = new ReqIFDeserializer(); + using var stream = TestDocuments.ToStream(xml, kind); + + return async + ? (await deserializer.DeserializeAsync(stream, kind, CancellationToken.None)).ToList() + : deserializer.Deserialize(stream, kind).ToList(); + } + } +} diff --git a/ReqIFSharp.Tests/AttributeValueTests/AttributeValueXHTMLTestFixture.cs b/ReqIFSharp.Tests/AttributeValueTests/AttributeValueXHTMLTestFixture.cs index 7872e63..e12a00f 100644 --- a/ReqIFSharp.Tests/AttributeValueTests/AttributeValueXHTMLTestFixture.cs +++ b/ReqIFSharp.Tests/AttributeValueTests/AttributeValueXHTMLTestFixture.cs @@ -128,6 +128,27 @@ public void Verify_That_WriteXmlAsync_Without_Definition_Set_Throws_Serializatio Throws.Exception.TypeOf()); } + [Test] + public void Verify_that_ReadXmlAsync_throws_exception_when_cancelled() + { + const string xml = "" + + "text" + + ""; + + var cts = new CancellationTokenSource(); + cts.Cancel(); + + using var xmlReader = XmlReader.Create(new StringReader(xml), new XmlReaderSettings { Async = true }); + xmlReader.MoveToContent(); + + var attributeValueXhtml = new AttributeValueXHTML(this.loggerFactory); + + Assert.That(async () => await attributeValueXhtml.ReadXmlAsync(xmlReader, cts.Token), + Throws.Exception.InstanceOf()); + + Assert.That(attributeValueXhtml.TheValue, Is.Null, "nothing must be read after the cancellation"); + } + [Test] public void Verify_That_WriteXmlAsync_Throws_Exception_when_cancelled() { diff --git a/ReqIFSharp.Tests/MinifiedDocumentTestFixture.cs b/ReqIFSharp.Tests/MinifiedDocumentTestFixture.cs index 4ee5856..9a0a90d 100644 --- a/ReqIFSharp.Tests/MinifiedDocumentTestFixture.cs +++ b/ReqIFSharp.Tests/MinifiedDocumentTestFixture.cs @@ -96,7 +96,7 @@ public async Task Verify_that_every_header_field_of_a_minified_document_is_read_ public void Verify_that_a_minified_document_is_read_the_same_as_the_original(string fileName) { var original = TestDocuments.Read(fileName); - var minified = CreateMinifiedCopy(original); + var minified = TestDocuments.Minify(original); foreach (var kind in TestDocuments.Kinds) { @@ -113,7 +113,7 @@ public void Verify_that_a_minified_document_is_read_the_same_as_the_original(str public async Task Verify_that_a_minified_document_is_read_the_same_as_the_original_async(string fileName) { var original = TestDocuments.Read(fileName); - var minified = CreateMinifiedCopy(original); + var minified = TestDocuments.Minify(original); foreach (var kind in TestDocuments.Kinds) { @@ -130,7 +130,7 @@ public async Task Verify_that_a_minified_document_is_read_the_same_as_the_origin public void Verify_that_the_spec_relations_of_a_relation_group_in_a_minified_document_are_read() { var original = CreateDocumentWithSpecRelationsInRelationGroup(); - var minified = CreateMinifiedCopy(original); + var minified = TestDocuments.Minify(original); foreach (var kind in TestDocuments.Kinds) { @@ -149,7 +149,7 @@ public void Verify_that_the_spec_relations_of_a_relation_group_in_a_minified_doc public async Task Verify_that_the_spec_relations_of_a_relation_group_in_a_minified_document_are_read_async() { var original = CreateDocumentWithSpecRelationsInRelationGroup(); - var minified = CreateMinifiedCopy(original); + var minified = TestDocuments.Minify(original); foreach (var kind in TestDocuments.Kinds) { @@ -188,7 +188,7 @@ public async Task Verify_that_the_elements_after_the_spec_relations_of_a_relatio { var original = CreateDocumentWithSpecRelationsInRelationGroup(insertBefore: "TYPE"); - foreach (var xml in new[] { original, CreateMinifiedCopy(original) }) + foreach (var xml in new[] { original, TestDocuments.Minify(original) }) { foreach (var kind in TestDocuments.Kinds) { @@ -213,7 +213,7 @@ public async Task Verify_that_an_empty_spec_relations_element_of_a_relation_grou Assert.That(original, Does.Contain(""), "the document must contain an empty SPEC-RELATIONS element"); - foreach (var xml in new[] { original, CreateMinifiedCopy(original) }) + foreach (var xml in new[] { original, TestDocuments.Minify(original) }) { foreach (var kind in TestDocuments.Kinds) { @@ -240,7 +240,7 @@ public async Task Verify_that_an_unsupported_child_of_spec_relations_is_skipped_ var original = CreateDocumentWithSpecRelationsInRelationGroup(insertBefore: "TYPE", unsupportedChild: unsupported); - foreach (var xml in new[] { original, CreateMinifiedCopy(original) }) + foreach (var xml in new[] { original, TestDocuments.Minify(original) }) { foreach (var kind in TestDocuments.Kinds) { @@ -341,33 +341,5 @@ private static void AssertHeader(ReqIFHeader header) Assert.That(header.Title, Is.EqualTo("a title")); }); } - - /// - /// Returns a copy of the document without the whitespace between its ReqIF elements. Whitespace inside - /// XHTML values and tool extensions is kept: it is content, read as raw XML. - /// - private static string CreateMinifiedCopy(string original) - { - var document = XDocument.Parse(original, LoadOptions.PreserveWhitespace); - - var whitespace = document.DescendantNodes() - .OfType() - .Where(t => string.IsNullOrWhiteSpace(t.Value) && !IsRawXmlContent(t)) - .ToList(); - - Assert.That(whitespace, Is.Not.Empty, "the original must contain whitespace between elements"); - - whitespace.ForEach(t => t.Remove()); - - return document.ToString(SaveOptions.DisableFormatting); - } - - private static bool IsRawXmlContent(XText text) - { - return text.Ancestors().Any(a => - a.Name.Namespace != TestDocuments.ReqIFNamespace - || a.Name.LocalName == "THE-VALUE" - || a.Name.LocalName == "REQ-IF-TOOL-EXTENSION"); - } } } diff --git a/ReqIFSharp.Tests/TestDocuments.cs b/ReqIFSharp.Tests/TestDocuments.cs index 3bd814a..9918f92 100644 --- a/ReqIFSharp.Tests/TestDocuments.cs +++ b/ReqIFSharp.Tests/TestDocuments.cs @@ -90,6 +90,35 @@ public static MemoryStream ToStream(string xml, SupportedFileExtensionKind kind) return zipStream; } + /// + /// Returns a copy of the document without the whitespace between its ReqIF elements. Whitespace inside + /// XHTML values (THE-VALUE, THE-ORIGINAL-VALUE) and tool extensions is kept: it is content, read as raw XML. + /// + public static string Minify(string original) + { + var document = XDocument.Parse(original, LoadOptions.PreserveWhitespace); + + var whitespace = document.DescendantNodes() + .OfType() + .Where(t => string.IsNullOrWhiteSpace(t.Value) && !IsRawXmlContent(t)) + .ToList(); + + Assert.That(whitespace, Is.Not.Empty, "the original must contain whitespace between elements"); + + whitespace.ForEach(t => t.Remove()); + + return document.ToString(SaveOptions.DisableFormatting); + } + + private static bool IsRawXmlContent(XText text) + { + return text.Ancestors().Any(a => + a.Name.Namespace != ReqIFNamespace + || a.Name.LocalName == "THE-VALUE" + || a.Name.LocalName == "THE-ORIGINAL-VALUE" + || a.Name.LocalName == "REQ-IF-TOOL-EXTENSION"); + } + /// /// 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). diff --git a/ReqIFSharp/AttributeValue/AttributeValueXHTML.cs b/ReqIFSharp/AttributeValue/AttributeValueXHTML.cs index 3837cad..7981182 100644 --- a/ReqIFSharp/AttributeValue/AttributeValueXHTML.cs +++ b/ReqIFSharp/AttributeValue/AttributeValueXHTML.cs @@ -232,33 +232,55 @@ public string ExtractUnformattedTextFromValue() /// /// an instance of /// + /// + /// Only the direct children (DEFINITION, THE-VALUE and THE-ORIGINAL-VALUE) are read, in any order. + /// and already move the reader to the + /// next node, so the loop does not call after them; otherwise the next sibling + /// would be skipped when there is no whitespace between the elements. + /// internal override void ReadXml(XmlReader reader) { this.ReadXmlAttributes(reader); using (var subtree = reader.ReadSubtree()) { - while (subtree.Read()) + subtree.MoveToContent(); + subtree.Read(); + + while (!subtree.EOF) { - if (subtree.MoveToContent() == XmlNodeType.Element && reader.LocalName == "ATTRIBUTE-DEFINITION-XHTML-REF") + if (subtree.NodeType != XmlNodeType.Element || subtree.Depth != 1) { - var reference = reader.ReadElementContentAsString(); - this.Definition = this.ReqIFContent.SpecTypes.SelectMany(x => x.SpecAttributes).OfType().SingleOrDefault(x => x.Identifier == reference); - if (this.Definition == null) - { - throw new InvalidOperationException($"The attribute-definition XHTML {reference} could not be found for the value."); - } + subtree.Read(); + continue; } - if (subtree.MoveToContent() == XmlNodeType.Element && reader.LocalName == "THE-VALUE") + switch (subtree.LocalName) { - this.TheValue = subtree.ReadInnerXml().Trim(); + case "DEFINITION": + using (var definitionSubtree = subtree.ReadSubtree()) + { + this.ReadDefinition(definitionSubtree); + } - if (this.TheValue.Contains("xhtml:object") && this.TheValue.Contains("data")) - { - var externalObjects = this.CreateExternalObjects(this.TheValue); - this.ExternalObjects.AddRange(externalObjects); - } + subtree.Read(); + break; + case "THE-VALUE": + this.TheValue = subtree.ReadInnerXml().Trim(); + + if (this.TheValue.Contains("xhtml:object") && this.TheValue.Contains("data")) + { + var externalObjects = this.CreateExternalObjects(this.TheValue); + this.ExternalObjects.AddRange(externalObjects); + } + + break; + case "THE-ORIGINAL-VALUE": + this.TheOriginalValue = subtree.ReadInnerXml().Trim(); + break; + default: + subtree.Skip(); + break; } } } @@ -273,40 +295,113 @@ internal override void ReadXml(XmlReader reader) /// /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. /// + /// + /// Only the direct children (DEFINITION, THE-VALUE and THE-ORIGINAL-VALUE) are read, in any order. + /// and already move the reader to the + /// next node, so the loop does not call after them; otherwise the next sibling + /// would be skipped when there is no whitespace between the elements. + /// internal override async Task ReadXmlAsync(XmlReader reader, CancellationToken token) { this.ReadXmlAttributes(reader); using (var subtree = reader.ReadSubtree()) { - while (await subtree.ReadAsync()) + await subtree.MoveToContentAsync(); + await subtree.ReadAsync(); + + while (!subtree.EOF) { token.ThrowIfCancellationRequested(); - if (await subtree.MoveToContentAsync() == XmlNodeType.Element && reader.LocalName == "ATTRIBUTE-DEFINITION-XHTML-REF") + if (subtree.NodeType != XmlNodeType.Element || subtree.Depth != 1) { - var reference = await reader.ReadElementContentAsStringAsync(); - this.Definition = this.ReqIFContent.SpecTypes.SelectMany(x => x.SpecAttributes).OfType().SingleOrDefault(x => x.Identifier == reference); - if (this.Definition == null) - { - throw new InvalidOperationException($"The attribute-definition XHTML {reference} could not be found for the value."); - } + await subtree.ReadAsync(); + continue; } - if (await subtree.MoveToContentAsync() == XmlNodeType.Element && reader.LocalName == "THE-VALUE") + switch (subtree.LocalName) { - this.TheValue = (await subtree.ReadInnerXmlAsync()).Trim(); + case "DEFINITION": + using (var definitionSubtree = subtree.ReadSubtree()) + { + await this.ReadDefinitionAsync(definitionSubtree); + } - if (this.TheValue.Contains("xhtml:object") && this.TheValue.Contains("data")) - { - var externalObjects = await this.CreateExternalObjectsAsync(this.TheValue); - this.ExternalObjects.AddRange(externalObjects); - } + await subtree.ReadAsync(); + break; + case "THE-VALUE": + this.TheValue = (await subtree.ReadInnerXmlAsync()).Trim(); + + if (this.TheValue.Contains("xhtml:object") && this.TheValue.Contains("data")) + { + var externalObjects = await this.CreateExternalObjectsAsync(this.TheValue); + this.ExternalObjects.AddRange(externalObjects); + } + + break; + case "THE-ORIGINAL-VALUE": + this.TheOriginalValue = (await subtree.ReadInnerXmlAsync()).Trim(); + break; + default: + await subtree.SkipAsync(); + break; } } } } + /// + /// Reads the referenced by the DEFINITION element + /// + /// + /// an over the subtree of the DEFINITION element + /// + private void ReadDefinition(XmlReader reader) + { + reader.MoveToContent(); + + if (reader.ReadToDescendant("ATTRIBUTE-DEFINITION-XHTML-REF", reader.NamespaceURI)) + { + this.SetDefinition(reader.ReadElementContentAsString()); + } + } + + /// + /// Asynchronously reads the referenced by the DEFINITION element + /// + /// + /// an over the subtree of the DEFINITION element + /// + private async Task ReadDefinitionAsync(XmlReader reader) + { + await reader.MoveToContentAsync(); + + if (reader.ReadToDescendant("ATTRIBUTE-DEFINITION-XHTML-REF", reader.NamespaceURI)) + { + this.SetDefinition(await reader.ReadElementContentAsStringAsync()); + } + } + + /// + /// Sets the to the with the referenced identifier + /// + /// + /// The identifier of the + /// + /// + /// The could not be found + /// + private void SetDefinition(string reference) + { + this.Definition = this.ReqIFContent.SpecTypes.SelectMany(x => x.SpecAttributes).OfType().SingleOrDefault(x => x.Identifier == reference); + + if (this.Definition == null) + { + throw new InvalidOperationException($"The attribute-definition XHTML {reference} could not be found for the value."); + } + } + /// /// Reads the properties that are defined as XML Attributes (IS-SIMPLIFIED) ///