### ----------------------------------------------------
### -- LibXML
### -- Licenses: Artistic-2.0
### -- Authors: David Warring
### -- File: t/02parse.t
### ----------------------------------------------------
### -- Chunk 1 of 2
use v6;
use Test;
##
# this test checks the parsing capabilities of LibXML
# it relies on the success of t/01basic.t

plan 20;
use LibXML;
use LibXML::Document;
use LibXML::DocumentFragment;
use LibXML::Raw;
use LibXML::Namespace;
use LibXML::Node;
use LibXML::Enums;
use LibXML::SAX::Handler;
use LibXML::SAX::Handler::SAX2;
use LibXML::SAX::Handler::XML;
my \config = LibXML.config;

constant XML_DECL = "<?xml version=\"1.0\"?>\n";

# test values
my @goodWFStrings = (
    '<foobar/>',
    '<foobar></foobar>',
    XML_DECL ~ "<foobar></foobar>",
    '<?xml version="1.0" encoding="UTF-8"?>' ~ "\n<foobar></foobar>",
    '<?xml version="1.0" encoding="ISO-8859-1"?>' ~ "\n<foobar></foobar>",
    XML_DECL ~ "<foobar> </foobar>\n",
    XML_DECL ~ '<foobar><foo/></foobar> ',
    XML_DECL ~ '<foobar> <foo/> </foobar> ',
    XML_DECL ~ '<foobar><![CDATA[<>&"\']]></foobar>',
    XML_DECL ~ '<foobar>&lt;&gt;&amp;&quot;&apos;</foobar>',
    XML_DECL ~ '<foobar>&#x20;&#160;</foobar>',
    XML_DECL ~ '<!--comment--><foobar>foo</foobar>',
    XML_DECL ~ '<foobar>foo</foobar><!--comment-->',
    XML_DECL ~ '<foobar>foo<!----></foobar>',
    XML_DECL ~ '<foobar foo="bar"/>',
    XML_DECL ~ '<foobar foo="\'bar>"/>',
    #XML_DECL ~ '<bar:foobar foo="bar"><bar:foo/></bar:foobar>',
    #'<bar:foobar/>'
);

my @goodWFNSStrings = (
    XML_DECL ~ '<foobar xmlns:bar="xml://foo" bar:foo="bar"/>'~"\n",
    XML_DECL ~ '<foobar xmlns="xml://foo" foo="bar"><foo/></foobar>'~"\n",
    XML_DECL ~ '<bar:foobar xmlns:bar="xml://foo" foo="bar"><foo/></bar:foobar>'~"\n",
    XML_DECL ~ '<bar:foobar xmlns:bar="xml://foo" foo="bar"><bar:foo/></bar:foobar>'~"\n",
    XML_DECL ~ '<bar:foobar xmlns:bar="xml://foo" bar:foo="bar"><bar:foo/></bar:foobar>'~"\n",
);

my @goodWFDTDStrings = (
    XML_DECL ~ '<!DOCTYPE foobar ['~"\n"~'<!ENTITY foo " test ">'~"\n"~']>'~"\n"~'<foobar>&foo;</foobar>',
    XML_DECL ~ '<!DOCTYPE foobar [<!ENTITY foo "bar">]><foobar>&foo;</foobar>',
    XML_DECL ~ '<!DOCTYPE foobar [<!ENTITY foo "bar">]><foobar>&foo;&gt;</foobar>',
    XML_DECL ~ '<!DOCTYPE foobar [<!ENTITY foo "bar=&quot;foo&quot;">]><foobar>&foo;&gt;</foobar>',
    XML_DECL ~ '<!DOCTYPE foobar [<!ENTITY foo "bar">]><foobar>&foo;&gt;</foobar>',
    XML_DECL ~ '<!DOCTYPE foobar [<!ENTITY foo "bar">]><foobar foo="&foo;"/>',
    XML_DECL ~ '<!DOCTYPE foobar [<!ENTITY foo "bar">]><foobar foo="&gt;&foo;"/>',
);

