### ----------------------------------------------------
### -- LibXML
### -- Licenses: Artistic-2.0
### -- Authors: David Warring
### -- File: lib/LibXML/Raw.rakumod
### ----------------------------------------------------
### -- Chunk 1 of 4
use v6;

#| Bindings to the libxml2 library
unit class LibXML::Raw;

=begin pod

=head2 Synopsis

    do {
        # Create a document from scratch
        use LibXML::Raw;
        my xmlDoc:D $doc .= new;
        my xmlElem:D $root = $doc.new-node: :name<Hello>, :content<World!>;
        .Reference for $doc, $root;
        $doc.SetRootElement($root);
        say $doc.Str; # .. <Hello>World!</Hello>
        # unreference/destroy before we go out of scope
        .Unreference for $root, $doc;
    }

=head2 Description

The LibXML::Raw module contains class definitions for native bindings to the LibXML2 library.

=head3 Low level native access

Other high level classes, by convention, have a `raw()` accessor, which can be
used, if needed, to gain access to native objects from this module.

Some care needs to be taken in keeping persistent references to raw structures.

The following is unsafe:

   my LibXML::Element $elem .= new: :name<Test>;
   my xmlElem:D $raw = $elem.raw;
   $elem = Nil;
   say $raw.Str; # could have been destroyed along with $elem

If the raw object supports the `Reference` and `Unreference` methods, the object can be reference counted and uncounted:

   my LibXML::Element $elem .= new: :name<Test>;
   my xmlElem:D $raw = $elem.raw;
   $raw.Reference; # add a reference to the object
   $elem = Nil;
   say $raw.Str; # now safe
   with $raw {
       .Unreference; # unreference, free if no more references
       $_ = Nil;
   }

Otherwise, the object can usually be copied. That copy then needs to be freed, to avoid memory leaks:

  my LibXML::Namespace $ns .= new: :prefix<foo>, :URI<http://foo.org>;
  my xmlNs:D $raw = $ns.raw;
  $raw .= Copy;
  $ns = Nil;
  say $raw.Str; # safe
  with $raw {
      .Free; # free the copy
      $_ = Nil;
  }

=end pod

use NativeCall;
use LibXML::Enums;
use LibXML::Raw::Dict;
use LibXML::Raw::HashTable;
use LibXML::Raw::DOM::Attr;
use LibXML::Raw::DOM::Document;
use LibXML::Raw::DOM::Element;
use LibXML::Raw::DOM::Node;
use Method::Also;

our @ClassMap;

use LibXML::Raw::Defs :$XML2, :$BIND-XML2, :Opaque, :xmlCharP;

sub xmlParserVersion is export { cglobal($XML2, 'xmlParserVersion', Str); }

module xml6_config is export {
    our sub have_threads(-->int32) is native($BIND-XML2) is symbol('xml6_config_have_threads') is export {*}
    our sub have_compression(-->int32) is native($BIND-XML2) is symbol('xml6_config_have_compression') is export {*}
    our sub have_iconv(-->int32) is native($BIND-XML2) is symbol('xml6_config_have_iconv') is export {*}
    our sub version(--> Str) is native($BIND-XML2) is symbol('xml6_config_version') is export {*};
}

our sub ref-current(-->int32) is native($BIND-XML2) is symbol('xml6_ref_current') {*}
our sub ref-total(-->int32) is native($BIND-XML2) is symbol('xml6_ref_total') {*}

module xml6_gbl {...}

module CLib {
    use LibXML::Raw::Defs :$CLIB;
    our sub memcpy(Blob:D, Pointer:D, size_t) is native($CLIB) {*}
    our sub free(Pointer:D) is native($CLIB) {*}
}

# Pointer to string, expected to be freed by the caller
class xmlAllocedStr is Pointer is repr('CPointer') {
    method Str {
        nativecast(Str, self);
    }
    submethod DESTROY {
        xml6_gbl::xml-free(self);
    }
}

