# XML Processing

To enable XML processing and access XML functions, import the XML module with the following command:

    import { XmlParser, XmlNode } from "xml";

The XmlParser constructor accepts an options object that allows you to customize how the script parses XML into a JavaScript object. The following options are available:

Option | Description | Example
--- | --- | ---
    attributeNamePrefix
| The prefix added to attribute names in the output object. Default: "@_" | For example, <root attr="val"/> becomes { "root": { "@_attr": "val" } }.
    ignoreAttributes
| If set to true, attributes are ignored and not included in the output. Default: false |
    textNodeName
| The property name used for text nodes in the output object. Default: "#text" | For example, <root>value</root> becomes { "root": { "#text": "value" } }.
    tagValueProcessor
| Optional function Functions are tools that are available in Spreadsheet for processing and analyzing acquisitions or other results. You can add functions to your Spreadsheet job to create tool chains and produce results for specific applications. to transform tag values. Signature: (tagValue, jpath, hasAttributes) => newValue | For example, you can convert all tag values to uppercase.
    attributeValueProcessor
| Optional function to process attribute values. Signature: (attrName, attrValue, jpath) => newValue | For example, you can add a suffix to all attribute values.

## XmlParser Example

Incoming message:
Copy

    <command method="native" params="MT" />

Parser options:
Copy

    this.parser = new XmlParser({
        attributeNamePrefix: "", // Means that all attributes will become properties
    });

Accessing the converted XML in JavaScript:
Copy

    const xmlDoc = this.parser.parse(payload);
    const method = xmlDoc.command.method;
    const params = xmlDoc.command.params;

Creating XML string from JavaScript object:
Copy

    /**
       * Recursively builds XML from a JSON object.
       * Arrays are represented as repeated 'item' tags.
       * @param {object} obj - The JSON object to convert.
       * @param {string} [nodeName='root'] - The root XML node name.
       * @returns {string} XML string
       */
      buildXmlFromObject(obj, nodeName = "root") {
        function buildNode(key, value) {
          if (Array.isArray(value)) {
            const arrNode = new XmlNode(key);
            value.forEach((item) => {
              arrNode.addChildNode(buildNode("item", item));
            });
            return arrNode;
          } else if (value !== null && typeof value === "object") {
            const objNode = new XmlNode(key);
            for (const [childKey, childValue] of Object.entries(value)) {
              objNode.addChildNode(buildNode(childKey, childValue));
            }
            return objNode;
          } else if (value === null) {
            return new XmlNode(key); // empty element for null
          } else {
            return XmlNode.of(key, String(value));
          }
        }

        const rootNode = buildNode(nodeName, obj);
        return rootNode.toString();
      }

Full XML example:
Copy

    import { device, connection } from "script-sdk";
    import { XmlNode, XmlParser } from "xml";

    export default class XmlFormatter {
        constructor() {
            // Setup topics for notifications and responses
            this.resultsTopic = `cognex/${connection.clientId}/results`;
            this.responseTopic = `cognex/${connection.clientId}/responses`;

            // Set up xml parser
            this.parser = new XmlParser({
                attributeNamePrefix: "",
            });

            // Set up result handling
            device.streams.results.setHandler((result) => {
                this.onResult(result);
            });

            // Set up command handling
            // Note that we specify "string" here to get the raw string payload
            connection.subscriptions.setHandler(async (payload, topic, props) => {
                await this.onIncomingMessage(payload, topic, props);
            }, "string");
        }

        async onResult(result) {
            // Determine send properties
            const payload = this.buildXmlFromObject(result, "result");

            // Publish the message
            console.log(`Publishing Result to topic ${this.resultsTopic}: ${payload}`);
            connection.publish(payload, this.resultsTopic);
        }

        /**
         * Handles incoming command messages.
         *
         * @param {object} payload - The message payload received from the external system.
         * @param {string} topic - The topic the message was received on.
         * @param {object} props - Additional properties associated with the message.
         * @returns {Promise<void>}
         */
        async onIncomingMessage(payload, topic, props) {
            //Expecting an XML payload in the form "<command method=\"native\" params=\"params\"/>"
            const xmlDoc = this.parser.parse(payload);
            const method = xmlDoc.command.method;
            const params = xmlDoc.command.params;

            if (method === "trigger") {
                let response = await device.commands["trigger"].execute();
                console.log(`Trigger command response: ${this.buildXmlFromObject(response, "response")}`);
            } else if (method === "native") {
                let response = await device.commands["native"].execute(params);
                console.log(`Native command response: ${this.buildXmlFromObject(response, "response")}`);
            } else {
                console.log("Unknown command");
            }
        }

        /**
         * Recursively builds XML from a JSON object.
         * Arrays are represented as repeated 'item' tags.
         * @param {object} obj - The JSON object to convert.
         * @param {string} [nodeName='root'] - The root XML node name.
         * @returns {string} XML string
         */
        buildXmlFromObject(obj, nodeName = "root") {
            function buildNode(key, value) {
                if (Array.isArray(value)) {
                    const arrNode = new XmlNode(key);
                    value.forEach((item) => {
                        arrNode.addChildNode(buildNode("item", item));
                    });
                    return arrNode;
                } else if (value !== null && typeof value === "object") {
                    const objNode = new XmlNode(key);
                    for (const [childKey, childValue] of Object.entries(value)) {
                        objNode.addChildNode(buildNode(childKey, childValue));
                    }
                    return objNode;
                } else if (value === null) {
                    return new XmlNode(key); // empty element for null
                } else {
                    return XmlNode.of(key, String(value));
                }
            }

            const rootNode = buildNode(nodeName, obj);
            return rootNode.toString();
        }
    }