my @badWFStrings = (
    "",                                        # totally empty document
    XML_DECL,                                  # only XML Declaration
    "<!--ouch-->",                             # comment only is like an empty document
    '<!DOCTYPE ouch [<!ENTITY foo "bar">]>',   # no good either ...
    '<ouch>',                                  # single tag (tag mismatch)
    '<ouch/>foo',                              # trailing junk
    'foo<ouch/>',                              # leading junk
    '<ouch foo=bar/>',                         # bad attribute
    '<ouch foo="bar/>',                        # bad attribute
    '<ouch>&</ouch>"=',                        # bad char
    '<ouch>&#0x20;</ouch>',                    # bad char
    '<ouch>&foo;</ouch>',                      # undefind entity
    '<ouch>&gt</ouch>',                        # unterminated entity
    XML_DECL ~ '<!DOCTYPE foobar [<!ENTITY foo "bar">]><foobar &foo;="ouch"/>',          # bad placed entity
    XML_DECL ~ '<!DOCTYPE foobar [<!ENTITY foo "bar=&quot;foo&quot;">]><foobar &foo;/>', # even worse
    '<ouch><!---></ouch>',                     # bad comment
    # see issue #18
    (LibXML.version >= v2.09.10 ?? '<ouch><!-----></ouch>' !! '<ouch><!---></ouch>'),
);


my %goodPushWF = (
    single1 => ['<foobar/>'],
    single2 => ['<foobar>','</foobar>'],
    single3 => [ XML_DECL, "<foobar>", "</foobar>" ],
    single4 => ["<foo", "bar/>"],
    single5 => ["<", "foo","bar", "/>"],
    single6 => ['<?xml version="1.0" encoding="UTF-8"?>',"\n<foobar/>"],
    single7 => ['<?xml',' version="1.0" ','encoding="UTF-8"?>',"\n<foobar/>"],
    single8 => ['<foobar', ' foo=', '"bar"', '/>'],
    single9 => ['<?xml',' versio','n="1.0" ','encodi','ng="U','TF8"?>',"\n<foobar/>"],
    multiple1 => [ '<foobar>','<foo/>','</foobar> ', ],
    multiple2 => [ '<foobar','><fo','o','/><','/foobar> ', ],
    multiple3 => [ '<foobar>','<![CDATA[<>&"\']]>','</foobar>'],
    multiple4 => [ '<foobar>','<![CDATA[', '<>&', ']]>', '</foobar>' ],
    multiple5 => [ '<foobar>','<!','[CDA','TA[', '<>&', ']]>', '</foobar>' ],
    multiple6 => ['<foobar>','&lt;&gt;&amp;&quot;&apos;','</foobar>'],
    multiple6 => ['<foobar>','&lt',';&','gt;&a','mp;','&quot;&ap','os;','</foobar>'],
    multiple7 => [ '<foobar>', '&#x20;&#160;','</foobar>' ],
    multiple8 => [ '<foobar>', '&#x','20;&#1','60;','</foobar>' ],
    multiple9 => [ '<foobar>','moo','moo','</foobar> ', ],
    multiple10 => [ '<foobar>','moo','</foobar> ', ],
    comment1  => [ '<!--comment-->','<foobar/>' ],
    comment2  => [ '<foobar/>','<!--comment-->' ],
    comment3  => [ '<!--','comment','-->','<foobar/>' ],
    comment4  => [ '<!--','-->','<foobar/>' ],
    comment5  => [ '<foobar>fo','o<!---','-><','/foobar>' ],
    attr1     => [ '<foobar',' foo="bar"/>'],
    attr2     => [ '<foobar',' foo','="','bar','"/>'],
    attr3     => [ '<foobar',' fo','o="b','ar"/>'],
    #prefix1   => [ '<bar:foobar/>' ],
    #prefix2   => [ '<bar',':','foobar/>' ],
    #prefix3   => [ '<ba','r:fo','obar/>' ],
    ns1       => [ '<foobar xmlns:bar="xml://foo"/>' ],
    ns2       => [ '<foobar ','xmlns:bar="xml://foo"','/>' ],
    ns3       => [ '<foo','bar x','mlns:b','ar="foo"/>' ],
    ns4       => [ '<bar:foobar xmlns:bar="xml://foo"/>' ],
    ns5       => [ '<bar:foo','bar xm','lns:bar="fo','o"/>' ],
    ns6       => [ '<bar:fooba','r xm','lns:ba','r="foo"','><bar',':foo/','></bar'~':foobar>'],
    dtd1      => [XML_DECL, '<!DOCTYPE ','foobar [','<!ENT','ITY foo " test ">',']>','<foobar>&f','oo;</foobar>',],
    dtd2      => [XML_DECL, '<!DOCTYPE ','foobar [','<!ENT','ITY foo " test ">',']>','<foobar>&f','oo;&gt;</foobar>',],
);

