perf(mtconnect): stream sample-chunk parsing through XmlReader; no per-chunk document tree
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using System.Globalization;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.MTConnect;
|
||||
@@ -33,6 +34,23 @@ internal static class MTConnectXml
|
||||
public static bool IsNamed(XElement element, string localName) =>
|
||||
string.Equals(element.Name.LocalName, localName, StringComparison.Ordinal);
|
||||
|
||||
/// <summary>
|
||||
/// Does the element the reader is positioned on have the given local name, whatever its
|
||||
/// namespace?
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="MTConnectStreamsParser"/> reads its documents through a forward-only
|
||||
/// <see cref="XmlReader"/> rather than a materialised tree — one sample chunk is read once
|
||||
/// and thrown away, so building an <see cref="XDocument"/> for it is pure waste. The reader
|
||||
/// overloads here exist so that going forward-only did not fork the two silent rules in the
|
||||
/// type-level remarks: both flavours match on the local name, read attributes unqualified,
|
||||
/// and produce the same error text.
|
||||
/// </remarks>
|
||||
/// <param name="reader">A reader positioned on an element.</param>
|
||||
/// <param name="localName">The unqualified name to match.</param>
|
||||
public static bool IsNamed(XmlReader reader, string localName) =>
|
||||
string.Equals(reader.LocalName, localName, StringComparison.Ordinal);
|
||||
|
||||
/// <summary>Direct element children of <paramref name="parent"/> with the given local name.</summary>
|
||||
/// <param name="parent">The element whose children to filter.</param>
|
||||
/// <param name="localName">The unqualified name to match.</param>
|
||||
@@ -52,19 +70,46 @@ internal static class MTConnectXml
|
||||
return string.IsNullOrWhiteSpace(value) ? null : value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an unqualified attribute off the element the reader is positioned on, normalizing a
|
||||
/// present-but-empty value to <c>null</c>. See <see cref="IsNamed(XmlReader, string)"/> for
|
||||
/// why the reader overloads exist.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="XmlReader.GetAttribute(string)"/> matches on the attribute's <i>qualified</i>
|
||||
/// name, so a prefixed <c>x:dataItemId</c> does not answer to <c>dataItemId</c> — exactly
|
||||
/// what rule 2 in the type-level remarks requires, and what <see cref="XElement.Attribute"/>
|
||||
/// does for a namespace-free <see cref="XName"/>.
|
||||
/// </remarks>
|
||||
/// <param name="reader">A reader positioned on an element.</param>
|
||||
/// <param name="name">The unqualified attribute name.</param>
|
||||
public static string? OptionalAttribute(XmlReader reader, string name)
|
||||
{
|
||||
var value = reader.GetAttribute(name);
|
||||
|
||||
return string.IsNullOrWhiteSpace(value) ? null : value;
|
||||
}
|
||||
|
||||
/// <summary>Reads a required unqualified attribute, or throws naming the owner and the attribute.</summary>
|
||||
/// <param name="element">The element carrying the attribute.</param>
|
||||
/// <param name="name">The unqualified attribute name.</param>
|
||||
/// <param name="owner">How to describe the element in the error message.</param>
|
||||
/// <param name="subject">How to describe the document in the error message (e.g. "/probe response").</param>
|
||||
/// <exception cref="InvalidDataException">The attribute is absent or blank.</exception>
|
||||
public static string RequiredAttribute(XElement element, string name, string owner, string subject)
|
||||
{
|
||||
var value = OptionalAttribute(element, name);
|
||||
public static string RequiredAttribute(XElement element, string name, string owner, string subject) =>
|
||||
OptionalAttribute(element, name) ?? throw MissingAttribute(name, owner, subject);
|
||||
|
||||
return value ?? throw new InvalidDataException(
|
||||
$"MTConnect {subject} is malformed: {owner} has no '{name}' attribute.");
|
||||
}
|
||||
/// <summary>
|
||||
/// Reads a required unqualified attribute off the element the reader is positioned on, or
|
||||
/// throws naming the owner and the attribute.
|
||||
/// </summary>
|
||||
/// <param name="reader">A reader positioned on an element.</param>
|
||||
/// <param name="name">The unqualified attribute name.</param>
|
||||
/// <param name="owner">How to describe the element in the error message.</param>
|
||||
/// <param name="subject">How to describe the document in the error message.</param>
|
||||
/// <exception cref="InvalidDataException">The attribute is absent or blank.</exception>
|
||||
public static string RequiredAttribute(XmlReader reader, string name, string owner, string subject) =>
|
||||
OptionalAttribute(reader, name) ?? throw MissingAttribute(name, owner, subject);
|
||||
|
||||
/// <summary>Reads an optional unqualified attribute as an <see cref="int"/>.</summary>
|
||||
/// <param name="element">The element carrying the attribute.</param>
|
||||
@@ -95,10 +140,28 @@ internal static class MTConnectXml
|
||||
/// <param name="owner">How to describe the element in the error message.</param>
|
||||
/// <param name="subject">How to describe the document in the error message.</param>
|
||||
/// <exception cref="InvalidDataException">The attribute is absent, blank, or not an integer.</exception>
|
||||
public static long RequiredLongAttribute(XElement element, string name, string owner, string subject)
|
||||
{
|
||||
var raw = RequiredAttribute(element, name, owner, subject);
|
||||
public static long RequiredLongAttribute(XElement element, string name, string owner, string subject) =>
|
||||
AsLong(RequiredAttribute(element, name, owner, subject), name, owner, subject);
|
||||
|
||||
/// <summary>
|
||||
/// Reads a required unqualified attribute off the element the reader is positioned on, as a
|
||||
/// <see cref="long"/>.
|
||||
/// </summary>
|
||||
/// <param name="reader">A reader positioned on an element.</param>
|
||||
/// <param name="name">The unqualified attribute name.</param>
|
||||
/// <param name="owner">How to describe the element in the error message.</param>
|
||||
/// <param name="subject">How to describe the document in the error message.</param>
|
||||
/// <exception cref="InvalidDataException">The attribute is absent, blank, or not an integer.</exception>
|
||||
public static long RequiredLongAttribute(XmlReader reader, string name, string owner, string subject) =>
|
||||
AsLong(RequiredAttribute(reader, name, owner, subject), name, owner, subject);
|
||||
|
||||
/// <summary>The one spelling of "that attribute is missing", shared by both flavours.</summary>
|
||||
private static InvalidDataException MissingAttribute(string name, string owner, string subject) =>
|
||||
new($"MTConnect {subject} is malformed: {owner} has no '{name}' attribute.");
|
||||
|
||||
/// <summary>The one spelling of "that attribute is not a number", shared by both flavours.</summary>
|
||||
private static long AsLong(string raw, string name, string owner, string subject)
|
||||
{
|
||||
if (!long.TryParse(raw, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value))
|
||||
{
|
||||
throw new InvalidDataException(
|
||||
|
||||
Reference in New Issue
Block a user