# forward declarations
class anyNode        is repr('CStruct') is export {...}
class itemNode       is repr('CStruct') is export {...}
class xmlAttr        is repr('CStruct') is export {...}
class xmlAttrDecl    is repr('CStruct') is export {...}
class xmlDtd         is repr('CStruct') is export {...}
class xmlDoc         is repr('CStruct') is export {...}
class xmlDocFrag     is repr('CStruct') is export {...}
class xmlElem        is repr('CStruct') is export {...}
class xmlElementDecl is repr('CStruct') is export {...}
class xmlEntity      is repr('CStruct') is export {...}
class xmlError       is repr('CStruct') is export {...}
class xmlNode        is repr('CStruct') is export {...}
class xmlNodeSet     is repr('CStruct') is export {...}
class xmlNotation    is repr('CStruct') is export {...}
class xmlParserCtxt  is repr('CStruct') is export {...}
class xmlParserInput is repr('CStruct') is export {...}
class xmlSAXLocator  is repr('CStruct') is export {...}
class xmlXPathParserContext
                     is repr('CStruct') is export {...}
class xmlXPathObject is repr('CStruct') is export {...}

module xml6_gbl is export {

    our sub cache-size(-->int32) is native($BIND-XML2) is symbol('xml6_gbl_cache_size') {*}

    our sub save-error-handlers(--> Pointer) is symbol('xml6_gbl_save_error_handlers') is native($BIND-XML2) is export {*}
    our sub restore-error-handlers(Pointer) is symbol('xml6_gbl_restore_error_handlers') is native($BIND-XML2) is export {*}

    our sub set-generic-error-handler( &callb (Str $msg), Pointer $setter) is native($BIND-XML2) is symbol('xml6_gbl_set_generic_error_handler') {*}

    our sub get-keep-blanks(--> int32) is symbol('xml6_gbl_os_thread_get_keep_blanks') is native($BIND-XML2) is export { * }
    our sub set-keep-blanks(int32 $v) is symbol('xml6_gbl_os_thread_set_keep_blanks') is native($BIND-XML2) is export { * }

    our sub get-tag-expansion(--> int32) is symbol('xml6_gbl_os_thread_get_tag_expansion') is native($BIND-XML2) is export { * }
    our sub set-tag-expansion(int32 $v) is symbol('xml6_gbl_os_thread_set_tag_expansion') is native($BIND-XML2) is export { * }

    our sub get-external-entity-loader( --> Pointer ) is native($BIND-XML2) is symbol('xml6_gbl_get_external_entity_loader') {*}
    our sub set-external-entity-loader( Pointer --> xmlParserInput) is native($BIND-XML2) is symbol('xml6_gbl_set_external_entity_loader') {*}

    our sub get-default-sax-locator( --> xmlSAXLocator ) is native($BIND-XML2) is symbol('xml6_gbl_get_default_sax_locator') {*}

    our sub xml-free(Pointer) is symbol('xml6_gbl_os_thread_xml_free') is native($BIND-XML2) is export {*}
    our sub init() is symbol('xml6_gbl_init') is native($BIND-XML2) is export {*}
}

# Opaque structs
#| A libxml automata description, It can be compiled into a regexp
class xmlAutomata is repr(Opaque) is export {}

#| A state int the automata description,
class xmlAutomataState is repr(Opaque) is export {}

#| old buffer struct limited to 32bit signed addressing (2Gb). xmlBuf is preferred, where available
class xmlBuffer32 is repr(Opaque) is export {
    our sub New( --> xmlBuffer32) is native($XML2) is symbol('xmlBufferCreate') is export {*};
    method Write(xmlCharP --> int32) is native($XML2) is symbol('xmlBufferCat') {*}
    method WriteQuoted(xmlCharP --> int32) is native($XML2) is symbol('xmlBufferWriteQuotedString') {*}
    method NodeDump(xmlDoc $doc, anyNode $cur, int32 $level, int32 $format --> int32) is native($XML2) is symbol('xmlNodeDump') {*};
    method Content(--> Pointer) is symbol('xmlBufferContent') is native($XML2) { * }
    method Length(--> int32) is symbol('xmlBufferLength') is native($XML2) { * }
    method NotationDump(xmlNotation) is native($XML2) is symbol('xmlDumpNotationDecl') {*};
    method Blob {
        my buf8 $buf;
        my $size = self.Length;
        fail "buffer size $size < 0" unless $size >= 0;
        if $size {
            $buf .= allocate: $size;
            my Pointer $content = self.Content
                || fail "Null Buffer content";
            CLib::memcpy($buf, $content, $size);
        }
        $buf;
    }
    method Str { self.Blob.decode }
    method Free() is native($XML2) is symbol('xmlBufferFree') {*};
    method new(--> xmlBuffer32:D) { New() }
}

