import { assert } from 'chai';
import { setup } from 'f-mocha';
import { run, wait } from 'f-promise';
import * as fs from 'fs';
import { binaryFileReader, cutter, stringReader, textFileReader, textFileWriter, xmlFormatter, xmlParser } from '../..';
setup();
const { equal, ok, strictEqual, deepEqual } = assert;
function short(s: string) {
return s.length < 50 ? s : s.substring(0, 47) + '...';
}
function parseTest(xml: string, js: any, skipRT?: boolean) {
const full = '' + xml + '\n';
const parsed = stringReader(full)
.transform(cutter(2)) //
.transform(xmlParser('root'))
.toArray();
deepEqual(parsed[0].root, js, 'parse ' + short(xml));
if (!skipRT) {
const rt = stringReader(full)
.transform(cutter(2)) //
.transform(xmlParser('root')) //
.transform(xmlFormatter('root'))
.toArray()
.join('');
strictEqual(rt, full, 'roundtrip ' + short(full));
}
}
function rtTest(name: string, xml: string, indent: string | undefined, result: any) {
const full = '' + xml + '\n';
result = '' + (indent ? '\n' : '') + '' + (result || xml) + '\n';
const rt = stringReader(full)
.transform(cutter(2)) //
.transform(xmlParser('root')) //
.transform(
xmlFormatter({
tags: 'root',
indent: indent,
}),
)
.toArray()
.join('');
strictEqual(rt, result, 'roundtrip ' + full);
}
describe(module.id, () => {
it('simple tag without attributes', () => {
parseTest('', {
a: {},
});
parseTest('', {
a: '',
});
parseTest('5', {
a: '5',
});
});
it('simple tag with attributes', () => {
parseTest('5', {
a: {
$: {
x: '3',
y: '4',
},
$value: '5',
},
});
parseTest('', {
a: {
$: {
x: '3',
},
$value: '',
},
});
parseTest('', {
a: {
$: {
x: '3',
},
},
});
});
it('entities', () => {
parseTest('', {
a: {
$: {
x: 'a>b&c<',
},
},
});
parseTest('a>b&c<', {
a: 'a>b&c<',
});
});
it('children', () => {
parseTest('34', {
a: {
b: '3',
c: '4',
},
});
parseTest('34', {
a: {
b: {
$: {
x: '2',
},
$value: '3',
},
c: '4',
},
});
parseTest('345', {
a: {
b: ['3', '4'],
c: '5',
},
});
});
it('cdata', () => {
parseTest(']]>', {
a: {
$cdata: '',
},
});
parseTest('', {
a: {
$cdata: '',
},
});
});
it('comments in text', () => {
parseTest(
'abc ghi',
{
a: 'abc ghi',
},
true,
);
});
it('reformatting', () => {
rtTest('spaces outside', ' \r\n\t \t', undefined, '');
rtTest('spaces inside tag', '', undefined, '');
rtTest('spaces around children', ' \n\t', undefined, '');
rtTest('spaces and cdata', ' \n\n\t]]>\t', undefined, '\n\t]]>');
rtTest('spaces in value', ' ', undefined, ' ');
rtTest('more spaces in value', ' \r\n\t', undefined, '
');
rtTest(
'indentation',
'5',
'\t',
'\n\t\n\t\t5\n\t\t\n\t\t\t\n\t\t\n\t\n',
);
});
it('empty element in list', () => {
parseTest(
'x',
{
a: {
b: ['', 'x', ''],
},
},
true,
);
});
it('rss feed', () => {
const entries = textFileReader(__dirname + '/../../../test/fixtures/rss-sample.xml') //
.transform(cutter(2)) //
.transform(xmlParser('rss/channel/item'))
.toArray();
strictEqual(entries.length, 10);
strictEqual(entries[0].rss.channel.title, 'Yahoo! Finance: Top Stories');
strictEqual(entries[0].rss.channel.item.title, 'Wall Street ends down on first trading day of 2014');
strictEqual(entries[9].rss.channel.title, 'Yahoo! Finance: Top Stories');
strictEqual(
entries[9].rss.channel.item.title,
"2013's big winners abandoned 'safety' and bet on central bankers",
);
});
it('binary input', () => {
const entries = binaryFileReader(__dirname + '/../../../test/fixtures/rss-sample.xml') //
.transform(cutter(2)) //
.transform(xmlParser('rss/channel/item'))
.toArray();
strictEqual(entries.length, 10);
strictEqual(entries[0].rss.channel.title, 'Yahoo! Finance: Top Stories');
strictEqual(entries[0].rss.channel.item.title, 'Wall Street ends down on first trading day of 2014');
strictEqual(entries[9].rss.channel.title, 'Yahoo! Finance: Top Stories');
strictEqual(
entries[9].rss.channel.item.title,
"2013's big winners abandoned 'safety' and bet on central bankers",
);
});
it('rss roundtrip', () => {
let expected = wait(cb => fs.readFile(__dirname + '/../../../test/fixtures/rss-sample.xml', 'utf8', cb));
let result = textFileReader(__dirname + '/../../../test/fixtures/rss-sample.xml') //
.transform(cutter(5)) //
.transform(xmlParser('rss/channel/item')) //
.transform(
xmlFormatter({
tags: 'rss/channel/item',
indent: ' ',
}),
) //
.toArray()
.join('');
expected = expected.replace(/\r?\n */g, '').replace(/<\!--.*-->/g, '');
result = result.replace(/\r?\n */g, '');
strictEqual(result, expected);
});
it('escaping', () => {
let xml = '';
let js = '';
for (let i = 0; i < 0x10000; i++) {
if (i > 300 && i % 100) continue;
// tab, cr, lf, ' and " could be formatted verbatim but we escape them
if ((i >= 0x20 && i <= 0x7e) || (i >= 0xa1 && i <= 0xd7ff) || (i >= 0xe000 && i <= 0xfffd)) {
if (i >= 0x2000 && i < 0xd000) continue; // skip to speed up test
const ch = String.fromCharCode(i);
if (ch === '<') xml += '<';
else if (ch === '>') xml += '>';
else if (ch === '&') xml += '&';
else if (ch === '"') xml += '"';
else if (ch === "'") xml += ''';
else xml += ch;
} else {
let hex = i.toString(16);
while (hex.length < 2) hex = '0' + hex;
while (hex.length > 2 && hex.length < 4) hex = '0' + hex;
xml += '' + hex + ';';
}
js += String.fromCharCode(i);
}
xml += '';
parseTest(xml, {
a: js,
});
});
});