|                                                                                                                                                                                                                                                                                                                                                                                          |  | const ARRAY = "array";const BOOLEAN = "boolean";const DATE = "date";const NULL = "null";const NUMBER = "number";const OBJECT = "object";const SPECIAL_OBJECT = "special-object";const STRING = "string";
const PRIVATE_VARS = ["_selfCloseTag", "_attrs"];const PRIVATE_VARS_REGEXP = new RegExp(PRIVATE_VARS.join("|"), "g");
/** * Determines the indent string based on current tree depth. */const getIndentStr = (indent = "", depth = 0) => indent.repeat(depth);
/** * Sugar function supplementing JS's quirky typeof operator, plus some extra help to detect * "special objects" expected by jstoxml. * Example: * getType(new Date()); * -> 'date' */const getType = (val) =>  (Array.isArray(val) && ARRAY) ||  (typeof val === OBJECT && val !== null && val._name && SPECIAL_OBJECT) ||  (val instanceof Date && DATE) ||  (val === null && NULL) ||  typeof val;
/** * Replaces matching values in a string with a new value. * Example: * filterStr('foo&bar', { '&': '&' }); * -> 'foo&bar' */const filterStr = (inputStr = "", filter = {}) => {  // Passthrough/no-op for nonstrings (e.g. number, boolean).
  if (typeof inputStr !== "string") {    return inputStr;  }
  const regexp = new RegExp(    `(${Object.keys(filter).join("|")})(?!(\\w|#)*;)`,    "g"  );
  return String(inputStr).replace(    regexp,    (str, entity) => filter[entity] || ""  );};
/** * Maps an object or array of arribute keyval pairs to a string. * Examples: * { foo: 'bar', baz: 'g' } -> 'foo="bar" baz="g"' * [ { ⚡: true }, { foo: 'bar' } ] -> '⚡ foo="bar"' */const getAttributeKeyVals = (attributes = {}, filter) => {  let keyVals = [];  if (Array.isArray(attributes)) {    // Array containing complex objects and potentially duplicate attributes.
    keyVals = attributes.map((attr) => {      const key = Object.keys(attr)[0];      const val = attr[key];
      const filteredVal = filter ? filterStr(val, filter) : val;      const valStr = filteredVal === true ? "" : `="${filteredVal}"`;      return `${key}${valStr}`;    });  } else {    const keys = Object.keys(attributes);    keyVals = keys.map((key) => {      // Simple object - keyval pairs.
      // For boolean true, simply output the key.
      const filteredVal = filter        ? filterStr(attributes[key], filter)        : attributes[key];      const valStr = attributes[key] === true ? "" : `="${filteredVal}"`;
      return `${key}${valStr}`;    });  }
  return keyVals;};
/** * Converts an attributes object/array to a string of keyval pairs. * Example: * formatAttributes({ a: 1, b: 2 }) * -> 'a="1" b="2"' */const formatAttributes = (attributes = {}, filter) => {  const keyVals = getAttributeKeyVals(attributes, filter);  if (keyVals.length === 0) return "";
  const keysValsJoined = keyVals.join(" ");  return ` ${keysValsJoined}`;};
/** * Converts an object to a jstoxml array. * Example: * objToArray({ foo: 'bar', baz: 2 }); * -> * [ *   { *     _name: 'foo', *     _content: 'bar' *   }, *   { *     _name: 'baz', *     _content: 2 *   } * ] */const objToArray = (obj = {}) =>  Object.keys(obj).map((key) => {    return {      _name: key,      _content: obj[key],    };  });
/** * Determines if a value is a primitive JavaScript value (not including Symbol). * Example: * isPrimitive(4); * -> true */const PRIMITIVE_TYPES = [STRING, NUMBER, BOOLEAN];const isPrimitive = (val) => PRIMITIVE_TYPES.includes(getType(val));
/** * Determines if a value is a simple primitive type that can fit onto one line.  Needed for * determining any needed indenting and line breaks. * Example: * isSimpleType(new Date()); * -> true */const SIMPLE_TYPES = [...PRIMITIVE_TYPES, DATE, SPECIAL_OBJECT];const isSimpleType = (val) => SIMPLE_TYPES.includes(getType(val));/** * Determines if an XML string is a simple primitive, or contains nested data. * Example: * isSimpleXML('<foo />'); * -> false */const isSimpleXML = (xmlStr) => !xmlStr.match("<");
/** * Assembles an XML header as defined by the config. */const DEFAULT_XML_HEADER = '<?xml version="1.0" encoding="UTF-8"?>';const getHeaderString = ({ header, indent, isOutputStart /*, depth */ }) => {  const shouldOutputHeader = header && isOutputStart;  if (!shouldOutputHeader) return "";
  const shouldUseDefaultHeader = typeof header === BOOLEAN;  // return `${shouldUseDefaultHeader ? DEFAULT_XML_HEADER : header}${indent ? "\n" : ""
  //   }`;
  return shouldUseDefaultHeader ? DEFAULT_XML_HEADER : header;};
