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 += '&#x' + hex + ';'; } js += String.fromCharCode(i); } xml += ''; parseTest(xml, { a: js, }); }); });