my $goodfile = "samples/dromeds.xml";
my $badfile = "samples/bad.xml";

my LibXML $parser .= new();

sub shorten_string($string is copy) { # Used for test naming.
    $string //= "'undef'";
    $string ~~ s:g/\n/\\n/;
    $string.chars < 25
        ?? $string
        !! $string.substr(0, 10) ~ "..." ~ $string.substr(*-10);
}

# 1 NON VALIDATING PARSER
# 1.1 WELL FORMED STRING PARSING
# 1.1.1 DEFAULT VALUES

subtest 'non validating parser', {
    subtest 'well formed strings', {
        for flat @goodWFStrings, @goodWFNSStrings, @goodWFDTDStrings -> Str $string {
            lives-ok { my LibXML::Document $ = $parser.parse(:$string) }
        }
    }

    dies-ok { $parser.parse(:string(Str)) }, "parse undef string";

    subtest 'invalid strings', {
        for @badWFStrings -> $string {
            throws-like { $parser.parse(:$string); },
            X::LibXML::Parser,
            "Error thrown passing '{shorten_string($string)}'";
        }
    }
}

subtest 'keep-blanks', { 
    temp $parser.keep-blanks = False;

    for flat @goodWFStrings, @goodWFNSStrings, @goodWFDTDStrings -> $string {
	lives-ok {my LibXML::Document:D $ = $parser.parse(:$string);}
    }

    for @badWFStrings -> $string {
        dies-ok { $parser.parse: :$string; }, "keep-blanks error:'{shorten_string($string)}'";
    }
}

subtest 'expand-entities', {
    temp $parser.expand-entities = False;

    for flat @goodWFStrings, @goodWFNSStrings, @goodWFDTDStrings -> $string {
        lives-ok {my LibXML::Document:D $ = $parser.parse: :$string;}
    }

    dies-ok { $parser.parse: :string(Str); };

    for @badWFStrings -> $string {
        dies-ok { $parser.parse: :$string; }, "expand-entities error:'{shorten_string($string)}'";
    }

}

subtest 'pedantic-parser', {
    temp $parser.pedantic-parser = True;

    for flat @goodWFStrings, @goodWFNSStrings, @goodWFDTDStrings -> $string {
        lives-ok { my LibXML::Document:D $ = $parser.parse(:$string);}
    }

    for @badWFStrings -> $string {
        dies-ok { $parser.parse(:$string); }, "pedantic-parser error:'{shorten_string($string)}'";
    }
}

subtest 'parse :file', {
    lives-ok {my LibXML::Document:D $ = $parser.parse(:file($goodfile));}

    throws-like( { $parser.parse(:file($badfile))},
                 X::LibXML::Parser,
                 "Error thrown with bad xml file");

    {
        my $string = "<a>    <b/> </a>";
        my $tstr = "<a><b/></a>\n";
        temp $parser.keep-blanks = False;
        temp $parser.config.skip-xml-declaration = True;
        my $docA = $parser.parse: :$string;
        my $docB = $parser.parse: :file("samples/test3.xml");
        is $docA, $tstr, "xml string round trips as expected";
        is $docB, $tstr, "test3.xml round trips as expected";
    }
}

if $*DISTRO.is-win {
    skip ':io tests failing on Windows';
}
else {
    subtest 'parse :io', {
        my $io = $goodfile.IO;
        isa-ok $io, IO::Path;
        my $doc = $parser.parse: :$io;
        isa-ok $doc, 'LibXML::Document';

        $io .= open(:r, :bin);
        isa-ok $io, IO::Handle;
        lives-ok {my LibXML::Document:D $ = $parser.parse: :$io;}

        $io = $badfile.IO;
        isa-ok $io, IO::Path;
        throws-like(
            { $parser.parse: :$io; },
            X::LibXML::Parser, :message(rx/:s Extra content at the end of the document/),
            "error parsing bad file from file handle of $badfile"
        );
    }
}

