# core/formatters/config.xml.formatter.ts

> 45 lines of code and 14 definitions.

Tree: GovLab Quality
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/quality#file-quality-core-formatters-config-xml-formatter-ts
Source text: https://banes-lab.com/source/quality/core/formatters/config.xml.formatter.ts.txt

Listed in [core/formatters](https://banes-lab.com/api/source/quality/core/formatters.md), after [core/formatters/config.toml.formatter.ts](https://banes-lab.com/source/quality/core/formatters/config.toml.formatter.ts.md) and before [core/formatters/config.yaml.formatter.ts](https://banes-lab.com/source/quality/core/formatters/config.yaml.formatter.ts.md).

## Definitions

- `escapeXml` (lexical_declaration, line 14)
- `renderElement` (lexical_declaration, line 28)
- `renderAttrs` (lexical_declaration, line 22)
- `serializeXml` (lexical_declaration, line 43, exported)
- `XML_INDENT` (lexical_declaration, line 5)
- `XML_DECLARATION` (lexical_declaration, line 6)
- `XML_ESCAPES` (lexical_declaration, line 7)
- `escaped` (lexical_declaration, line 15)
- `pad` (lexical_declaration, line 29)
- `attrs` (lexical_declaration, line 30)
- `children` (lexical_declaration, line 31)
- `text` (lexical_declaration, line 32)
- `childLines` (lexical_declaration, line 39)
- `head` (lexical_declaration, line 44, exported)

## Contained in

- [core/formatters](https://banes-lab.com/anatomy/quality/folder-quality-core-formatters.md)

## Linked from

- [core/formatters](https://banes-lab.com/anatomy/quality/folder-quality-core-formatters.md)

## Source

```typescript
import type { XmlElement } from "#types/emitter.types";
import { defineFormatter } from "#core/registries/formatter.registry";
import { xmlRulesetTree } from "#core/converters/emitter.xml.converter";

const XML_INDENT = 2;
const XML_DECLARATION = '<?xml version="1.0" encoding="UTF-8"?>';
const XML_ESCAPES = new Map<string, string>([
    ['"', "&quot;"],
    ["&", "&amp;"],
    ["<", "&lt;"],
    [">", "&gt;"],
]);

const escapeXml = function escapeXml(text: string): string {
    let escaped = "";
    for (const ch of text) {
        escaped += XML_ESCAPES.get(ch) ?? ch;
    }
    return escaped;
};

const renderAttrs = function renderAttrs(attrs: Record<string, string> | undefined): string {
    return Object.entries(attrs ?? {})
        .map(([key, value]) => ` ${key}="${escapeXml(value)}"`)
        .join("");
};

const renderElement = function renderElement(element: XmlElement, indent: number): string[] {
    const pad = " ".repeat(indent);
    const attrs = renderAttrs(element.attrs);
    const children = element.children ?? [];
    const text = element.text ?? "";
    if (children.length === 0 && text === "") {
        return [`${pad}<${element.tag}${attrs}/>`];
    }
    if (children.length === 0) {
        return [`${pad}<${element.tag}${attrs}>${escapeXml(text)}</${element.tag}>`];
    }
    const childLines = children.flatMap((child) => renderElement(child, indent + XML_INDENT));
    return [`${pad}<${element.tag}${attrs}>`, ...childLines, `${pad}</${element.tag}>`];
};

export const serializeXml = function serializeXml(root: XmlElement, doctype?: string): string {
    const head = typeof doctype === "string" && doctype.length > 0 ? [XML_DECLARATION, doctype] : [XML_DECLARATION];
    return `${[...head, ...renderElement(root, 0)].join("\n")}\n`;
};

defineFormatter({
    format: "xml",
    render: (input) => serializeXml(xmlRulesetTree(input.descriptor, input.concepts), input.descriptor.xmlDoctype),
});
```