#| New buffer structure, introduced in libxml 2.09.00, the actual structure internals are not public
class xmlBuf is repr(Opaque) is export {
    our sub New(size_t --> xmlBuf) is native($XML2) is symbol('xmlBufCreate') {*}
    method Write(xmlCharP --> int32) is native($XML2) is symbol('xmlBufCat') {*}
    method WriteQuoted(xmlCharP --> int32) is native($XML2) is symbol('xmlBufWriteQuotedString') {*}
    method NodeDump(xmlDoc $doc, anyNode $cur, int32 $level, int32 $format --> int32) is native($XML2) is symbol('xmlBufNodeDump') { * }
    method Content(--> Pointer) is symbol('xmlBufContent') is native($XML2) { * }
    method Length(--> int32) is symbol('xmlBufLength') is native($XML2) { * }
    method Blob {
        my buf8 $buf;
        my $size = self.Length;
        fail "buffer size $size < 0" unless $size >= 0;
        if $size {
            $buf .= allocate: $size;
            my Pointer $content = self.Content
                || fail "Null Buffer content";
            CLib::memcpy($buf, $content, $size);
        }
        $buf;
    }
    method Str { self.Blob.decode }
    method Free is symbol('xmlBufFree') is native($XML2) { * }
    method new(UInt:D :$size = 0 --> xmlBuf:D) { New($size) }
}

# type defs
class xmlCharEncodingHandler is repr(Opaque) is export {
    our sub Find(Str --> xmlCharEncodingHandler) is native($XML2) is symbol('xmlFindCharEncodingHandler') {*}
}

# subsets
sub xmlParseCharEncoding(Str --> int32) is export is native($XML2) {*}
my subset xmlEncodingStr of Str is export where {!.defined || xmlParseCharEncoding($_).defined}


#| List structure used when there is an enumeration in DTDs.
class xmlEnumeration is repr('CStruct') is export {
    has xmlEnumeration $.next;
    has xmlCharP $.value;
}

#| A Location Set
class xmlLocationSet is repr(Opaque) is export {}

#| Callback for freeing some parser input allocations.
class xmlParserInputDeallocate is repr(Opaque) is export {}

#| The parser can be asked to collect Node information, i.e. at what
#| place in the file they were detected.
class xmlParserNodeInfo is repr(Opaque) is export {}

#| The structure of a compiled expression form is not public.
class xmlXPathCompExpr is repr(Opaque) is export {
    our sub Compile(xmlCharP:D --> xmlXPathCompExpr)  is symbol('xmlXPathCompile') is native($XML2) {*}
    method Free is native($XML2) is symbol('xmlXPathFreeCompExpr') {*}
    method new(Str:D :$expr) {
        Compile($expr);
    }
}

#| A compiled (XPath based) pattern to select nodes
class xmlPattern is repr(Opaque) is export {
    method Match(anyNode --> int32) is native($XML2) is symbol('xmlPatternMatch') {*}
    our sub Compile (xmlCharP, xmlDict, int32, CArray[xmlCharP] --> xmlPattern) is symbol('xmlPatterncompile') is native($XML2) {*}
    method Free is native($XML2) is symbol('xmlFreePattern') {*}
    method new(Str:D :$pattern!, xmlDict :$dict, UInt :$flags, CArray[xmlCharP] :$ns) {
        Compile($pattern, $dict, $flags, $ns);
    }
}

#| A libxml regular expression, they can actually be far more complex
#| thank the POSIX regex expressions.
class xmlRegexp is repr(Opaque) is export {
    our sub Compile(xmlCharP --> xmlRegexp) is symbol('xmlRegexpCompile') is native($XML2) {*}
    method Match(xmlCharP --> int32) is symbol('xmlRegexpExec') is native($XML2) {*}
    method IsDeterministic(--> int32) is symbol('xmlRegexpIsDeterminist') is native($XML2) {*}
    method Free is native($XML2) is symbol('xmlRegFreeRegexp') {*}
    method new(Str:D :$regexp) {
        Compile($regexp);
    }
}

