Source: utils/xpath.js

// Temporary XPath implementation until the xml2js-xpath package adds needed functionality.

const childToParent = {
  unitClassDefinition: 'unitClassDefinitions',
  unitModifierDefinition: 'unitModifierDefinitions',
  valueClassDefinition: 'valueClassDefinitions',
  schemaAttributeDefinition: 'schemaAttributeDefinitions',
  propertyDefinition: 'propertyDefinitions',
}

/**
 * Execute an XPath query on an xml2js object.
 *
 * @param {Object} element An xml2js element.
 * @param {string} query An XPath query.
 * @returns {Object[]} An array of xml2js elements matching the query.
 */
export const find = function (element, query) {
  const { elementName, attributeName } = parseXPath(query)

  if (elementName === 'node') {
    const parentElement = element.schema?.[0] ?? element
    const nodeList = parentElement.node ?? []
    return nodeList.flatMap((child) => search(child, elementName, attributeName))
  }

  return element?.[childToParent[elementName]]?.[0][elementName] ?? []
}

/**
 * Parse an XPath query.
 *
 * This is a minimal parser only suitable for this package.
 *
 * @param {string} query An XPath query.
 * @returns {Object} The parsed search parameters.
 */
const parseXPath = function (query) {
  const nodeQuery = /^\/\/(\w+)$/
  const attributeQuery = /^\/\/(\w+)\[@(\w+)]$/

  let elementName, attributeName

  const attributeMatch = query.match(attributeQuery)
  if (attributeMatch) {
    ;[, elementName, attributeName] = attributeMatch
  } else {
    const nodeMatch = query.match(nodeQuery)
    if (nodeMatch) {
      ;[, elementName] = nodeMatch
    } else {
      return {}
    }
  }
  return { elementName: elementName, attributeName: attributeName }
}

/**
 * Search for children of an element with a given name and attribute.
 *
 * @param {Object} element An xml2js element.
 * @param {string} elementName The element name.
 * @param {string} attributeName The attribute name.
 * @returns {Object[]} An array of xml2js elements with the given name and attribute.
 */
const search = function (element, elementName, attributeName) {
  let result = []
  if (attributeName === undefined || ('$' in element && attributeName in element.$)) {
    result.push(element)
  }
  if (elementName in element) {
    result = result.concat(
      element[elementName].flatMap((element) => {
        return search(element, elementName, attributeName)
      }),
    )
  }
  return result
}