All files / src xml-from-object.ts

100% Statements 66/66
100% Branches 19/19
100% Functions 19/19
100% Lines 48/48

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 1272x                         2x             22x 2x             2x 22x 3x 3x 3x       3x 2x       252x 126x 126x   4x   3x   17x   82x   20x     48x       126x 106x 102x 99x 82x       4x 4x 4x       3x       17x 17x       82x       20x       106x 106x 106x       106x 77x       29x       106x 106x 106x       106x 2x       4x 4x   2x       4x    
import { isArrayOfObject, isArrayOfString } from "./helpers";
import {
  FromObjectHeader,
  SchemaFieldConfig,
  FromObjectInternalValue,
  FromObjectParams,
  FromObjectSchema,
  SchemaFieldAttributes,
} from "./types";
 
/**
 * Class way to transform a Javascript plain object into XML
 */
export class XmlFromObject {
  /**
   * Transform a Javascript plain object into XML
   * @param {object} schemaConfig.schema - The typed schema with fields and configs to create the XML.
   * @param {object} [schemaConfig.header] - (Optional) The xml header (typically "<?xml version...>").
   *
   */
  fromObject = xmlFromObject;
}
 
/**
 * Functional way to transform a Javascript plain object into XML
 * @param {object} schemaConfig.schema - The typed schema with fields and configs to create the XML.
 * @param {object} [schemaConfig.header] - (Optional) The xml header (typically "<?xml version...>").
 */
export function xmlFromObject(schemaConfig: FromObjectParams): string {
  if (!schemaConfig.header) return parseSchemaToXml(schemaConfig.schema);
  const parsedHeader = parseXmlHeader(schemaConfig.header);
  const parsedSchema = parseSchemaToXml(schemaConfig.schema);
  return parsedHeader + parsedSchema;
}
 
function parseXmlHeader(header: FromObjectHeader): string {
  if (header.custom !== undefined) return header.custom;
  return `<?xml version="${header.version}" encoding="${header.encoding}"?>`;
}
 
function parseSchemaToXml(schema: FromObjectSchema): string {
  const resultInArray = Object.entries(schema).map(([key, { value: untypedValue, ...fieldConfig }]) => {
    const { value, type } = getValueType(key, untypedValue);
    switch (type) {
      case "array-of-object":
        return parseTagForArrayOfObjects(key, value, fieldConfig);
      case "array-of-string":
        return parseTagForArrayOfString(key, value, fieldConfig);
      case "object":
        return parseTagForObject(key, value, fieldConfig);
      case "string":
        return parseTagForString(key, value, fieldConfig);
      case "self-closing":
        return parseSelfClosingTag(key);
    }
  });
  return resultInArray.join("");
}
 
function getValueType(key: string, value: any): FromObjectInternalValue {
  if (value === undefined || value === null) return { type: "self-closing", value: undefined };
  if (isArrayOfObject(value)) return { type: "array-of-object", value: value as FromObjectSchema[] };
  if (isArrayOfString(value)) return { type: "array-of-string", value: value as string[] };
  if (typeof value === "object") return { type: "object", value: value as FromObjectSchema };
  return { type: "string", value: String(value) };
}
 
function parseTagForArrayOfObjects(key: string, values: FromObjectSchema[], config: SchemaFieldConfig): string {
  const nestedTagsAsArrayOfString = values.map(parseSchemaToXml);
  const nestedTags = nestedTagsAsArrayOfString.join("");
  return keyValueToXmlTag(key, nestedTags, config);
}
 
function parseTagForArrayOfString(key: string, value: string[], config: SchemaFieldConfig): string {
  return keyValueToXmlTag(key, value.join(), config);
}
 
function parseTagForObject(key: string, value: FromObjectSchema, config: SchemaFieldConfig): string {
  const nestedValue = parseSchemaToXml(value);
  return keyValueToXmlTag(key, nestedValue, config);
}
 
function parseTagForString(key: string, value: string, config: SchemaFieldConfig): string {
  return keyValueToXmlTag(key, value, config);
}
 
function parseSelfClosingTag(key: string): string {
  return `<${key}/>`;
}
 
function keyValueToXmlTag(key: string, value: string, config: SchemaFieldConfig): string {
  const parsedKey = parseKey(key, config);
  const parsedValue = parseValue(value, config);
  return parsedKey.start + parsedValue + parsedKey.end;
}
 
function parseValue(value: string, config: SchemaFieldConfig): string {
  if (config.options?.cdata) return wrapWithCdata(value);
  return value;
}
 
function wrapWithCdata(value: string): string {
  return `<![CDATA[${value}]]>`;
}
 
function parseKey(key: string, config: SchemaFieldConfig): { start: string; end: string } {
  const start = parseStartOfKey(key, config);
  const end = `</${key}>`;
  return { start, end };
}
 
function parseStartOfKey(key: string, config: SchemaFieldConfig): string {
  if (!config.attributes) return `<${key}>`;
  return `<${key} ${parseKeyAttributes(config.attributes)}>`;
}
 
function parseKeyAttributes(attributes: SchemaFieldAttributes): string {
  const attributesInArray = Object.entries(attributes).map(([key, value]) => {
    return parseAttribute(key, value);
  });
  return attributesInArray.join(" ");
}
 
function parseAttribute(key: string, value: string): string {
  return `${key}="${value}"`;
}