subtest 'expand-entities + parse: :file', {
    temp $parser.expand-entities = True;
    my LibXML::Document:D $doc = $parser.parse: :file( "samples/dtd.xml" );

    my LibXML::Element $root = $doc.documentElement;
    my LibXML::Node @cn = $root.childNodes;
    is +@cn, 1, "1 child node";

    temp $parser.expand-entities = False;
    $doc = $parser.parse: :file( "samples/dtd.xml" );
    @cn = $doc.documentElement.childNodes;
    is +@cn, 3, "3 child nodes";

    temp $parser.dtd = True;
    $doc = $parser.parse: :file( "samples/complex/complex2.xml" );
    @cn = $doc.documentElement.childNodes;
    is +@cn, 1, "1 child node";
}

my $goodXInclude = q{
<x>
<xinclude:include
 xmlns:xinclude="http://www.w3.org/2001/XInclude"
 href="test2.xml"/>
</x>
};


my $badXInclude = q{
<x xmlns:xinclude="http://www.w3.org/2001/XInclude">
<xinclude:include href="bad.xml"/>
</x>
};


subtest 'x-include processing', {
    temp $parser.URI = "samples/";
    temp $parser.keep-blanks = False;
    my LibXML::Document $doc = $parser.parse: :string( $goodXInclude );

    my $i;
    lives-ok { $i = $parser.processXIncludes($doc); };
    is-deeply $i, 1, "return value from processXIncludes == 1";

    $doc = $parser.parse: :string( $badXInclude );
    $i = Nil;

    throws-like { $parser.processXIncludes($doc); },
        X::LibXML::Parser,
        :message(rx/'Extra content at the end of the document'/),
        "error parsing a bad include";

    # auto expand
    temp $parser.expand-xinclude = True;
    $doc = $parser.parse: :string( $goodXInclude );

    $doc = Nil;
    throws-like { $doc = $parser.parse: :string( $badXInclude ); },
        X::LibXML::Parser,
        :message(rx/'samples/bad.xml:3: parser error : Extra content at the end of the document'/),
         "error parsing $badfile in include";
    ok !$doc.defined, "no doc returned";

    # some bad stuff
    throws-like { $parser.processXIncludes(Str); },
    X::TypeCheck::Binding::Parameter, "Error parsing undef include";

    throws-like { $parser.processXIncludes("blahblah"); },
    X::TypeCheck::Binding::Parameter, "Error parsing bogus include";
}


subtest 'push parser', {
    my LibXML $pparser .= new();
    subtest 'well-formed documents', {
        for qw<single1 single2 single3 single4 single5 single6
                         single7 single8 single9 multiple1 multiple2 multiple3
                         multiple4 multiple5 multiple6 multiple7 multiple8
                         multiple9 multiple10 comment1 comment2 comment3
                         comment4 comment5 attr1 attr2 attr3
			 ns1 ns2 ns3 ns4 ns5 ns6 dtd1 dtd2> -> $key {
            for %goodPushWF{$key}.list {
                $pparser.parse-chunk( $_ );
            }
            
            lives-ok {my LibXML::Document:D $d = $pparser.parse-chunk("", :terminate); }, "No error parsing $key";
        }
    }

    subtest 'well-formed multiple chunks', {
        my @good_strings = ("<foo>", "bar", "</foo>" );

        my LibXML $parser .= new;

        for @good_strings {
            $parser.parse-chunk( $_ );
        }
        lives-ok {my LibXML::Document $ = $parser.parse-chunk("",:terminate);}
    }

    subtest 'parsing broken documents', {
        my %bad_strings  = (
            predocend1   => ["<A>" ],
            predocend2   => ["<A>", "B"],
            predocend3   => ["<A>", "<C>"],
            predocend4   => ["<A>", "<C/>"],
            postdocend1  => ["<A/>", "<C/>"],
            postdocend2  => ["<A/>", "B"],
            postdocend3  => ["<A/>", "BB"],
            badcdata     => ["<A> ","<!","[CDATA[B]","</A>"],
            badending1   => ["<A> ","B","</C>"],
                            badending2   => ["<A> ","</C>","</A>"],
                       );
        my $doc;
        for %bad_strings.keys.sort -> $key {
            $doc = Nil;
	    my $bad-chunk;
            my $err;
            for @(%bad_strings{$key}) -> $chunk {
                try {
                    $parser.parse-chunk( $chunk );
                    CATCH { default { $err = .message; $bad-chunk = $chunk; } };
                }
                last if $bad-chunk;
            }

            dies-ok { $doc = $parser.parse-chunk('', :terminate)}, "Got an error parsing empty chunk after chunks for $key";
        }

    }

    subtest 'recovering push parser', {
        $parser.init-push;

        for "<A>", "B" {
            $parser.push($_);
        }

        my $doc;
        lives-ok {
            quietly {
            my LibXML::Document:D $ = $parser.finish-push(:recover);
            }
        }, 'RECOVERING PUSH PARSER';
    }
}