#| An XInclude context (libxml v2.13.00+)
class xmlXIncludeCtxt is repr(Opaque) is export {
    our sub New(xmlDoc --> xmlXIncludeCtxt) is native($XML2) is symbol('xmlXIncludeNewContext') {*}
    method new(xmlDoc:D :$doc!) { New($doc) }
    method SetFlags(int32) is native($XML2) is symbol('xmlXIncludeSetFlags') {*}
    # recommended libxml 2.13.0+
    method SetErrorHandler(&error-func (xmlXIncludeCtxt $, xmlError $)) is native($XML2) is symbol('xmlXIncludeSetErrorHandler') {*};
    # legacy
    method SetStructuredErrorFunc(&error-func (xmlXIncludeCtxt $, xmlError $)) is native($XML2) is symbol('xmlSetStructuredErrorFunc') {*};
    method ProcessNode(xmlNode --> int32) is native($XML2) is symbol('xmlXIncludeProcessNode') {*}
    method Free()  is native($XML2) is symbol('xmlXIncludeFreeContext') {*}
}

#| A mapping of name to axis function
class xmlXPathAxis is repr(Opaque) is export {}

#|  A mapping of name to conversion function
class xmlXPathType is repr(Opaque) is export {}

#| Each xmlValidState represent the validation state associated to the
#| set of nodes currently open from the document root to the current element.
class xmlValidState is repr(Opaque) is export {}

multi trait_mod:<is>(Attribute $att, :&rw-ptr!) {

    my role PointerSetterHOW[&setter] {
        # override standard Attribute method for generating accessors
        method compose(Mu $package) {
            my $name = self.name.subst(/^(\$|\@|\%)'!'/, '');
            sub accessor(\obj) is rw {
                sub FETCH($) { self.get_value(obj) }
                sub STORE($, $val is raw) {
                    setter(obj, $val);
                }
                Proxy.new: :&FETCH, :&STORE;
            }
            try $package.^add_method( $name, &accessor );
        }
    }

    $att does PointerSetterHOW[&rw-ptr]
}

multi trait_mod:<is>(Attribute $att, :&rw-str!) {

    my role StringSetterHOW[&setter] {
        method compose(Mu $package) {
            my $name = self.name.subst(/^(\$|\@|\%)'!'/, '');
            my sub accessor(\obj) is rw {
                sub FETCH($) { self.get_value(obj) }
                sub STORE($, $val) {
                    my $str := do with $val {.Str} else { Str };
                    setter(obj, $str);
                }
                Proxy.new: :&FETCH, :&STORE;
            }
            try $package.^add_method( $name, &accessor );
        }
    }

    $att does StringSetterHOW[&rw-str]
}

class xmlParserInputBuffer is repr('CStruct') is export {
    my constant xmlInputReadCallback = Pointer;
    my constant xmlInputWriteCallback = Pointer;
    has Pointer $.context;
    has xmlInputReadCallback $.read-callback;
    has xmlInputWriteCallback $.write-callback;
    has xmlCharEncodingHandler $.encoder;
    has xmlBuf $.buffer;
    has xmlBuf $.raw;
    has int32  $.compressed;
    has int32  $.error;
    has ulong  $.raw-consumed;

    our sub Alloc(int32 $enc --> xmlParserInputBuffer) is native($XML2) is symbol('xmlAllocParserInputBuffer') {*}
    method new(xmlEncodingStr :$enc, xmlCharP :$string) {
        my Int $encoding = xmlParseCharEncoding($enc);
        given Alloc($encoding) {
             if $string {
                 my $n := .PushStr($string);
                 die "push to input buffer failed"
                     if $n < 0;
             }
            $_;
        }
    }
    method PushStr(xmlCharP:D --> int32) is native($BIND-XML2) is symbol('xml6_input_buffer_push_str') {*}
}

