All files / src/pipeline/rfcxml xml-builder.js

96.15% Statements 25/26
94.28% Branches 33/35
100% Functions 10/10
96% Lines 24/25

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        7x     212x       211x             84x       152x     152x 86x   84x 1x   83x           55x 55x   319x               151x 151x       151x 12x     139x   139x 3x     136x 82x     54x     7x                  
/**
 * Minimal RFCXML helpers: escaping, attribute formatting, and element assembly.
 */
 
const ESCAPED_TAG_PLACEHOLDER = '__SPEC_UP_ESCAPED_TAG__';
 
function restoreEscapes(text) {
    return String(text ?? '').replaceAll(ESCAPED_TAG_PLACEHOLDER, '[[');
}
 
function escapeXml(text) {
    return restoreEscapes(text)
        .replaceAll('&', '&')
        .replaceAll('<', '&lt;')
        .replaceAll('>', '&gt;');
}
 
function escapeAttr(text) {
    return escapeXml(text).replaceAll('"', '&quot;').replaceAll("'", '&apos;');
}
 
function formatAttrs(attrs) {
    Iif (!attrs) {
        return '';
    }
    return Object.entries(attrs)
        .filter(([, value]) => value !== undefined && value !== null && value !== false)
        .map(([key, value]) => {
            if (value === true) {
                return ` ${key}="true"`;
            }
            return ` ${key}="${escapeAttr(String(value))}"`;
        })
        .join('');
}
 
function indentBlock(text, level) {
    const pad = '  '.repeat(level);
    return String(text)
        .split('\n')
        .map(line => (line.length ? pad + line : line))
        .join('\n');
}
 
/**
 * Build an XML element. `content` may be a string, an array of strings, or omitted (empty element).
 */
function el(name, attrs, content) {
    const attrStr = formatAttrs(typeof attrs === 'object' && !Array.isArray(attrs) ? attrs : {});
    const body = arguments.length === 2 && (typeof attrs === 'string' || Array.isArray(attrs))
        ? attrs
        : content;
 
    if (body === undefined || body === null || body === '') {
        return `<${name}${attrStr}/>`;
    }
 
    const inner = Array.isArray(body) ? body.filter(part => part != null && part !== '').join('\n') : String(body);
 
    if (name === 'sourcecode' || name === 'artwork') {
        return `<${name}${attrStr}>\n${inner}\n</${name}>`;
    }
 
    if (!inner.includes('\n')) {
        return `<${name}${attrStr}>${inner}</${name}>`;
    }
 
    return `<${name}${attrStr}>\n${indentBlock(inner, 1)}\n</${name}>`;
}
 
module.exports = {
    ESCAPED_TAG_PLACEHOLDER,
    restoreEscapes,
    escapeXml,
    escapeAttr,
    formatAttrs,
    indentBlock,
    el
};