use LibXML::SAX;
subtest 'SAX parser', {
    subtest 'XML handler', {
        need XML;
        my LibXML::SAX::Handler::XML $sax-handler .= new;
        my LibXML::SAX $generator .= new: :$sax-handler;

        my $string  = q{<bar foo="bar">foo</bar>};

        lives-ok {my XML::Document $ = $generator.parse: :$string;}, 'SAX PARSER';

    }

    subtest 'SAX2 handler', {
        my LibXML::SAX::Handler::SAX2 $sax-handler .= new;
        my LibXML::SAX $generator .= new: :$sax-handler;

        subtest 'general tests', {
            my $string  = q{<bar foo="bar">foo</bar>};

            lives-ok {my LibXML::Document:D $ = $generator.parse: :$string;}

            for @goodWFStrings -> $string {
                  lives-ok {my LibXML::Document:D $ = $generator.parse: :$string;}
            }
        }

        subtest 'CDATA sections', {
            my $string = q{<foo><![CDATA[&foo<bar]]></foo>};

            my LibXML::Document:D $doc = $generator.parse: :$string;
            my @cn = $doc.documentElement.childNodes();
            is +@cn, 1, "Child nodes - 1" ;
            is @cn[0].nodeType, +XML_CDATA_SECTION_NODE;
            is @cn[0].content, '&foo<bar';
            is @cn[0].Str, '<![CDATA[&foo<bar]]>';
        }

        subtest 'namespaces', {
            my $i = 0;
            for @goodWFNSStrings -> $string {
                my $doc = $generator.parse: :$string;
                isa-ok $doc , LibXML::Document;
                is $doc.Str.subst(/' encoding="UTF-8"'/, ''), $string, "Good NS $i";
                $i++
            }
            # find out if namespaces are there

            my $string2 = q{<foo xmlns:bar="http://foo.bar">bar<bar:bi/></foo>};

            my LibXML::Document $doc = $generator.parse: :string($string2);

            my LibXML::Namespace @namespaces = $doc.documentElement.namespaces;

            is +@namespaces , 1, "1 namespace";
            is @namespaces[0].type, +XML_NAMESPACE_DECL, "Node type: " ~ +XML_NAMESPACE_DECL;

            my LibXML::Element $root = $doc.documentElement;

            my $vstring = q{<foo xmlns:bar="http://foo.bar">bar<bar:bi/></foo>};
            is $root.Str, $vstring;
        }

        subtest 'attributes', {
            my LibXML::Document $doc = $generator.parse: :string(q{<foo bar="x" baz="y" />});
            my LibXML::Node:D $root = $doc.documentElement;
            # attributes as a tied hash
            my $attrs := $root.attributes;
            is +$attrs , 2, "2 attributes";
            # attributes via an iterator
            my LibXML::Attr @props = $root.properties;
            is +@props , 2, "2 properties";
            my $props = $root.properties;
            is $props[0], @props[0];
            is $props[1], @props[1];
            is $props.head, @props[0];
            is $props.tail, @props[1];
            is $attrs<bar>, @props[0];
            is $attrs<baz>, @props[1];
        }

        subtest 'internal subsets', {
            for @goodWFDTDStrings -> $string {
                lives-ok { my LibXML::Document:D $ = $generator.parse: :$string;}
            }
        }

        subtest 'parse :file', {
            lives-ok {my LibXML::Document $ = $generator.parse: :file("samples/test.xml"); }
        }
    }
}