#|An xmlParserInput is an input flow for the XML processor.
#| Each entity parsed is associated an xmlParserInput (except the
#| few predefined ones). 
class xmlParserInput is export {
    has xmlParserInputBuffer           $.buf;  # UTF-8 encoded buffer
    has Str                       $.filename   # The file analyzed, if any
          is rw-str(method xml6_input_set_filename(Str) is native($BIND-XML2) {*});
    has Str                       $.directory; # the directory/base of the file
    has xmlCharP                       $.base; # Base of the array to parse
    has xmlCharP                        $.cur; # Current char being parsed
    has xmlCharP                        $.end; # end of the array to parse
    has int32                        $.length; # length if known
    has int32                          $.line; # Current line
    has int32                           $.col; # Current column
    has ulong                      $.consumed; # How many xmlChars already consumed
    has xmlParserInputDeallocate       $.free; # function to deallocate the base
    has xmlCharP                   $.encoding; # the encoding string for entity
    has xmlCharP                    $.version; # the version string for entity
    has int32                    $.standalone; # Was that entity marked standalone
    has int32                            $.id; # a unique identifier for the entity

    # since libxml 2.14.0
    our sub NewFromString(Str $url, Str $str, int32 $flags --> ::?CLASS) is native($XML2) is symbol('xmlNewInputFromString') {*}
    method Free is native($XML2) is symbol('xmlFreeInputStream') {*}
    method new(Str:D :$string!, Str :$url, UInt:D :$flags = 0) {
        NewFromString($url, $string, $flags);
    }
}

#| An XML Element content as stored after parsing an element definition
#| in a DTD.
class xmlElementContent is repr('CStruct') is export {
    has int32             $.type;   # PCDATA, ELEMENT, SEQ or OR
    has int32             $.occurs; # ONCE, OPT, MULT or PLUS
    has xmlCharP          $.name;   # Element name
    has xmlElementContent $.c1;     # First child
    has xmlElementContent $.c2;     # Second child
    has xmlElementContent $.parent; # Parent
    has xmlCharP          $.prefix; # Namespace prefix
    method PotentialChildren(CArray[Pointer], int32 $len is rw, int32 $max --> int32)  is native($XML2) is symbol('xmlValidGetPotentialChildren') {*}
    our sub Dump(Blob, int32, xmlElementContent, bool) is native($XML2) is symbol('xmlSnprintfElementContent') {*}
    method Str(UInt :$max = 255, Bool:D :$paren = so ($!type == XML_ELEMENT_CONTENT_SEQ|XML_ELEMENT_CONTENT_OR)) {
        my buf8 $buf .= allocate($max);
        Dump($buf, $max, self, +$paren);
        nativecast(Str, $buf);
    }
}

role domNode[$class, UInt:D $type] is export {
    @ClassMap[$type] = $class;
    method delegate {
        fail "expected node of type $type, got {self.type}"
            unless self.type == $type;
        self
    }
}

#| An XML namespace.
#| Note that prefix == NULL is valid, it defines the default namespace
#| within the subtree (until overridden).
class xmlNs is export is repr('CStruct') {
    also does domNode[$?CLASS, XML_NAMESPACE_DECL];

    has xmlNs        $.next; # next Ns link for this node
    has int32        $.type; # global or local (enum xmlNsType)
    has xmlCharP     $.href; # URL for the namespace
    has xmlCharP   $.prefix; # prefix for the namespace
    has Pointer  $._private; # application data
    has xmlDoc    $.context; # normally an xmlDoc

    method new(Str:D :$URI!, Str :$prefix, xmlElem :$node) {
        $node.NewNs($URI, $prefix);
    }
    method Free is native($XML2) is symbol('xmlFreeNs') {*}
    method Copy(--> xmlNs) is native($BIND-XML2) is symbol('xml6_ns_copy') {*}
    method copy { $.Copy }
    method next-node($) { self.next }
    method UniqueKey(--> xmlCharP) is native($BIND-XML2) is symbol('xml6_ns_unique_key') {*}
    method Str {
        nextsame without self;
        nextsame if self.prefix ~~ 'xml';
        # approximation of xmlsave.c static function: xmlNsDumpOutput(...)
        my xmlBuffer32 $buf .= new;

        $buf.Write('xmlns');
        $buf.Write(':' ~ $_)
            with self.prefix;

        with self.href {
            $buf.Write('=');
            $buf.WriteQuoted($_);
        }

        my Str $content = $buf.Str;
        $buf.Free;
        $content;
    }
    method ItemNode { nativecast(itemNode, self) }
}

