Files
MicrosoftIris/UIXrender/Subsystems/Xml/XmlLiteReader.cs
T
2026-07-22 18:34:57 -05:00

95 lines
3.9 KiB
C#

using System;
using System.IO;
using System.Xml;
using Microsoft.Iris.Render.Interop.XmlLite;
namespace Microsoft.Iris.Render.Subsystems.Xml;
// Backs the SpXmlLite* family (UIX/Microsoft/Iris/OS/NativeApi.cs). The original was a
// thin wrapper over Windows' XmlLite COM reader; this is the same *contract* over
// System.Xml.XmlReader, which is in-box, cross-platform and behaviourally equivalent for
// the pull-parsing subset the surface exposes (read a node, inspect name/prefix/value,
// walk attributes, report line/position).
//
// One real semantic difference to be aware of and handled here, not papered over: XmlLite
// reports attributes as nodes you move to with MoveToFirst/NextAttribute and then read
// via the *same* GetLocalName/GetValue accessors, and System.Xml.XmlReader works exactly
// that way too -- so the mapping is direct. What is *not* direct is IsEmptyElement, which
// XmlReader only reports on the element node itself; it's captured on each read rather
// than queried lazily, so it stays correct after moving to an attribute.
internal sealed class XmlLiteReader : IDisposable
{
private readonly XmlReader _reader;
private readonly IXmlLineInfo _lineInfo;
private XmlLiteReader(XmlReader reader)
{
_reader = reader;
_lineInfo = reader as IXmlLineInfo;
}
public bool IsEmptyElement { get; private set; }
public static XmlLiteReader Create(string text, bool isFragment)
{
var settings = new XmlReaderSettings
{
// A "fragment" has no single root element; ConformanceLevel.Fragment is
// exactly XmlLite's isFragment flag.
ConformanceLevel = isFragment ? ConformanceLevel.Fragment : ConformanceLevel.Document,
DtdProcessing = DtdProcessing.Ignore,
IgnoreWhitespace = false,
CloseInput = true,
};
return new XmlLiteReader(XmlReader.Create(new StringReader(text), settings));
}
public bool Read(out NativeXmlNodeType nodeType)
{
if (!_reader.Read())
{
nodeType = NativeXmlNodeType.None;
IsEmptyElement = false;
return false;
}
IsEmptyElement = _reader.NodeType == XmlNodeType.Element && _reader.IsEmptyElement;
nodeType = Map(_reader.NodeType);
return true;
}
public bool MoveToFirstAttribute() => _reader.MoveToFirstAttribute();
public bool MoveToNextAttribute() => _reader.MoveToNextAttribute();
public string LocalName => _reader.LocalName;
public string Prefix => _reader.Prefix;
public string QualifiedName => _reader.Name;
public string Value => _reader.Value;
public uint LineNumber => (uint)(_lineInfo?.LineNumber ?? 0);
public uint LinePosition => (uint)(_lineInfo?.LinePosition ?? 0);
// NativeXmlNodeType's values are System.Xml.XmlNodeType's own numbering minus the
// members Iris doesn't use (verified against UIX/Microsoft/Iris/OS/NativeXmlNodeType.cs),
// so this could be a cast -- it's written out so an unmapped node type degrades to
// None instead of producing a value the managed side has no case for.
private static NativeXmlNodeType Map(XmlNodeType type) => type switch
{
XmlNodeType.Element => NativeXmlNodeType.Element,
XmlNodeType.Attribute => NativeXmlNodeType.Attribute,
XmlNodeType.Text => NativeXmlNodeType.Text,
XmlNodeType.CDATA => NativeXmlNodeType.CDATA,
XmlNodeType.ProcessingInstruction => NativeXmlNodeType.ProcessingInstruction,
XmlNodeType.Comment => NativeXmlNodeType.Comment,
XmlNodeType.DocumentType => NativeXmlNodeType.DocumentType,
XmlNodeType.Whitespace or XmlNodeType.SignificantWhitespace => NativeXmlNodeType.Whitespace,
XmlNodeType.EndElement => NativeXmlNodeType.EndElement,
XmlNodeType.XmlDeclaration => NativeXmlNodeType.XmlDeclaration,
_ => NativeXmlNodeType.None,
};
public void Dispose() => _reader.Dispose();
}