subtest 'SAX push parser', {
    my LibXML::SAX::Handler::SAX2 $sax-handler .= new;
    my LibXML::SAX $parser .= new: :$sax-handler;

    $parser.push( '<foo/>' );
    my $doc = $parser.finish-push;
    isa-ok $doc , 'LibXML::Document';

    for %goodPushWF.keys.sort -> $key {
        for @(%goodPushWF{$key}) {
            $parser.push( $_);
        }

        lives-ok {my LibXML::Document:D $ = $parser.finish-push; }, "sax push parse $key";
    }
}

subtest 'parse well balanced chunks', {
    my $MAX_WF_C = 10;
    my $MAX_WB_C = 16;

    my %chunks = (
                    wellformed1  => '<A/>',
                    wellformed2  => '<A></A>',
                    wellformed3  => '<A B="C"/>',
                    wellformed4  => '<A>D</A>',
                    wellformed5  => '<A><![CDATA[D]]></A>',
                    wellformed6  => '<A><!--D--></A>',
                    wellformed7  => '<A><K/></A>',
                    wellformed8  => '<A xmlns="xml://E"/>',
                    wellformed9  => '<F:A xmlns:F="xml://G" F:A="B">D</F:A>',
                    wellformed10 => '<!--D-->',
                    wellformed11  => '<A xmlns:F="xml://E"/>',
                    wellbalance1 => '<A/><A/>',
                    wellbalance2 => '<A></A><A></A>',
                    wellbalance3 => '<A B="C"/><A B="H"/>',
                    wellbalance4 => '<A>D</A><A>I</A>',
                    wellbalance5 => '<A><K/></A><A><L/></A>',
                    wellbalance6 => '<A><![CDATA[D]]></A><A><![CDATA[I]]></A>',
                    wellbalance7 => '<A><!--D--></A><A><!--I--></A>',
                    wellbalance8 => '<F:A xmlns:F="xml://G" F:A="B">D</F:A><J:A xmlns:J="xml://G" J:A="M">D</J:A>',
                    wellbalance9 => 'D<A/>',
                    wellbalance10=> 'D<A/>D',
                    wellbalance11=> 'D<A/><!--D-->',
                    wellbalance12=> 'D<A/><![CDATA[D]]>',
                    wellbalance13=> '<![CDATA[D]]><A/>D',
                    wellbalance14=> '<!--D--><A/>',
                    wellbalance15=> '<![CDATA[D]]>',
                    wellbalance16=> 'D',
                 );

    my @badWBStrings = (
        "<ouch>",
        "<ouch>bar",
        "bar</ouch>",
        "&",            # bad char
       ## "h\x[e4]h?",         # bad encoding
        "<!--->",       # bad stays bad ;)
        "<!----->",     # bad stays bad ;)
    );


    my LibXML $pparser .= new;

    subtest 'DOM chunk parser', {

        subtest 'well-formed', {
            plan $MAX_WF_C;
            for 1 .. $MAX_WF_C -> $_ is copy {
                my Str:D $string = %chunks{'wellformed' ~ $_};
                my LibXML::DocumentFragment $frag = $pparser.parse-balanced: :$string;
                if $frag.nodeType == +XML_DOCUMENT_FRAG_NODE
                     && $frag.hasChildNodes {
                    if $frag.firstChild.isSameNode($frag.lastChild) {
                        if $string ~~ /'<A></A>'/ {
                            $_--; # because we cannot distinguish between <a/> and <a></a>
                        }

                        is $frag.Str, %chunks{'wellformed' ~ $_}, $string ~ " is well formed";
                        next;
                    }
                }
                flunk("Unexpected fragment without child nodes");
            }
        }

        subtest 'well-balanced', {
            plan $MAX_WB_C;
            for 1 .. $MAX_WB_C -> $_ is copy {
                my Str:D $string = %chunks{'wellbalance' ~ $_};
                my LibXML::DocumentFragment $frag = $pparser.parse-balanced: :$string;
                if $frag.nodeType == +XML_DOCUMENT_FRAG_NODE
                     && $frag.hasChildNodes {
                    if $string ~~ /'<A></A>'/ {
                        $_--;
                    }
                    is $frag.Str, %chunks{'wellbalance' ~ $_}, $string ~ " is well balanced";
                    next;
                }
                flunk("Can't test balancedness");
            }
        }

        subtest 'broken strings', {
            dies-ok { $pparser.parse-balanced: :string(Mu); };

            for @badWBStrings -> $string {
                dies-ok { $pparser.parse-balanced: :$string; }, "parse-balanced fails: $string";
            }
        }
    }

    subtest 'Perl segfault', {

        my $sDoc   = '<C/><D/>';
        my $sChunk = '<A/><B/>';

        my LibXML $parser .= new();
        my LibXML::DocumentFragment $doc = $parser.parse-balanced: :string( $sDoc);
        my  LibXML::DocumentFragment $chk = $parser.parse-balanced: :string( $sChunk);

        my $fc = $doc.firstChild;
        isa-ok $fc, 'LibXML::Element';

        $doc.appendChild( $chk );

        is $doc.Str, '<C/><D/><A/><B/>', 'appendChild';
    }

    subtest 'Perl segfault', {
        # 5.1.2 Segmentation fault tests

        my $sDoc   = '<C/><D/>';
        my $sChunk = '<A/><B/>';

        my LibXML $parser .= new();
        my $doc = $parser.parse-balanced: :string($sDoc);
        my $chk = $parser.parse-balanced: :string($sChunk);

        my $fc = $doc.firstChild;
        isa-ok $fc, 'LibXML::Element';

        $doc.insertAfter( $chk, $fc );

        is $doc.Str, '<C/><A/><B/><D/>', 'insertAfter';
    }

    subtest 'Perl segfault', {
        # 5.1.3 Segmentation fault tests

        my $sDoc   = '<C/><D/>';
        my $sChunk = '<A/><B/>';

        my LibXML $parser .= new();
        my  LibXML::DocumentFragment $doc = $parser.parse-balanced: :string($sDoc);
        my  LibXML::DocumentFragment $chk = $parser.parse-balanced: :string($sChunk);

        my $fc = $doc.firstChild;

        $doc.insertBefore( $chk, $fc );

        is $doc.Str, '<A/><B/><C/><D/>', 'insertBefore' ;
    }

    subtest 'SAX chunk parser', {

        my LibXML::SAX::Handler::SAX2 $sax-handler .= new;
        my LibXML::SAX $parser .= new: :$sax-handler;

        subtest 'well-formed', {
            plan $MAX_WF_C;
             for 1 .. $MAX_WF_C -> $_ is copy {
                my $string = %chunks{'wellformed' ~ $_};
                my  LibXML::DocumentFragment $frag = $parser.parse-balanced: :$string;
                if ( $frag.nodeType == +XML_DOCUMENT_FRAG_NODE
                     && $frag.hasChildNodes ) {
                    if $frag.firstChild.isSameNode( $frag.lastChild ) {
                        if $string ~~ /'<A></A>'/ {
                            $_--;
                        }
                        is $frag.Str, %chunks{'wellformed' ~ $_}, $string ~ ' is well formed';
                        next;
                    }
                }
                flunk("Couldn't pass well formed test since frag was bad");
            }
        }

        subtest 'well-balanced', {
            plan $MAX_WB_C;
            for 1 .. $MAX_WB_C -> $_ is copy {
                my Str:D $string = %chunks{'wellbalance' ~ $_};
                my  LibXML::DocumentFragment $frag = $parser.parse-balanced: :$string;
                if ( $frag.nodeType == XML_DOCUMENT_FRAG_NODE
                     && $frag.hasChildNodes ) {
                    if $string ~~ /'<A></A>'/ {
                        $_--;
                    }
                    is $frag.Str, %chunks{'wellbalance' ~ $_}, $string ~ " is well balanced";
                    next;
                }
                flunk("Couldn't pass well balanced test since frag was bad");
            }
        }
    }
}