#| A SAX Locator.
class xmlSAXLocator is export {
    has Pointer  $.getPublicIdFunc is rw-ptr(
        method xml6_sax_locator_set_getPublicId( &cb (xmlParserCtxt $ctx) ) is native($BIND-XML2) {*}
    );

    has Pointer $.getSystemIdFunc is rw-ptr(
        method xml6_sax_locator_set_getSystemId( &cb (xmlParserCtxt $ctx) ) is native($BIND-XML2) {*}
    );

    has Pointer $.getLineNumberFunc is rw-ptr(
        method xml6_sax_locator_set_getLineNumber( &cb (xmlParserCtxt $ctx --> int32) ) is native($BIND-XML2) {*}
    );

    has Pointer $.getColumnNumberFunc is rw-ptr(
        method xml6_sax_locator_set_getColumnNumber( &cb (xmlParserCtxt $ctx --> int32) ) is native($BIND-XML2) {*}
    );

    submethod BUILD(*%atts) {
        for %atts.pairs.sort {
            self."{.key}"() = .value;
        }
    }

    method getPublicId(xmlParserCtxt $ctx) {
        with nativecast(:(xmlParserCtxt $ctx --> xmlCharP), $!getPublicIdFunc) -> &cb {
            &cb($ctx)
        }
    }

    method getSystemId(xmlParserCtxt $ctx --> xmlCharP) {
        with nativecast(:(xmlParserCtxt $ctx --> int32), $!getSystemIdFunc) -> &cb {
            &cb($ctx)
        }
    }

    method getLineNumber(xmlParserCtxt $ctx) {
        with nativecast(:(xmlParserCtxt $ctx --> int32), $!getLineNumberFunc) -> &cb {
            &cb($ctx)
        }
    }

    method getColumnNumber(xmlParserCtxt $ctx) {
        with nativecast(:(xmlParserCtxt $ctx --> int32), $!getColumnNumberFunc) -> &cb {
            &cb($ctx)
        }
    }

    method default {
         xml6_gbl::get-default-sax-locator();
    }
}

#| A SAX handler is bunch of callbacks called by the parser when processing
#| of the input generate data or structure information.
class xmlSAXHandler is repr('CStruct') is export {

    submethod BUILD(*%atts) {
        for %atts.pairs.sort {
            self."{.key}"() = .value;
        }
    }
    method raw { self } # already raw

