Table of Contents

About XML IO

The XML IO design pattern in HiNc Framework is based on IMakeXmlSource interface and XFactory class. This pattern provides a standardized way to serialize and deserialize objects to and from XML format.

Don't serialize the runtime member object like Func<TResult> or Action either cache object. The runtime objects can be optionally sent by the res 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.

Core Components

IMakeXmlSource Interface

The IMakeXmlSource interface defines the contract for objects that can be serialized to XML format. It contains a single method MakeXmlSource.

XFactory Registration

Every class implementing IMakeXmlSource must:

  1. Define a static XName property matching the class name.
  2. Register itself in the static constructor using XFactory.Regs.Add
  3. Implement XML serialization and deserialization logic

For example, see BallApt:

static BallApt()
{
    // Register to the <see cref="XFactory.Default"/>.
    XFactory.Regs.Add(XName, (xml,baseDirectory,relFile, res) => new BallApt(xml));
}

Implementation Patterns

Simple Value Objects

See BallApt implementation:

/// <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)
        );
}

Complex Data Structures

See SpindleCapability implementation:

/// <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)))))
            )
        );
}

Best Practices

  1. XName: Always define static XName property matching the class name.
  2. Registration: Register in static constructor using XFactory.Regs
  3. Call the XName such like _ = CalleeClass.XName; in the caller class static initailization field so that the registration takes effect before calling the Callee construction by XFactory.
  4. Error Handling: Use appropriate GenMode
  5. Legacy Support: Maintain backward compatibility when needed