/** * Recursively traverses an object tree and converts the output to an XML string. * Example: * toXML({ foo: 'bar' }); * -> <foo>bar</foo> */const defaultEntityFilter = {  "<": "<",  ">": ">",  "&": "&",};export const toXML = (obj = {}, config = {}) => {  const {    // Tree depth
    depth = 0,    indent,    _isFirstItem,    // _isLastItem,
    _isOutputStart = true,    header,    attributesFilter: rawAttributesFilter = {},    filter: rawFilter = {},  } = config;
  const shouldTurnOffAttributesFilter = typeof rawAttributesFilter === 'boolean' && !rawAttributesFilter;  const attributesFilter = shouldTurnOffAttributesFilter ? {} : {    ...defaultEntityFilter,    ...{ '"': """ },    ...rawAttributesFilter,  };
  const shouldTurnOffFilter = typeof rawFilter === 'boolean' && !rawFilter;  const filter = shouldTurnOffFilter ? {} : { ...defaultEntityFilter, ...rawFilter };
  // Determine indent string based on depth.
  const indentStr = getIndentStr(indent, depth);
  // For branching based on value type.
  const valType = getType(obj);
  const headerStr = getHeaderString({ header, indent, depth, isOutputStart: _isOutputStart });
  const isOutputStart = _isOutputStart && !headerStr && _isFirstItem && depth === 0;
  let outputStr = "";  switch (valType) {    case "special-object": {      // Processes a specially-formatted object used by jstoxml.
      const { _name, _content } = obj;
      // Output text content without a tag wrapper.
      if (_content === null) {        outputStr = _name;        break;      }
      // Handles arrays of primitive values. (#33)
      const isArrayOfPrimitives =        Array.isArray(_content) && _content.every(isPrimitive);      if (isArrayOfPrimitives) {        const primitives = _content          .map((a) => {            return toXML(              {                _name,                _content: a,              },              {                ...config,                depth,                _isOutputStart: false              }            );          });        return primitives.join('');      }
      // Don't output private vars (such as _attrs).
      if (_name.match(PRIVATE_VARS_REGEXP)) break;
      // Process the nested new value and create new config.
      const newVal = toXML(_content, { ...config, depth: depth + 1, _isOutputStart: isOutputStart });      const newValType = getType(newVal);      const isNewValSimple = isSimpleXML(newVal);
      // Pre-tag output (indent and line breaks).
      const preIndentStr = (indent && !isOutputStart) ? "\n" : "";      const preTag = `${preIndentStr}${indentStr}`;
      // Special handling for comments, preserving preceding line breaks/indents.
      if (_name === '_comment') {        outputStr += `${preTag}<!-- ${_content} -->`;        break;      }
      // Tag output.
      const valIsEmpty = newValType === "undefined" || newVal === "";      const shouldSelfClose =        typeof obj._selfCloseTag === BOOLEAN          ? valIsEmpty && obj._selfCloseTag          : valIsEmpty;      const selfCloseStr = shouldSelfClose ? "/" : "";      const attributesString = formatAttributes(obj._attrs, attributesFilter);      const tag = `<${_name}${attributesString}${selfCloseStr}>`;
      // Post-tag output (closing tag, indent, line breaks).
      const preTagCloseStr = indent && !isNewValSimple ? `\n${indentStr}` : "";      const postTag = !shouldSelfClose        ? `${newVal}${preTagCloseStr}</${_name}>`        : "";      outputStr += `${preTag}${tag}${postTag}`;      break;    }
    case "object": {      // Iterates over keyval pairs in an object, converting each item to a special-object.
      const keys = Object.keys(obj);      const outputArr = keys.map((key, index) => {        const newConfig = {          ...config,          _isFirstItem: index === 0,          _isLastItem: index + 1 === keys.length,          _isOutputStart: isOutputStart        };
        const outputObj = { _name: key };
        if (getType(obj[key]) === "object") {          // Sub-object contains an object.
          // Move private vars up as needed.  Needed to support certain types of objects
          // E.g. { foo: { _attrs: { a: 1 } } } -> <foo a="1"/>
          PRIVATE_VARS.forEach((privateVar) => {            const val = obj[key][privateVar];            if (typeof val !== "undefined") {              outputObj[privateVar] = val;              delete obj[key][privateVar];            }          });
          const hasContent = typeof obj[key]._content !== "undefined";          if (hasContent) {            // _content has sibling keys, so pass as an array (edge case).
            // E.g. { foo: 'bar', _content: { baz: 2 } } -> <foo>bar</foo><baz>2</baz>
            if (Object.keys(obj[key]).length > 1) {              const newContentObj = Object.assign({}, obj[key]);              delete newContentObj._content;
              outputObj._content = [                ...objToArray(newContentObj),                obj[key]._content,              ];            }          }        }
        // Fallthrough: just pass the key as the content for the new special-object.
        if (typeof outputObj._content === "undefined")          outputObj._content = obj[key];
        const xml = toXML(outputObj, newConfig, key);
        return xml;      }, config);
      outputStr = outputArr.join('');      break;    }
    case "function": {      // Executes a user-defined function and returns output.
      const fnResult = obj(config);
      outputStr = toXML(fnResult, config);      break;    }
    case "array": {      // Iterates and converts each value in an array.
      const outputArr = obj.map((singleVal, index) => {        const newConfig = {          ...config,          _isFirstItem: index === 0,          _isLastItem: index + 1 === obj.length,          _isOutputStart: isOutputStart        };        return toXML(singleVal, newConfig);      });
      outputStr = outputArr.join('');
      break;    }
    // number, string, boolean, date, null, etc
    default: {      outputStr = filterStr(obj, filter);      break;    }  }
  return `${headerStr}${outputStr}`;};
export default {  toXML,};
 |