subtest 'validating parser', {
    my %badstrings = (
                    SIMPLE => '<?xml version="1.0"?>'~"\n<A/>\n",
                  );
    my LibXML $parser .= new;

    $parser.validation = True;
    dies-ok {my  $doc = $parser.parse: :string(%badstrings<SIMPLE>); }, "Failed to parse SIMPLE bad string";
}

subtest 'line numbers', {
    my $goodxml = q:to<EOXML>;
    <?xml version="1.0"?>
    <foo>
        <bar/>
    </foo>
    EOXML

    my $badxml = q:to<EOXML>;
    <?xml version="1.0"?>
    <!DOCTYPE foo [<!ELEMENT foo EMPTY>]>
    <bar/>
    EOXML

    my LibXML $parser .= new;
    $parser.validation = True;

    throws-like { $parser.parse: :string( $badxml ); },
    X::LibXML::Parser,
    # correct line number may or may not be present
    # depending on libxml2 version
    :message(rx/^^\:<[03]>\:/), "line 03 found in error";

    $parser.line-numbers = True;
    throws-like { $parser.parse: :string( $badxml ); },
    X::LibXML::Parser, :message(rx/^^\:3\:/), "line 3 found in error";

    # switch off validation for the following tests
    temp $parser.validation = False;

    my LibXML::Document $doc;
    lives-ok { $doc = $parser.parse: :string( $goodxml ); };

    my $root = $doc.documentElement();
    is $root.line-number(), 2, "line number is 2";

    my LibXML::Node @kids = $root.childNodes();
    is @kids[1].line-number(),3, "line number is 3";

    my $newkid = $root.appendChild( $doc.createElement( "bar" ) );
    is $newkid.line-number(), 0, "line number is 0";

    $parser.line-numbers = False;
    lives-ok { $doc = $parser.parse: :string( $goodxml ); };

    $root = $doc.documentElement();
    is $root.line-number(), 0, "line number is 0";

    @kids = $root.childNodes();
    is @kids[1].line-number(), 0, "line number is 0";
}

