369 lines
22 KiB
HTML
369 lines
22 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>About XML IO | HiAPI-C# 2025 </title>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<meta name="title" content="About XML IO | HiAPI-C# 2025 ">
|
|
|
|
|
|
<link rel="icon" href="../../img/HiAPI.favicon.ico">
|
|
<link rel="stylesheet" href="../../public/docfx.min.css">
|
|
<link rel="stylesheet" href="../../public/main.css">
|
|
<meta name="docfx:navrel" content="../../toc.html">
|
|
<meta name="docfx:tocrel" content="../toc.html">
|
|
|
|
<meta name="docfx:rel" content="../../">
|
|
|
|
|
|
|
|
<meta name="loc:inThisArticle" content="In this article">
|
|
<meta name="loc:searchResultsCount" content="{count} results for "{query}"">
|
|
<meta name="loc:searchNoResults" content="No results for "{query}"">
|
|
<meta name="loc:tocFilter" content="Filter by title">
|
|
<meta name="loc:nextArticle" content="Next">
|
|
<meta name="loc:prevArticle" content="Previous">
|
|
<meta name="loc:themeLight" content="Light">
|
|
<meta name="loc:themeDark" content="Dark">
|
|
<meta name="loc:themeAuto" content="Auto">
|
|
<meta name="loc:changeTheme" content="Change theme">
|
|
<meta name="loc:copy" content="Copy">
|
|
<meta name="loc:downloadPdf" content="Download PDF">
|
|
|
|
<script type="module" src="./../../public/docfx.min.js"></script>
|
|
|
|
<script>
|
|
const theme = localStorage.getItem('theme') || 'auto'
|
|
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
|
|
</script>
|
|
|
|
</head>
|
|
|
|
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
|
|
<header class="bg-body border-bottom">
|
|
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
|
|
<div class="container-xxl flex-nowrap">
|
|
<a class="navbar-brand" href="../../index.html">
|
|
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
|
|
|
|
</a>
|
|
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
|
|
<i class="bi bi-three-dots"></i>
|
|
</button>
|
|
<div class="collapse navbar-collapse" id="navpanel">
|
|
<div id="navbar">
|
|
<form class="search" role="search" id="search">
|
|
<i class="bi bi-search"></i>
|
|
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
</header>
|
|
|
|
<main class="container-xxl">
|
|
<div class="toc-offcanvas">
|
|
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
|
|
<div class="offcanvas-header">
|
|
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
|
|
</div>
|
|
<div class="offcanvas-body">
|
|
<nav class="toc" id="toc"></nav>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="content">
|
|
<div class="actionbar">
|
|
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
|
|
<i class="bi bi-list"></i>
|
|
</button>
|
|
|
|
<nav id="breadcrumb"></nav>
|
|
</div>
|
|
|
|
<article data-uid="">
|
|
<h1 id="about-xml-io">About XML IO</h1>
|
|
|
|
<p>The XML IO design pattern in HiNc Framework is based on <a class="xref" href="../../api/Hi.Common.XmlUtils.IMakeXmlSource.html">IMakeXmlSource</a> interface and <a class="xref" href="../../api/Hi.Common.XmlUtils.XFactory.html">XFactory</a> class. This pattern provides a standardized way to serialize and deserialize objects to and from XML format.</p>
|
|
<p>Don't serialize the runtime member object like <a class="xref" href="https://learn.microsoft.com/dotnet/api/system.func-1">Func<TResult></a> or <a class="xref" href="https://learn.microsoft.com/dotnet/api/system.action">Action</a> either cache object. The runtime objects can be optionally sent by the <code>res</code> part on the XFactory Registration or set by the other host or dependent object. If it is set by the other object, then there is nothing can do to it in the XML IO procedure.</p>
|
|
<h2 id="core-components">Core Components</h2>
|
|
<h3 id="imakexmlsource-interface">IMakeXmlSource Interface</h3>
|
|
<p>The <a class="xref" href="../../api/Hi.Common.XmlUtils.IMakeXmlSource.html">IMakeXmlSource</a> interface defines the contract for objects that can be serialized to XML format. It contains a single method <code>MakeXmlSource</code>.</p>
|
|
<h3 id="xfactory">XFactory</h3>
|
|
<p><a class="xref" href="../../api/Hi.Common.XmlUtils.XFactory.html">XFactory</a> is an instance class with a process-wide <code>Default</code> singleton (<code>XFactory.Default</code>). The instance form exists for test isolation and parallel pipelines that need disjoint generator registries; the static <code>Gen<T></code> / <code>GenByChild<T></code> / <code>GenByFile<T></code> entry points always read from <code>Default</code>.</p>
|
|
<p>Each instance owns its own <code>Generators</code> dictionary (XML element name → generator delegate). Types add themselves via a <code>Reg(factory)</code> call (see below).</p>
|
|
<h3 id="explicit-registration-via-regxfactory-factory--null">Explicit Registration via <code>Reg(XFactory factory = null)</code></h3>
|
|
<p>Every class implementing IMakeXmlSource exposes a public static <code>Reg</code> method:</p>
|
|
<pre><code class="lang-csharp">public static void Reg(XFactory factory = null)
|
|
{
|
|
factory ??= XFactory.Default;
|
|
factory.Generators.TryAdd(XName, (xml, baseDirectory, relFile, progress, res)
|
|
=> new MyClass(xml, baseDirectory, relFile, progress));
|
|
}
|
|
</code></pre>
|
|
<p>Key properties:</p>
|
|
<ul>
|
|
<li><strong>Explicit.</strong> Callers see registration happen — no hidden side effect from accessing a static member or constructing a type.</li>
|
|
<li><strong>Idempotent.</strong> Uses <code>TryAdd</code>, so the same <code>Reg</code> may be invoked any number of times from any number of boot paths.</li>
|
|
<li><strong>Composable.</strong> Custom factory instances are supported via the optional <code>factory</code> parameter; default usage (<code>MyClass.Reg();</code>) populates <code>XFactory.Default</code>.</li>
|
|
</ul>
|
|
<p>For example, see <a class="xref" href="../../api/Hi.Milling.Apts.BallApt.html">BallApt</a>:</p>
|
|
<pre><code class="lang-csharp" name="XmlRegistration">/// <summary>
|
|
/// Registers this type's deserializer with the given <see cref="XFactory"/>
|
|
/// (or <see cref="XFactory.Default"/> when <paramref name="factory"/> is
|
|
/// <c>null</c>). Idempotent.
|
|
/// </summary>
|
|
public static void Reg(XFactory factory = null)
|
|
{
|
|
factory ??= XFactory.Default;
|
|
factory.Generators.TryAdd(XName, (xml,baseDirectory, relFile, progress, res) => new BallApt(xml));
|
|
}
|
|
</code></pre><h3 id="composite-types-chain-regfactory-on-dependents">Composite types chain <code>Reg(factory)</code> on dependents</h3>
|
|
<p>When a class deserializes child elements via <code>XFactory.Gen<T></code> / <code>XFactory.GenByChild<T></code>, its <code>Reg(factory)</code> must chain <code>Reg(factory)</code> on each concrete child type so the whole dependency graph is reachable from a single root call:</p>
|
|
<pre><code class="lang-csharp">public static void Reg(XFactory factory = null)
|
|
{
|
|
factory ??= XFactory.Default;
|
|
|
|
DependentA.Reg(factory);
|
|
DependentB.Reg(factory);
|
|
|
|
factory.Generators.TryAdd(XName, (xml, baseDirectory, relFile, progress, res)
|
|
=> new MyComposite(xml, baseDirectory, relFile, progress));
|
|
}
|
|
</code></pre>
|
|
<p>For polymorphic deserialization (<code>GenByChild<IInterface></code>), the composite must chain every concrete implementation that may appear in the XML. The largest composite, <a class="xref" href="../../api/Hi.NcParsers.SoftNcRunner.html">SoftNcRunner</a>, chains roughly 130 dependents (every dependency, initializer, segmenter, syntax, and semantic the NC pipeline may deserialize).</p>
|
|
<h3 id="multi-name-registration-legacy-aliases">Multi-name registration (legacy aliases)</h3>
|
|
<p>When the XML payload may carry an old element name for backward compatibility, register the current <code>XName</code> first and group legacy aliases under a <code>//legacy aliases</code> comment:</p>
|
|
<pre><code class="lang-csharp">public static void Reg(XFactory factory = null)
|
|
{
|
|
factory ??= XFactory.Default;
|
|
|
|
XFactory.XGeneratorDelegate gen = (xml, baseDirectory, relFile, progress, res)
|
|
=> new MachiningProject(xml, baseDirectory, progress);
|
|
|
|
factory.Generators.TryAdd(XName, gen);
|
|
|
|
//legacy aliases
|
|
factory.Generators.TryAdd("MachiningCourse", gen);
|
|
factory.Generators.TryAdd("MillingCourse", gen);
|
|
}
|
|
</code></pre>
|
|
<h3 id="iprogress-threading">IProgress Threading</h3>
|
|
<p>The <code>IProgress<object></code> parameter is threaded through the entire deserialization chain. When a class constructor calls <a class="xref" href="../../api/Hi.Common.XmlUtils.XFactory.html">XFactory</a> to deserialize child objects, it passes the same <code>progress</code> instance:</p>
|
|
<pre><code class="lang-csharp">public MyClass(XElement src, string baseDirectory, string relFile,
|
|
IProgress<object> progress)
|
|
{
|
|
Child = XFactory.GenByChild<IChild>(
|
|
src.Element(nameof(Child)), subBaseDirectory, progress);
|
|
}
|
|
</code></pre>
|
|
<p>Parsing errors are reported to the caller-provided <code>IProgress<object></code> handler.</p>
|
|
<h2 id="boot-path">Boot path</h2>
|
|
<p>An application's entry point (web service <code>Program.cs</code>, WPF <code>App.xaml.cs</code>, test fixture, etc.) must call the appropriate top-level <code>Reg()</code> once at startup, before any project XML is deserialized. For the simulation pipeline this is:</p>
|
|
<pre><code class="lang-csharp">LocalProjectService.Reg();
|
|
</code></pre>
|
|
<p><code>LocalProjectService.Reg()</code> chains <code>MachiningProject.Reg()</code>, which in turn chains every type the simulation pipeline may deserialize. After this single call returns, <code>XFactory.Default.Generators</code> carries the full deserialization graph.</p>
|
|
<h2 id="implementation-patterns">Implementation Patterns</h2>
|
|
<h3 id="simple-value-objects">Simple Value Objects</h3>
|
|
<p>See <a class="xref" href="../../api/Hi.Milling.Apts.BallApt.html">BallApt</a> implementation:</p>
|
|
<pre><code class="lang-csharp" name="XmlImplementation">/// <summary>
|
|
/// Name for XML IO.
|
|
/// </summary>
|
|
public static string XName => nameof(BallApt);
|
|
/// <summary>
|
|
/// Ctor.
|
|
/// </summary>
|
|
/// <param name="src">XML</param>
|
|
public BallApt(XElement src)
|
|
{
|
|
Diameter_mm = double.Parse(src.Element("D").Value);
|
|
FluteHeight_mm = double.Parse(src.Element("FluteH").Value);
|
|
}
|
|
/// <inheritdoc/>
|
|
public XElement MakeXmlSource(string baseDirectory, string relFile, bool exhibitionOnly) => ToXElement();
|
|
|
|
/// <inheritdoc/>
|
|
public XElement ToXElement()
|
|
{
|
|
return new XElement(XName,
|
|
new XElement("D", Diameter_mm),
|
|
new XElement("FluteH", FluteHeight_mm)
|
|
);
|
|
}
|
|
</code></pre><h3 id="complex-data-structures">Complex Data Structures</h3>
|
|
<p>See <a class="xref" href="../../api/Hi.Milling.SpindleCapability.html">SpindleCapability</a> implementation:</p>
|
|
<pre><code class="lang-csharp" name="XmlImplementation">/// <summary>
|
|
/// Name for XML IO.
|
|
/// </summary>
|
|
public static string XName => nameof(SpindleCapability);
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="SpindleCapability"/> class.
|
|
/// </summary>
|
|
/// <param name="src">The XML element containing spindle data.</param>
|
|
/// <param name="baseDirectory">The base directory for resolving relative paths.</param>
|
|
/// <param name="res">Additional resolution parameters.</param>
|
|
public SpindleCapability(XElement src, string baseDirectory, params object[] res)
|
|
{
|
|
this.SetNameNote(src);
|
|
if (src.Element(nameof(EnergyEfficiency)) != null)
|
|
EnergyEfficiency = XmlConvert.ToDouble(
|
|
src.Element(nameof(EnergyEfficiency)).Value);
|
|
src.Element(nameof(WorkingTemperatureUpperBoundary_C))?.SelfInvoke(
|
|
e => WorkingTemperatureUpperBoundary_C = XmlConvert.ToDouble(e.Value));
|
|
src.Element(nameof(GearShiftSpindleSpeed_rpm))?.Value?.SelfInvoke(
|
|
s => GearShiftSpindleSpeed_rpm = string.IsNullOrEmpty(s)
|
|
? null : XmlConvert.ToDouble(s));
|
|
if (src.Element(nameof(DryRunFrictionPowerCoefficient_mWdrpm)) != null)
|
|
DryRunFrictionPowerCoefficient_mWdrpm = XmlConvert.ToDouble(
|
|
src.Element(nameof(DryRunFrictionPowerCoefficient_mWdrpm)).Value);
|
|
if (src.Element(nameof(DryRunWindagePowerCoefficient_pWdrpm3)) != null)
|
|
DryRunWindagePowerCoefficient_pWdrpm3 = XmlConvert.ToDouble(
|
|
src.Element(nameof(DryRunWindagePowerCoefficient_pWdrpm3)).Value);
|
|
|
|
if (src.Element("SpindleSpeedToPowerContours") != null) //for legacy
|
|
WorkableDurationToSpindleSpeedPowerContoursDictionary_min_cycleDs_kW =
|
|
src.Element("SpindleSpeedToPowerContours").Elements("Contour")
|
|
.ToDictionary(
|
|
contourElem =>
|
|
{
|
|
double r = XmlConvert.ToDouble(contourElem.Attribute("InsistentRatio")?.Value);
|
|
//600s=10mins
|
|
return r == 1 ? double.PositiveInfinity : (r * 600);
|
|
},
|
|
contourElem => contourElem.Elements("SpindleSpeedToPower").Select(
|
|
elem => new Vec2d(
|
|
XmlConvert.ToDouble(elem.Element("SpindleSpeed-RPM").Value) / 60,
|
|
XmlConvert.ToDouble(elem.Element("Power-kW").Value)))
|
|
.ToList());
|
|
src.Element("WorkableDurationToSpindleSpeedPowerContoursDictionary")
|
|
?.SelfInvoke(dicElem =>
|
|
{
|
|
WorkableDurationToSpindleSpeedPowerContoursDictionary_min_cycleDs_kW
|
|
= dicElem.Elements("Contour")
|
|
.ToDictionary(
|
|
contourElem => XmlConvert.ToDouble(
|
|
contourElem.Attribute("WorkableDuration-min")?.Value),
|
|
contourElem => contourElem.Elements("SpindleSpeedToPower").Select(
|
|
elem => new Vec2d(
|
|
XmlConvert.ToDouble(elem.Element("SpindleSpeed-RPM").Value) / 60,
|
|
XmlConvert.ToDouble(elem.Element("Power-kW").Value)))
|
|
.ToList());
|
|
});
|
|
|
|
if (src.Element("SpindleSpeedToTorqueContours") != null) //for legacy
|
|
WorkableDurationToSpindleSpeedTorqueContoursDictionary_min_cycleDs_Nm =
|
|
src.Element("SpindleSpeedToTorqueContours").Elements("Contour")
|
|
.ToDictionary(
|
|
contourElem =>
|
|
{
|
|
double r = XmlConvert.ToDouble(contourElem.Attribute("InsistentRatio")?.Value);
|
|
//600s=10mins
|
|
return r == 1 ? double.PositiveInfinity : (r * 600);
|
|
},
|
|
contourElem => contourElem.Elements("SpindleSpeedToTorque").Select(
|
|
elem => new Vec2d(
|
|
XmlConvert.ToDouble(elem.Element("SpindleSpeed-RPM").Value) / 60,
|
|
XmlConvert.ToDouble(elem.Element("Torque-Nm").Value)))
|
|
.ToList());
|
|
src.Element("WorkableDurationToSpindleSpeedTorqueContoursDictionary")
|
|
?.SelfInvoke(dicElem =>
|
|
{
|
|
//MessageUtil.WriteLine($"dicElem: {dicElem}");
|
|
WorkableDurationToSpindleSpeedTorqueContoursDictionary_min_cycleDs_Nm =
|
|
dicElem.Elements("Contour").ToDictionary(
|
|
contourElem => XmlConvert.ToDouble(
|
|
contourElem.Attribute("WorkableDuration-min")?.Value),
|
|
contourElem => contourElem.Elements("SpindleSpeedToTorque").Select(
|
|
elem => new Vec2d(
|
|
XmlConvert.ToDouble(elem.Element("SpindleSpeed-RPM").Value) / 60,
|
|
XmlConvert.ToDouble(elem.Element("Torque-Nm").Value)))
|
|
.ToList());
|
|
//MessageUtil.WriteLine($"keys: {string.Join(',',WorkableDurationToSpindleSpeedTorqueContoursDictionary_min_cycleDs_Nm.Select(e=>e.Key))}");
|
|
});
|
|
|
|
//for legacy compatible.
|
|
if (src.Element("SpindleSpeedToPower--RPM-to-kW") != null)
|
|
InfInsistentSpindleSpeedToPower_cycleDs_kW =
|
|
src.Element("SpindleSpeedToPower--RPM-to-kW").Elements()
|
|
.Select(elem => new Vec2d(XmlConvert.ToDouble(elem.Attribute(
|
|
"SpindleSpeed-RPM").Value) / 60,
|
|
XmlConvert.ToDouble(elem.Value))).ToList();
|
|
//for legacy compatible.
|
|
if (src.Element("SpindleSpeedToTorque--RPM-to-Nm") != null)
|
|
InfInsistentSpindleSpeedToTorque_cycleDs_Nm =
|
|
src.Element("SpindleSpeedToTorque--RPM-to-Nm").Elements()
|
|
.Select(elem => new Vec2d(XmlConvert.ToDouble(elem.Attribute(
|
|
"SpindleSpeed-RPM").Value) / 60,
|
|
XmlConvert.ToDouble(elem.Value))).ToList();
|
|
}
|
|
/// <inheritdoc/>
|
|
public XElement MakeXmlSource(string baseDirectory, string relFile, bool exhibitionOnly)
|
|
{
|
|
return new XElement(XName,
|
|
this.GetNameNoteXElementList(),
|
|
new XElement(nameof(EnergyEfficiency), EnergyEfficiency),
|
|
new XElement(nameof(GearShiftSpindleSpeed_rpm), GearShiftSpindleSpeed_rpm),
|
|
new XElement(nameof(DryRunFrictionPowerCoefficient_mWdrpm),
|
|
DryRunFrictionPowerCoefficient_mWdrpm),
|
|
new XElement(nameof(DryRunWindagePowerCoefficient_pWdrpm3),
|
|
DryRunWindagePowerCoefficient_pWdrpm3),
|
|
new XElement("WorkableDurationToSpindleSpeedPowerContoursDictionary",
|
|
WorkableDurationToSpindleSpeedPowerContoursDictionary_min_cycleDs_kW.OrderBy(entry => entry.Key)
|
|
.Select(entry => new XElement("Contour",
|
|
new XAttribute("WorkableDuration-min", entry.Key),
|
|
entry.Value.Select(entry
|
|
=> new XElement("SpindleSpeedToPower",
|
|
new XElement("SpindleSpeed-RPM", entry.X * 60),
|
|
new XElement("Power-kW", entry.Y)))))
|
|
),
|
|
new XElement("WorkableDurationToSpindleSpeedTorqueContoursDictionary",
|
|
WorkableDurationToSpindleSpeedTorqueContoursDictionary_min_cycleDs_Nm.OrderBy(entry => entry.Key)
|
|
.Select(entry => new XElement("Contour",
|
|
new XAttribute("WorkableDuration-min", entry.Key),
|
|
entry.Value.Select(entry
|
|
=> new XElement("SpindleSpeedToTorque",
|
|
new XElement("SpindleSpeed-RPM", entry.X * 60),
|
|
new XElement("Torque-Nm", entry.Y)))))
|
|
)
|
|
);
|
|
}
|
|
</code></pre><h2 id="best-practices">Best Practices</h2>
|
|
<ol>
|
|
<li><strong>XName</strong>: Always define static <code>XName</code> property matching the class name.</li>
|
|
<li><strong>Registration</strong>: Expose <code>public static void Reg(XFactory factory = null)</code>; first line is <code>factory ??= XFactory.Default;</code> then <code>factory.Generators.TryAdd(XName, …)</code>.</li>
|
|
<li><strong>Chain dependents</strong>: For every concrete type T that the ctor reads via <code>XFactory.Gen<T></code> / <code>XFactory.GenByChild<T></code>, add <code>T.Reg(factory);</code> to the chain. For polymorphic <code>GenByChild<IInterface></code>, chain every implementation that the XML may carry.</li>
|
|
<li><strong>Idempotent</strong>: Use <code>TryAdd</code>, never <code>Add</code>. The same <code>Reg</code> is called from many boot paths.</li>
|
|
<li><strong>Progress Threading</strong>: Pass the <code>IProgress<object></code> parameter through all nested <code>XFactory</code> calls. See <a href="message-management.html">Message Management</a> for the rationale.</li>
|
|
<li><strong>Legacy Support</strong>: Register the canonical <code>XName</code> first, then group aliases under a <code>//legacy aliases</code> comment.</li>
|
|
<li><strong>Derived class registration</strong>: When a derived class needs its own <code>Reg</code>, mark it <code>public new static void Reg(XFactory factory = null)</code> so the C# compiler does not warn about hiding the base method.</li>
|
|
</ol>
|
|
|
|
</article>
|
|
|
|
<div class="contribution d-print-none">
|
|
</div>
|
|
|
|
<div class="next-article d-print-none border-top" id="nextArticle"></div>
|
|
|
|
</div>
|
|
|
|
<div class="affix">
|
|
<nav id="affix"></nav>
|
|
</div>
|
|
</main>
|
|
|
|
<div class="container-xxl search-results" id="search-results"></div>
|
|
|
|
<footer class="border-top text-secondary">
|
|
<div class="container-xxl">
|
|
<div class="flex-fill">
|
|
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
|
|
</div>
|
|
</div>
|
|
</footer>
|
|
</body>
|
|
</html>
|