    has Pointer   $.internalSubset is rw-ptr(
        method xml6_sax_set_internalSubset( &cb (xmlParserCtxt $ctx, Str $name, Str $external-id, Str $system-id) ) is native($BIND-XML2) {*}
    );
    has Pointer   $.isStandalone is rw-ptr(
        method xml6_sax_set_isStandalone( &cb (xmlParserCtxt $ctx --> int32) ) is native($BIND-XML2) {*}
    );
    has Pointer   $.hasInternalSubset is rw-ptr(
        method xml6_sax_set_hasInternalSubset( &cb (xmlParserCtxt $ctx --> int32) ) is native($BIND-XML2) {*}
    );
    has Pointer   $.hasExternalSubset is rw-ptr(
        method xml6_sax_set_hasExternalSubset( &cb (xmlParserCtxt $ctx --> int32) ) is native($BIND-XML2) {*}
    );
    has Pointer   $.resolveEntity is rw-ptr(
        method xml6_sax_set_resolveEntity( &cb (xmlParserCtxt $ctx, Str $name, Str $public-id, Str $system-id --> xmlParserInput) ) is native($BIND-XML2) {*}
    );
    has Pointer   $.getEntity is rw-ptr(
        method xml6_sax_set_getEntity( &cb (xmlParserCtxt $ctx, Str $name --> xmlEntity) ) is native($BIND-XML2) {*}
    );
    has Pointer   $.entityDecl is rw-ptr(
        method xml6_sax_set_entityDecl( &cb (xmlParserCtxt $ctx, Str $name, uint32 $type, Str $public-id, Str $system-id, Str $content) ) is native($BIND-XML2) {*}
    );
    has Pointer   $.notationDecl is rw-ptr(
        method xml6_sax_set_notationDecl( &cb (xmlParserCtxt $ctx, Str $name, Str $public-id, Str $system-id) ) is native($BIND-XML2) {*}
    );
    has Pointer   $.attributeDecl is rw-ptr(
        # todo xmlEnumeration $tree
        method xml6_sax_set_attributeDecl( &cb (xmlParserCtxt $ctx, Str $elem, Str $fullname, uint32 $type, uint32 $def, Str $default-value, xmlEnumeration $tree) ) is native($BIND-XML2) {*}
    );
    has Pointer   $.elementDecl is rw-ptr(
        method xml6_sax_set_elementDecl( &cb (xmlParserCtxt $ctx, Str $name, uint32 $type, xmlElementContent $content) ) is native($BIND-XML2) {*}
    );
    has Pointer   $.unparsedEntityDecl is rw-ptr(
        method xml6_sax_set_unparsedEntityDecl( &cb (xmlParserCtxt $ctx, Str $name, Str $public-id, Str $system-id, Str $notation-name) ) is native($BIND-XML2) {*}
    );
    has Pointer   $.setDocumentLocator is rw-ptr(
        method xml6_sax_set_setDocumentLocator( &cb (xmlParserCtxt $ctx, xmlSAXLocator $loc) ) is native($BIND-XML2) {*}
    );
    has Pointer   $.startDocument is rw-ptr(
        method xml6_sax_set_startDocument( &cb (xmlParserCtxt $ctx) ) is native($BIND-XML2) {*}
    );
    has Pointer   $.endDocument is rw-ptr(
        method xml6_sax_set_endDocument( &cb (xmlParserCtxt $ctx) ) is native($BIND-XML2) {*}
    );

    has Pointer   $.startElement is rw-ptr(
        method xml6_sax_set_startElement( &cb (xmlParserCtxt $ctx, Str $name, CArray[Str] $atts) ) is native($BIND-XML2) {*}
    );
    
    has Pointer   $.endElement is rw-ptr(
        method xml6_sax_set_endElement( &cb (xmlParserCtxt $ctx, Str $name) ) is native($BIND-XML2) {*}
    );
    has Pointer   $.reference is rw-ptr(
        method xml6_sax_set_reference( &cb (xmlParserCtxt $ctx, Str $name) ) is native($BIND-XML2) {*}
    );
    has Pointer   $.characters is rw-ptr(
        method xml6_sax_set_characters( &cb (xmlParserCtxt $ctx, CArray[byte] $chars, int32 $len) ) is native($BIND-XML2) {*}
    );
    has Pointer   $.ignorableWhitespace is rw-ptr(
        method xml6_sax_set_ignorableWhitespace( &cb (xmlParserCtxt $ctx, CArray[byte] $chars, int32 $len) ) is native($BIND-XML2) {*}
    );
    has Pointer   $.processingInstruction is rw-ptr(
        method xml6_sax_set_processingInstruction( &cb (xmlParserCtxt $ctx, Str $target, Str $data) ) is native($BIND-XML2) {*}
    );
    has Pointer   $.comment is rw-ptr(
        method xml6_sax_set_comment( &cb (xmlParserCtxt $ctx, Str $value) ) is native($BIND-XML2) {*}
    );
    has Pointer   $.warning is rw-ptr(
        method xml6_sax_set_warning( &cb (xmlParserCtxt $ctx, Str $msg) ) is native($BIND-XML2) {*}
    );
    has Pointer   $.error is rw-ptr(
        method xml6_sax_set_error( &cb (xmlParserCtxt $ctx, Str $msg) ) is native($BIND-XML2) {*}
    );
    has Pointer   $.fatalError is rw-ptr(
        method xml6_sax_set_fatalError( &cb (xmlParserCtxt $ctx, Str $msg) ) is native($BIND-XML2) {*}
    );
    has Pointer   $.getParameterEntity is rw-ptr(
        method xml6_sax_set_getParameterEntity( &cb (xmlParserCtxt $ctx, Str $name) ) is native($BIND-XML2) {*}
    );
    has Pointer   $.cdataBlock is rw-ptr(
        method xml6_sax_set_cdataBlock( &cb (xmlParserCtxt $ctx, CArray[byte] $chars, int32 $len) ) is native($BIND-XML2) {*}
    );
    has Pointer   $.externalSubset is rw-ptr(
        method xml6_sax_set_externalSubset( &cb (xmlParserCtxt $ctx, Str $name, Str $external-id, Str $system-id) ) is native($BIND-XML2) {*}
    );
    has uint32    $.initialized;
    has Pointer   $._private;
    has Pointer   $.startElementNs is rw-ptr(
        method xml6_sax_set_startElementNs( &cb (xmlParserCtxt $ctx, Str $local-name, Str $prefix, Str $uri, int32 $num-namespaces, CArray[Str] $namespaces, int32 $num-attributes, int32 $num-defaulted, CArray[Str] $attributes) ) is native($BIND-XML2) {*}
    );
    has Pointer   $.endElementNs is rw-ptr(
        method xml6_sax_set_endElementNs( &cb (xmlParserCtxt $ctx, Str $local-name, Str $prefix, Str $uri) ) is native($BIND-XML2) {*}
    );
    has Pointer   $.serror is rw-ptr(
        method xml6_sax_set_serror( &cb (xmlParserCtxt $ctx, xmlError $error) ) is native($BIND-XML2) {*}
    );