subtest 'clean namespaces', {
    my Str ( $xsDoc1, $xsDoc2 );
    my Str $fn1 = "samples/xmlns/goodguy.xml";
    my Str $fn2 = "samples/xmlns/badguy.xml";

    $xsDoc1 = q{<A:B xmlns:A="http://D"><A:C xmlns:A="http://D"></A:C></A:B>};
    $xsDoc2 = q{<A:B xmlns:A="http://D"><A:C xmlns:A="http://E"/></A:B>};

    my LibXML $parser .= new();
    $parser.clean-namespaces = True;

    is( $parser.parse(:string( $xsDoc1 )).documentElement.Str,
        q{<A:B xmlns:A="http://D"><A:C/></A:B>}, "string ns parse" );
    is( $parser.parse(:string( $xsDoc2 )).documentElement.Str,
        $xsDoc2, "string ns parse" );

    is( $parser.parse(:file($fn1)).documentElement.Str,
        q{<A:B xmlns:A="http://D"><A:C/></A:B>}, "file ns parse" );
    is( $parser.parse(:file($fn2)).documentElement.Str,
        $xsDoc2, "file ns parse" );

if $*DISTRO.is-win {
    skip ':io tests failing on Windows', 2;
}
else {
    my $fh1 = $fn1.IO;
    my $fh2 = $fn2.IO;

    is( $parser.parse(:io($fh1)).documentElement,
        q{<A:B xmlns:A="http://D"><A:C/></A:B>}, "io ns parse" );
    is( $parser.parse(:io($fh2 )).documentElement ,
        $xsDoc2, "io ns parse" );
}

    my @xaDoc1 = ('<A:B xmlns:A="http://D">','<A:C xmlns:A="h','ttp://D"/>' ,'</A:B>');
    my @xaDoc2 = ('<A:B xmlns:A="http://D">','<A:C xmlns:A="h','ttp://E"/>' , '</A:B>');

    my $doc;

    for @xaDoc1 {
        $parser.parse-chunk($_);
    }
    $doc = $parser.parse-chunk: :terminate;

    is( $doc.documentElement,
        q{<A:B xmlns:A="http://D"><A:C/></A:B>}, "chunk ns parse" );


    for @xaDoc2 {
        $parser.parse-chunk( $_ );
    }
    $doc = $parser.parse-chunk: :terminate;
    is $doc.documentElement, $xsDoc2, "chunk ns parse";
};

subtest 'external subset loading', {
    my $xmldoc = q:to<EOXML>;
    <!DOCTYPE X SYSTEM "samples/ext_ent.dtd">
    <X>&foo;</X>
    EOXML
    my LibXML $parser .= new;

    $parser.load-ext-dtd = True;

    # first time it should work
    my $doc    = $parser.parse: :string( $xmldoc );
    is $doc.documentElement.string-value(), " test ";

    #