import 'package:test/test.dart'; import 'package:xml/xml.dart'; import 'utils/assertions.dart'; import 'utils/matchers.dart'; void main() { test('basic', () { final builder = XmlBuilder(); builder.declaration(encoding: 'UTF-8'); builder.processing( 'xml-stylesheet', 'href="/style.css" type="text/css" title="default stylesheet"', ); builder.element( 'bookstore', nest: () { builder.comment('Only one book?'); builder.element( 'book', nest: () { builder.element( 'title', nest: () { builder.attribute('lang', 'en'); builder.text('Harry '); builder.cdata('Potter'); }, ); builder.element('price', nest: 29.99); }, ); }, ); final document = builder.buildDocument(); assertDocumentTreeInvariants(document); final actual = document.toString(); const expected = '' '' '' '' '' 'Harry <![CDATA[Potter]]>' '29.99' '' ''; expect(actual, expected); }); test('all', () { final builder = XmlBuilder(); builder.declaration(); builder.doctype('note', systemId: 'Note.dtd'); builder.processing('processing', 'instruction'); builder.element( 'element1', attributes: {'attribute1': 'value1'}, nest: () { builder.attribute( 'attribute2', 'value2', attributeType: XmlAttributeType.DOUBLE_QUOTE, ); builder.attribute( 'attribute3', 'value3', attributeType: XmlAttributeType.SINGLE_QUOTE, ); builder.element('element2'); builder.comment('comment'); builder.cdata('cdata'); builder.text('textual'); }, ); final xml = builder.buildDocument(); assertDocumentTreeInvariants(xml); final actual = xml.toString(); const expected = '' '' '' '' '' '' '' 'textual' ''; expect(actual, expected); }); test('self-closing', () { final builder = XmlBuilder(); builder.element( 'element', nest: () { builder.element('self-closing-default'); builder.element('self-closing-true', isSelfClosing: true); builder.element( 'self-closing-true-with-children', isSelfClosing: true, nest: '!', ); builder.element('self-closing-false', isSelfClosing: false); }, ); final xml = builder.buildDocument(); assertDocumentTreeInvariants(xml); final actual = xml.toString(); const expected = '' '' '' '!' '' ''; expect(actual, expected); }); test('nested string', () { final builder = XmlBuilder(); builder.element('element', nest: 'string'); final xml = builder.buildDocument(); assertDocumentTreeInvariants(xml); final actual = xml.toString(); const expected = 'string'; expect(actual, expected); }); test('nested iterable', () { final builder = XmlBuilder(); builder.element( 'element', nest: [ () => builder.text('st'), 'ri', ['n', 'g'], ], ); final xml = builder.buildDocument(); assertDocumentTreeInvariants(xml); final actual = xml.toString(); const expected = 'string'; expect(actual, expected); }); test('nested node (element)', () { final builder = XmlBuilder(); final nested = XmlElement(XmlName('nested')); builder.element('element', nest: nested); final xml = builder.buildDocument(); assertDocumentTreeInvariants(xml); expect(xml.children[0].children[0].toXmlString(), nested.toXmlString()); expect(xml.children[0].children[0], isNot(same(nested))); final actual = xml.toString(); const expected = ''; expect(actual, expected); }); test('nested node (element, repeated)', () { final builder = XmlBuilder(); final nested = XmlElement(XmlName('nested')); builder.element('element', nest: [nested, nested]); final xml = builder.buildDocument(); assertDocumentTreeInvariants(xml); expect(xml.children[0].children[0].toXmlString(), nested.toXmlString()); expect(xml.children[0].children[0], isNot(same(nested))); expect(xml.children[0].children[1].toXmlString(), nested.toXmlString()); expect(xml.children[0].children[1], isNot(same(nested))); final actual = xml.toString(); const expected = ''; expect(actual, expected); }); test('nested node (text)', () { final builder = XmlBuilder(); final nested = XmlText('text'); builder.element('element', nest: nested); final xml = builder.buildDocument(); assertDocumentTreeInvariants(xml); expect(xml.children[0].children[0].toXmlString(), nested.toXmlString()); expect(xml.children[0].children[0], isNot(same(nested))); final actual = xml.toString(); const expected = 'text'; expect(actual, expected); }); test('nested node (text, repeated)', () { final builder = XmlBuilder(); final nested = XmlText('text'); builder.element('element', nest: [nested, nested]); final xml = builder.buildDocument(); assertDocumentTreeInvariants(xml); expect(xml.children[0].children[0].value, 'texttext'); expect(xml.children[0].children[0], isNot(same(nested))); final actual = xml.toString(); const expected = 'texttext'; expect(actual, expected); }); test('nested node (data)', () { final builder = XmlBuilder(); final nested = XmlComment('abc'); builder.element('element', nest: nested); final xml = builder.buildDocument(); assertDocumentTreeInvariants(xml); expect(xml.children[0].children[0].toXmlString(), nested.toXmlString()); expect(xml.children[0].children[0], isNot(same(nested))); final actual = xml.toString(); const expected = ''; expect(actual, expected); }); test('nested node (attribute)', () { final builder = XmlBuilder(); final nested = XmlAttribute(XmlName('foo'), 'bar'); builder.element('element', nest: nested); final xml = builder.buildDocument(); assertDocumentTreeInvariants(xml); expect(xml.children[0].attributes[0].toXmlString(), nested.toXmlString()); expect(xml.children[0].attributes[0], isNot(same(nested))); final actual = xml.toString(); const expected = ''; expect(actual, expected); }); test('nested node (document)', () { final builder = XmlBuilder(); final nested = XmlDocument([]); expect(() => builder.element('element', nest: nested), throwsArgumentError); }); test('nested node (document fragment)', () { final builder = XmlBuilder(); final nested = XmlDocumentFragment([XmlText('foo'), XmlComment('bar')]); builder.element('element', nest: nested); final xml = builder.buildDocument(); assertDocumentTreeInvariants(xml); expect( xml.children[0].children[0].toXmlString(), nested.children[0].toXmlString(), ); expect(xml.children[0].children[0], isNot(same(nested.children[0]))); expect( xml.children[0].children[1].toXmlString(), nested.children[1].toXmlString(), ); expect(xml.children[0].children[1], isNot(same(nested.children[1]))); final actual = xml.toString(); const expected = 'foo'; expect(actual, expected); }); test('text', () { final builder = XmlBuilder(); builder.element( 'text', nest: () { builder.text('abc'); builder.text(''); builder.text('def'); }, ); final xml = builder.buildDocument(); assertDocumentTreeInvariants(xml); final actual = xml.toString(); const expected = 'abcdef'; expect(actual, expected); }); test('doctype (plain)', () { final builder = XmlBuilder() ..doctype('note') ..element('root'); final xml = builder.buildDocument(); assertDocumentTreeInvariants(xml); final actual = xml.toString(); const expected = ''; expect(actual, expected); }); test('doctype (system ID)', () { final builder = XmlBuilder() ..doctype('note', systemId: 'system.dtd') ..element('root'); final xml = builder.buildDocument(); assertDocumentTreeInvariants(xml); final actual = xml.toString(); const expected = ''; expect(actual, expected); }); test('doctype (public ID)', () { final builder = XmlBuilder() ..doctype('note', publicId: 'public.dtd', systemId: 'system.dtd') ..element('root'); final xml = builder.buildDocument(); assertDocumentTreeInvariants(xml); final actual = xml.toString(); const expected = ''; expect(actual, expected); }); test('doctype (internal subset)', () { final builder = XmlBuilder() ..doctype('note', internalSubset: '') ..element('root'); final xml = builder.buildDocument(); assertDocumentTreeInvariants(xml); final actual = xml.toString(); const expected = ']>'; expect(actual, expected); }); test('doctype (error)', () { final builder = XmlBuilder(); expect( () => builder.doctype('note', publicId: 'public.dtd'), throwsArgumentError, ); }); test('attribute', () { final builder = XmlBuilder(); builder.element( 'element', nest: () { builder.attribute('foo', 'bar'); }, ); final xml = builder.buildDocument(); assertDocumentInvariants(xml); final actual = xml.toString(); expect(actual, ''); }); test('attribute (single quote)', () { final builder = XmlBuilder(); builder.element( 'element', nest: () { builder.attribute( 'foo', 'bar', attributeType: XmlAttributeType.SINGLE_QUOTE, ); }, ); final xml = builder.buildDocument(); assertDocumentInvariants(xml); final actual = xml.toString(); expect(actual, ''); }); test('attribute (replaced)', () { final builder = XmlBuilder(); builder.element( 'element', nest: () { builder.attribute('foo', 'bar'); builder.attribute('foo', 'zork'); }, ); final xml = builder.buildDocument(); assertDocumentInvariants(xml); final actual = xml.toString(); expect(actual, ''); }); test('attribute (removed)', () { final builder = XmlBuilder(); builder.element( 'element', nest: () { builder.attribute('foo', 'bar'); builder.attribute('foo', null); }, ); final xml = builder.buildDocument(); assertDocumentInvariants(xml); final actual = xml.toString(); expect(actual, ''); }); test('attribute (multiple)', () { final builder = XmlBuilder(); builder.element( 'element', nest: () { for (var i = 1; i <= 5; i++) { builder.attribute('a$i', i); } }, ); final xml = builder.buildDocument(); assertDocumentInvariants(xml); final actual = xml.toString(); expect(actual, ''); }); test('attribute (update)', () { final builder = XmlBuilder(); builder.element( 'element', nest: () { builder.attribute('lang', 'en'); builder.attribute('lang', 'de'); }, ); final xml = builder.buildDocument(); assertDocumentTreeInvariants(xml); final actual = xml.toString(); const expected = ''; expect(actual, expected); }); test('attribute (update with namespace)', () { const namespaces = { 'http://www.w3.org/1999/xhtml': 'xhtml', 'http://www.w3.org/1999/xlink': 'xlink', }; final builder = XmlBuilder(); builder.element( 'element', namespaces: namespaces, nest: () { for (final uri in namespaces.keys) { builder.attribute('lang', 'en', namespace: uri); } for (final uri in namespaces.keys) { builder.attribute('lang', 'de', namespace: uri); } }, ); final xml = builder.buildDocument(); assertDocumentTreeInvariants(xml); final actual = xml.toString(); const expected = ''; expect(actual, expected); }); test('attribute (update with unknown namespace)', () { final builder = XmlBuilder(); builder.element( 'element', nest: () { builder.attribute('xhtml:lang', 'en'); builder.attribute('xhtml:lang', 'de'); }, ); final xml = builder.buildDocument(); assertDocumentTreeInvariants(xml); final actual = xml.toString(); const expected = ''; expect(actual, expected); }); test('xml', () { final builder = XmlBuilder(); builder.xml(''); final xml = builder.buildDocument(); assertDocumentTreeInvariants(xml); final actual = xml.toString(); const expected = ''; expect(actual, expected); }); test('xml (nested)', () { final builder = XmlBuilder(); builder.element( 'outer', nest: () { builder.xml(''); }, ); final xml = builder.buildDocument(); assertDocumentTreeInvariants(xml); final actual = xml.toString(); const expected = ''; expect(actual, expected); }); test('xml (multiple)', () { final builder = XmlBuilder(); builder.element( 'outer', nest: () { builder.xml(''); builder.xml('hello'); }, ); final xml = builder.buildDocument(); assertDocumentTreeInvariants(xml); final actual = xml.toString(); const expected = 'hello'; expect(actual, expected); }); test('xml (invalid)', () { final builder = XmlBuilder(); builder.element( 'outer', nest: () { expect( () => builder.xml(''), throwsA( isXmlTagException( message: 'Missing ', expectedName: 'broken', actualName: isNull, buffer: '', position: 8, ), ), ); }, ); final xml = builder.buildDocument(); assertDocumentTreeInvariants(xml); final actual = xml.toString(); const expected = ''; expect(actual, expected); }); test('namespace binding', () { const uri = 'http://www.w3.org/2001/XMLSchema'; final builder = XmlBuilder(); builder.element( 'schema', nest: () { builder.namespace(uri, 'xsd'); builder.attribute('lang', 'en', namespace: uri); builder.element('element', namespace: uri); }, namespace: uri, ); final xml = builder.buildDocument(); assertDocumentTreeInvariants(xml); final actual = xml.toString(); const expected = '' '' ''; expect(actual, expected); }); test('default namespace binding', () { const uri = 'http://www.w3.org/2001/XMLSchema'; final builder = XmlBuilder(); builder.element( 'schema', nest: () { builder.namespace(uri); builder.attribute('lang', 'en', namespace: uri); builder.element('element', namespace: uri); }, namespace: uri, ); final xml = builder.buildDocument(); assertDocumentTreeInvariants(xml); final actual = xml.toString(); const expected = '' '' ''; expect(actual, expected); }); test('undefined namespace', () { final builder = XmlBuilder(); expect( () => builder.element('element', namespace: 'http://foo.com/'), throwsArgumentError, ); }); test('invalid namespace', () { final builder = XmlBuilder(); builder.element( 'element', nest: () { expect( () => builder.namespace('http://foo.com/', 'xml'), throwsArgumentError, ); expect( () => builder.namespace('http://2.foo.com/', 'xmlns'), throwsArgumentError, ); }, ); final actual = builder.buildDocument().toString(); const expected = ''; expect(actual, expected); }); test('conflicting namespace', () { final builder = XmlBuilder(); builder.element( 'element', nest: () { builder.namespace('http://foo.com/', 'foo'); expect( () => builder.namespace('http://2.foo.com/', 'foo'), throwsArgumentError, ); }, namespace: 'http://foo.com/', ); final actual = builder.buildDocument().toString(); const expected = ''; expect(actual, expected); }); test('unused namespace', () { final builder = XmlBuilder(); builder.element( 'element', nest: () { builder.namespace('http://foo.com/', 'foo'); }, ); final actual = builder.buildDocument().toString(); const expected = ''; expect(actual, expected); }); test('unused namespace (optimized)', () { final builder = XmlBuilder(optimizeNamespaces: true); builder.element( 'element', nest: () { builder.namespace('http://foo.com/', 'foo'); }, ); final actual = builder.buildDocument().toString(); const expected = ''; expect(actual, expected); }); test('duplicate namespace', () { final builder = XmlBuilder(); builder.element( 'element', nest: () { builder.namespace('http://foo.com/', 'foo'); builder.element( 'outer', nest: () { builder.namespace('http://foo.com/', 'foo'); builder.element( 'inner', nest: () { builder.namespace('http://foo.com/', 'foo'); builder.attribute('lang', 'en', namespace: 'http://foo.com/'); }, ); }, ); }, ); final actual = builder.buildDocument().toString(); const expected = '' '' '' '' ''; expect(actual, expected); }); test('duplicate namespace on attribute (optimized)', () { final builder = XmlBuilder(optimizeNamespaces: true); builder.element( 'element', nest: () { builder.namespace('http://foo.com/', 'foo'); builder.element( 'outer', nest: () { builder.namespace('http://foo.com/', 'foo'); builder.element( 'inner', nest: () { builder.namespace('http://foo.com/', 'foo'); builder.attribute('lang', 'en', namespace: 'http://foo.com/'); }, ); }, ); }, ); final actual = builder.buildDocument().toString(); const expected = '' '' '' '' ''; expect(actual, expected); }); test('duplicate namespace on element (optimized)', () { final builder = XmlBuilder(optimizeNamespaces: true); builder.element( 'element', nest: () { builder.namespace('http://foo.com/', 'foo'); builder.element( 'outer', nest: () { builder.namespace('http://foo.com/', 'foo'); builder.element('inner', namespace: 'http://foo.com/'); }, ); }, ); final actual = builder.buildDocument().toString(); const expected = '' '' '' '' ''; expect(actual, expected); }); test('namespace defined with element', () { final builder = XmlBuilder(); builder.element( 'element', namespaces: {'http://foo.com/': 'foo', 'http://bar.com/': null}, ); final actual = builder.buildDocument().toString(); const expected = ''; expect(actual, expected); }); test('entities cdata escape', () { final builder = XmlBuilder(); builder.element('element', nest: ''); final xml = builder.buildDocument(); assertDocumentTreeInvariants(xml); final actual = xml.toString(); const expected = '<test><![CDATA[string]]></test>'; expect(actual, expected); }); test('declaration', () { final builder = XmlBuilder(); builder.declaration( version: '0.5', encoding: 'ASCII', attributes: {'foo': 'bar'}, ); builder.element('data'); final xml = builder.buildDocument(); assertDocumentTreeInvariants(xml); final actual = xml.toString(); const expected = ''; expect(actual, expected); }); test('declaration outside of document', () { final builder = XmlBuilder(); expect( () => builder.element('data', nest: builder.declaration), throwsA( isXmlNodeTypeException( message: startsWith('Got XmlNodeType.DECLARATION'), node: isNotNull, types: contains(XmlNodeType.ELEMENT), ), ), ); }); test('exception during nesting', () { final builder = XmlBuilder(); builder.element( 'outer', nest: () { expect( () => builder.element('inner', nest: () => throw UnimplementedError()), throwsUnsupportedError, ); }, ); final document = builder.buildDocument(); expect(document.toString(), ''); }); test('incomplete builder', () { final builder = XmlBuilder(); builder.element( 'element', nest: () { expect(builder.buildDocument, throwsStateError); }, ); final document = builder.buildDocument(); expect(document.toString(), ''); }); test('reused builder', () { final builder = XmlBuilder(); builder.element('element-one'); final firstDocument = builder.buildDocument(); expect(firstDocument.toString(), ''); builder.element('element-two'); final secondDocument = builder.buildDocument(); expect(secondDocument.toString(), ''); }); test('fragment builder', () { final builder = XmlBuilder(); builder.element('element-one'); builder.element('element-two'); final xml = builder.buildFragment(); assertFragmentInvariants(xml); }); }