    method xmlSAX2InitDefaultSAXHandler(int32 $warning) is native($XML2) {*}
    method xmlSAX2InitHtmlDefaultSAXHandler is native($XML2) {*}
    method init(Bool :$html, Bool :$warning = True) {
        $html
        ?? $.xmlSAX2InitHtmlDefaultSAXHandler()
        !! $.xmlSAX2InitDefaultSAXHandler( +$warning );
    }
    method ParseDoc(Str, int32 $recovery --> xmlDoc) is native($XML2) is symbol('xmlSAXParseDoc') {*};
    method IOParseDTD(xmlParserInputBuffer:D, int32 $enc --> xmlDtd) is native($XML2) is symbol('xmlIOParseDTD') {*}

    method ParseDTD(Str, Str --> xmlDtd) is native($XML2) is symbol('xmlSAXParseDTD') {*}

}

#| An XML Error instance.
class xmlError is export {
    has int32           $.domain; # What part of the library raised this error
    has int32             $.code; # The error code, e.g. an xmlParserError
    has Str            $.message; # human-readable informative error message
    has int32            $.level; # how consequent is the error
    has Str               $.file; # the filename
    has int32             $.line; # the line number if available
    has Str               $.str1; # extra string information
    has Str               $.str2; # extra string information
    has Str               $.str3; # extra string information
    has int32             $.int1; # extra number information
    has int32           $.column; # error column # or 0 if N/A
    has xmlParserCtxt     $.ctxt; # the parser context if available
    has anyNode           $.node; # the node in the tree

    our sub Last(--> xmlError) is native($BIND-XML2) is symbol('xml6_gbl_os_thread_get_last_error') {*}
    method context(uint32 is rw --> xmlAllocedStr) is native($BIND-XML2) is symbol('xml6_error_context_and_column') {*}
}

class xmlXPathObject is export {
    has int32 $.type;

    has xmlNodeSet $.nodeset is rw;
    has int32       $.bool;
    has num64      $.float;
    has xmlCharP  $.string;

    has Pointer     $.user;
    has int32      $.index;
    has Pointer    $.user2;
    has int32     $.index2;

    our sub IsInf(num64 --> int32) is native($XML2) is symbol('xmlXPathIsInf') {*}
    our sub IsNaN(num64 --> int32) is native($XML2) is symbol('xmlXPathIsNaN') {*}
    method add-reference is native($BIND-XML2) is symbol('xml6_xpath_object_add_reference') {*}
    method is-referenced(--> int32) is native($BIND-XML2) is symbol('xml6_xpath_object_is_referenced') {*}
    method remove-reference(--> int32) is native($BIND-