### ----------------------------------------------------
### -- LibXML
### -- Licenses: Artistic-2.0
### -- Authors: David Warring
### -- File: lib/LibXML/Raw.rakumod
### ----------------------------------------------------
### -- Chunk 2 of 4
XML2) is symbol('xml6_xpath_object_remove_reference') {*}
    method Reference {
        with self {
            .add-reference;
            with .value {
                when xmlNodeSet|anyNode { .Reference }
            }
        }
    }
    method Unreference {
        with self {
            my $v := .value;
            if .remove-reference {
                .select; # detach value
                .Free;
            }
            $v.Unreference if $v ~~ xmlNodeSet:D|anyNode:D;
        }
    }

    method domXPathGetNodeSet(int32 $select --> xmlNodeSet) is native($BIND-XML2) {*}
    method domXPathGetPoint(int32 $select --> anyNode) is native($BIND-XML2) {*}
    method Free is symbol('xmlXPathFreeObject') is native($XML2) {*}

    our sub NewString(xmlCharP --> xmlXPathObject) is native($XML2) is symbol('xmlXPathNewString') {*}
    our sub NewFloat(num64 --> xmlXPathObject) is native($XML2) is symbol('xmlXPathNewFloat') {*}
    our sub NewBoolean(int32 --> xmlXPathObject) is native($XML2) is symbol('xmlXPathNewBoolean') {*}
    our sub NewNodeSet(anyNode:D --> xmlXPathObject) is native($XML2) is symbol('xmlXPathNewNodeSet') {*}
    our sub NewPoint(anyNode:D, int32 --> xmlXPathObject) is native($BIND-XML2) is symbol('domXPathNewPoint') {*}
    our sub WrapNodeSet(xmlNodeSet --> xmlXPathObject) is native($XML2) is symbol('xmlXPathWrapNodeSet') {*}

    multi method COERCE(Bool:D $v)           { NewBoolean($v) }
    multi method COERCE(Numeric:D $v)        { NewFloat($v.Num) }
    multi method COERCE(Str:D $v)            { NewString($v) }
    multi method COERCE(anyNode:D $v, UInt :$index = 0)      { NewPoint($v, $index) }
    multi method COERCE(xmlNodeSet:D $v)     { WrapNodeSet($v.copy) }
    method coerce($v) is DEPRECATED<COERCE>  { self.COERCE($v) }

    method select {
        self.value: :select;
    }

    method value(Bool :$select = False) {
        return Nil unless self.defined;
        given $!type {
            when XPATH_UNDEFINED { Mu }
            when XPATH_NODESET | XPATH_XSLT_TREE {
                self.domXPathGetNodeSet(+$select); 
            }
            when XPATH_BOOLEAN { ? $!bool }
            when XPATH_NUMBER {
                given IsInf($!float) {
                    when +1 { Inf }
                    when -1 { -Inf }
                    default {
                        IsNaN($!float)
                            ?? NaN
                            !! $!float.Numeric;
                    }
                }
            }
            when XPATH_STRING { $!string }
            when XPATH_POINT {
                self.domXPathGetPoint(+$select);
            }
            when XPATH_LOCATIONSET {
                fail "todo: location-set values";
            }
            when XPATH_USERS {
                fail "todo: XPath user objects";
            }
            default {
                fail "unhandled XPath Object type: $_";
            }
        }
    }
}

#| Expression evaluation occurs with respect to a context.
#| the context consists of:
#|    - a node (the context node)
#|    - a node list (the context node list)
#|    - a set of variable bindings
#|    - a function library
#|    - the set of namespace declarations in scope for the expression
class xmlXPathContext is repr('CStruct') is export {
    has xmlDoc $.doc;                            # The current document
    has anyNode $.node;                          # The current node
    has int32 $.nb_variables_unused;             # unused (hash table)
    has int32 $.max_variables_unused;            # unused (hash table)
    has xmlHashTable $.varHash;                  # Hash table of defined variables

    has int32 $.nb_types;                        # number of defined types
    has int32 $.max_types;                       # max number of types
    has Pointer[xmlXPathType] $.types;           # Array of defined types

    has int32 $.nb_funcs_unused;                 # unused (hash table)
    has int32 $.max_funcs_unused;                # unused (hash table)
    has xmlHashTable $.funcHash;                 # Hash table of defined funcs

    has int32 $.nb_axis;                         # number of defined axis
    has int32 $.max_axis;                        # max number of axis
    has xmlXPathAxis $.axis;                     # Array of defined axis

    # the namespace nodes of the context node
    has Pointer[xmlNs] $.namespaces;             # Array of namespaces
    has int32 $.nsNr;                            # number of namespace in scope
    has Pointer $.user;                          # function to free

    # extra variables
    has int32 $.contextSize is rw;               # the context size
    has int32 $.proximityPosition is rw;         # the proximity position

    # the set of namespace declarations in scope for the expression
    has xmlHashTable $.nsHash;                   # The namespaces hash table
    my constant xmlXPathVariableLookupFunc = Pointer;
    has xmlXPathVariableLookupFunc $.varLookupFunc;# variable lookup func
    has Pointer $.varLookupData;                 # variable lookup data

    # Possibility to link in an extra item
    has Pointer $.extra;                         # needed for XSLT

    # The function name and URI when calling a function
    has xmlCharP $.function;
    has xmlCharP $.functionURI;

    # function lookup function and data
    my constant xmlXPathFuncLookupFunc = Pointer;
    has xmlXPathFuncLookupFunc $.funcLookupFunc; # function lookup func
    has Pointer $.funcLookupData;                # function lookup data

    # temporary namespace lists kept for walking the namespace axis
    has Pointer[xmlNs] $.tmpNsList;              # Array of namespaces
    has int32 $.tmpNsNr;                         # number of namespaces in scope

    # error reporting mechanism
    has Pointer $.userData;                      # user specific data block
    my constant xmlStructuredErrorFunc = Pointer;
    has xmlStructuredErrorFunc $.error;          # the callback in case of errors
    HAS xmlError $.lastError;                    # the last error
    has anyNode  $.debugNode;                    # the source node XSLT

    # dictionary
    has xmlDict $.dict;                          # dictionary if any

    has int32 $.flags;                           # flags to control compilation

    # Cache for reusal of XPath objects
    has Pointer $.cache;

    # Resource limits
    has ulong $.opLimit;
    has ulong $.opCount;
    has int32 $.depth;
    has int32 $.maxDepth;
    has int32 $.maxParserDepth;

    our sub New(anyNode --> xmlXPathContext) is native($BIND-XML2) is symbol('domXPathNewCtxt') {*}
    method Compile(xmlCharP --> xmlXPathCompExpr) is native($XML2) is symbol('xmlXPathCtxtCompile') {*}
    method Free is symbol('domXPathFreeCtxt') is native($BIND-XML2) {*}
    method Find(xmlXPathCompExpr, anyNode, int32 --> xmlXPathObject) is native($BIND-XML2) is symbol('domXPathFindCtxt') {*}
    method Select(xmlXPathCompExpr, anyNode --> xmlNodeSet) is native($BIND-XML2) is symbol('domXPathSelectCtxt') {*}
    method SetNode(anyNode) is native($BIND-XML2) is symbol('domXPathCtxtSetNode') {*}
    multi method new(xmlDoc:D :$doc!) {
        New($doc);
    }
    multi method new(xmlNode :$node) is default {
        New($node);
    }

    method findnodes(xmlXPathCompExpr:D $expr, anyNode $ref-node? --> xmlNodeSet) {
        self.Select($expr, $ref-node);
    }

    method find(xmlXPathCompExpr:D $expr, anyNode $ref-node?, Bool :$bool) {
        self.Find($expr, $ref-node, $bool.so);
    }
    method RegisterNs(Str, Str --> int32) is symbol('xmlXPathRegisterNs') is native($XML2) {*}
    method NsLookup(xmlCharP --> xmlCharP) is symbol('xmlXPathNsLookup') is native($XML2) {*}

    method RegisterFunc(xmlCharP $name, &func1 (xmlXPathParserContext, int32 --> xmlXPathObject) ) is symbol('xmlXPathRegisterFunc') is native($XML2) {*}
    method RegisterFuncNS(xmlCharP $name, xmlCharP $ns-uri, &func2 (xmlXPathParserContext, int32 --> xmlXPathObject) ) is symbol('xmlXPathRegisterFuncNS') is native($XML2) {*}
    method RegisterVariableLookup( &func3 (xmlXPathContext, Str, Str --> xmlXPathObject), Pointer ) is symbol('xmlXPathRegisterVariableLookup') is native($XML2) {*}
    method GetVariableLookupFunc( --> Pointer) is symbol('xml6_xpath_ctxt_get_var_lookup_func') is native($BIND-XML2) {*}
    constant xmlXPathFunction = Pointer;
    method RegisterFuncLookup( &func4 (xmlXPathContext, xmlCharP $name, xmlCharP $ns-uri --> xmlXPathFunction), Pointer) is native($XML2) is symbol('xmlXPathRegisterFuncLookup') {*};
    method FunctionLookupNS(xmlCharP $name, xmlCharP $ns_uri --> xmlXPathFunction) is native($XML2) is symbol('xmlXPathFunctionLookupNS') {*};
    # recommended libxml 2.13.0+
    method SetErrorHandler(&error-func (xmlXIncludeCtxt $, xmlError $)) is native($XML2) is symbol('xmlXPathSetErrorHandler') {*};
    # legacy
    method SetStructuredErrorFunc( &error-func (xmlXPathContext $, xmlError $)) is native($BIND-XML2) is symbol('domSetXPathCtxtErrorHandler') {*};
}

#| An XPath parser context. It contains pure parsing information,
#| an xmlXPathContext, and the stack of objects.
class xmlXPathParserContext is export {

    has xmlCharP $.cur;                      # the current char being parsed
    has xmlCharP $.base;                     # the full expression

    has int32 $.error;                       # error code

    has xmlXPathContext          $.context; # the evaluation context
    has xmlXPathObject             $.value; # the current value
    has int32                    $.valueNr; # number of values stacked
    has int32                   $.valueMax; # max number of values stacked
    has Pointer[xmlXPathObject] $.valueTab; # stack of values

    has xmlXPathCompExpr            $.comp; # the precompiled expression
    has int32                       $.xptr; # it this an XPointer expression
    has anyNode                 $.ancestor; # used for walking preceding axis

    has int32                 $.valueFrame; # used to limit Pop on the stack

    method valuePop(--> xmlXPathObject) is native($XML2) {*}
    method valuePush(xmlXPathObject --> int32) is native($XML2) {*}
}

class anyNode is export does LibXML::Raw::DOM::Node {
    has Pointer $._private; # application data
    has int32       $.type; # type number, must be second !
    has xmlCharP    $!name; # the name of the node, or the entity
    method name { $!name }
    has anyNode $.children; # parent->child link
    has anyNode     $.last; # last child link
    has anyNode   $.parent; # child->parent link
    has anyNode     $.next; # next sibling link
    has anyNode     $.prev; # previous sibling link
    has xmlDoc       $.doc  # the containing document
         is rw-ptr(method xml6_node_set_doc(xmlDoc) is native($BIND-XML2) {*});
    # + additional fields, depending on node-type; see xmlElem, xmlDoc, xmlAttr, etc...

    method GetBase { self.doc.NodeGetBase(self) }
    method SetBase(xmlCharP) is native($XML2) is symbol('xmlNodeSetBase') {*}
    method Free() is native($XML2) is symbol('xmlFreeNode') {*}
    method FreeList() is native($XML2) is symbol('xmlFreeNodeList') {*}
    method SetListDoc(xmlDoc) is native($XML2) is symbol('xmlSetListDoc') {*}
    method GetLineNo(--> long) is native($XML2) is symbol('xmlGetLineNo') {*}
    method IsBlank(--> int32) is native($XML2) is symbol('xmlIsBlankNode') {*}
    method GetNodePath(--> xmlAllocedStr) is native($XML2) is symbol('xmlGetNodePath') {*}
    method AddChild(anyNode --> anyNode) is native($XML2) is symbol('xmlAddChild') {*}
    method AddChildList(anyNode --> anyNode) is native($XML2) is symbol('xmlAddChildList') {*}
    method AddContent(xmlCharP --> int32) is native($XML2) is symbol('xmlNodeAddContent') {*}
    method SetContext(xmlXPathContext --> int32) is symbol('xmlXPathSetContextNode') is native($XML2) {*}
    method XPathEval(Str, xmlXPathContext --> xmlXPathObject) is symbol('xmlXPathNodeEval') is native($XML2) {*}
    method domXPathSelectStr(Str --> xmlNodeSet) is native($BIND-XML2) {*}
    method domXPathFind(xmlXPathCompExpr, int32 --> xmlXPathObject) is native($BIND-XML2) {*}
    method domFailure(--> xmlAllocedStr) is native($BIND-XML2) {*}
    method dom-error { die .Str with self.domFailure }
    method domAppendChild(anyNode --> anyNode) is native($BIND-XML2) {*}
    method domReplaceNode(anyNode --> anyNode) is native($BIND-XML2) {*}
    method domAddSibling(anyNode --> anyNode) is native($BIND-XML2) {*}
    method domReplaceChild(anyNode, anyNode --> anyNode) is native($BIND-XML2) {*}
    method domInsertBefore(anyNode, anyNode --> anyNode) is native($BIND-XML2) {*}
    method domInsertAfter(anyNode, anyNode --> anyNode) is native($BIND-XML2) {*}
    method domGetNodeName(--> xmlCharP) is native($BIND-XML2) {*}
    method domSetNodeName(Str) is native($BIND-XML2) {*}
    method domGetNodeValue(--> xmlAllocedStr) is native($BIND-XML2) {*}
    method domGetXPathKey(--> xmlCharP) is native($BIND-XML2) {*}
    method domGetASTKey(--> xmlCharP) is native($BIND-XML2) {*}
    method domSetNodeValue(Str) is native($BIND-XML2) {*}
    method domRemoveChild(anyNode --> anyNode) is native($BIND-XML2) {*}
    method domRemoveChildNodes(--> xmlDocFrag) is native($BIND-XML2) {*}

    method domAppendTextChild(Str $name, Str $value --> anyNode) is native($BIND-XML2) {*}
    method domAddNewChild(Str $uri, Str $name --> anyNode) is native($BIND-XML2) {*}
    method domSetNamespace(Str $URI, Str $prefix, int32 $flag --> int32) is native($BIND-XML2) {*}
    method first-child(int32 --> anyNode) is native($BIND-XML2) is symbol('xml6_node_first_child') {*}
    method last-child(int32 --> anyNode) is native($BIND-XML2) is symbol('xml6_node_last_child') {*}
    method next-node(int32 --> anyNode) is native($BIND-XML2) is symbol('xml6_node_next') {*}
    method prev-node(int32 --> anyNode) is native($BIND-XML2) is symbol('xml6_node_prev') {*}
    method is-referenced(--> int32) is native($BIND-XML2) is symbol('domNodeIsReferenced') {*}
    method root(--> anyNode) is native($BIND-XML2) is symbol('xml6_node_find_root') {*}
    method domGetChildrenByLocalName(Str --> xmlNodeSet) is native($BIND-XML2) {*}
    method domGetChildrenByTagName(Str --> xmlNodeSet) is native($BIND-XML2) {*}
    method domGetChildrenByTagNameNS(Str, Str --> xmlNodeSet) is native($BIND-XML2) {*}
    method domGetElementsByLocalName(Str --> xmlNodeSet) is native($BIND-XML2) {*}
    method domGetElementsByTagName(Str --> xmlNodeSet) is native($BIND-XML2) {*}
    method domGetElementsByTagNameNS(Str, Str --> xmlNodeSet) is native($BIND-XML2) {*}
    method !hash(int32 $keep-blanks --> xmlHashTable) is native($BIND-XML2) is symbol('xml6_hash_xpath_node_children') {*}
    method Hash(:$blank) {
        self!hash(+$blank.so);
    }

    method domNormalize(--> int32) is native($BIND-XML2) {*}
    method domUniqueKey(--> xmlAllocedStr) is native($BIND-XML2) {*}
    method domIsSameNode(anyNode --> int32) is native($BIND-XML2) {*}

    method xml6_node_to_str_C14N(int32 $comments, int32 $mode, CArray[Str] $inc-prefix is rw, xmlNodeSet --> xmlAllocedStr) is native($BIND-XML2) {*}

    method Str(anyNode:D: UInt :$options = 0 --> xmlCharP) is default {
        do with self.Blob(:$options) {
            .decode('utf8');
        } // Str;
    }

    method xml6_node_is_htmlish(--> int32) is native($BIND-XML2) {*}
    method isHTMLish { ? self.xml6_node_is_htmlish }
    method xml6_node_to_buf(int32 $opts, size_t $len is rw, Str $enc  --> Pointer[uint8]) is native($BIND-XML2) {*}

    method Blob(anyNode:D: int32 :$options = 0, xmlEncodingStr :$enc --> Blob) {
        my buf8 $buf;

        if self.xml6_node_to_buf($options, my size_t $len, $enc) -> $p {
            $buf .= allocate($len);
            CLib::memcpy($buf, $p, $len);
            xml6_gbl::xml-free($p);
        }

        $buf;
    }

    method xmlCopyNode (int32 $extended --> anyNode) is native($XML2) {*}
    method xmlDocCopyNode(xmlDoc, int32 --> anyNode) is native($XML2) {*}
    method copy(Bool :$deep) {
        my $extended := $deep ?? 1 !! 2;
        with $.doc {
            $.xmlDocCopyNode($_, $extended).delegate;
        }
        else {
            $.xmlCopyNode( $extended ).delegate;
        }
    }

    method string-value(--> xmlAllocedStr) is native($XML2) is symbol('xmlXPathCastNodeToString') {*}
    method Unlink is native($BIND-XML2) is symbol('domUnlinkNode') {*}
    method Release is native($BIND-XML2) is symbol('domReleaseNode') {*}
    method Reference is native($BIND-XML2) is symbol('xml6_node_add_reference') {*}
    method remove-reference(--> int32) is native($BIND-XML2) is symbol('xml6_node_remove_reference') {*}
    method lock(--> int32) is native($BIND-XML2) is symbol('xml6_node_lock') {*}
    method unlock(--> int32) is native($BIND-XML2) is symbol('xml6_node_unlock') {*}
    method Unreference{
        with self {
            if .remove-reference {
                # this particular node is no longer referenced directly
                given .root {
                    # release or keep the tree, in it's entirety
                    .Free unless .is-referenced;
                }
            }
        }
    }
    # role refactor needed
    method domSetNamespaceDeclPrefix(|c) { ... }
    method domSetNamespaceDeclURI(|c) { ... }
    method domGetNamespaceDeclURI(|c) { ... }
    method ItemNode handles<delegate cast> {  nativecast(itemNode, self) }

    method new() { fail "new() not available for " ~ self.WHAT.raku }
}

#| A node in an XML tree.
class xmlNode is anyNode {
    has xmlNs            $.ns  # pointer to the associated namespace
        is rw-ptr( method xml6_node_set_ns(xmlNs) is native($BIND-XML2) {*});
    has xmlCharP    $.content  # the content
        is rw-str(method xml6_node_set_content(xmlCharP) is native($BIND-XML2) {*});
    has xmlAttr  $.properties; # properties list
    has xmlNs         $.nsDef  # namespace definitions on this node
        is rw-ptr(method xml6_node_set_nsDef(xmlNs) is native($BIND-XML2) {*});
    has Pointer        $.psvi; # for type/PSVI information
    has uint16         $.line; # line number
    has uint16        $.extra; # extra data for XPath/XSLT

    method domSetNamespaceDeclURI(xmlCharP $prefix, xmlCharP $uri --> int32) is native($BIND-XML2) {*}
    method domGetNamespaceDeclURI(xmlCharP $prefix --> xmlCharP) is native($BIND-XML2) {*}
    method domSetNamespaceDeclPrefix(xmlCharP $prefix, xmlCharP $ns-prefix --> int32) is native($BIND-XML2) {*}
}

#| xmlNode of type: XML_ELEMENT_NODE
class xmlElem is xmlNode is export does LibXML::Raw::DOM::Element {
    also does domNode[$?CLASS, XML_ELEMENT_NODE];

    method NewNs(xmlCharP $href, xmlCharP $prefix --> xmlNs) is native($XML2) is symbol('xmlNewNs') {*};
    method SetProp(Str, Str --> xmlAttr) is native($XML2) is symbol('xmlSetProp') {*}
    method domGetAttributeNode(xmlCharP $qname --> xmlAttr) is native($BIND-XML2) {*}
    method domGetAttribute(xmlCharP $qname --> xmlAllocedStr) is native($BIND-XML2)  {*}
    method domHasAttributeNS(xmlCharP $uri, xmlCharP $name --> int32) is native($BIND-XML2) {*}
    method domGetAttributeNS(xmlCharP $uri, xmlCharP $name --> xmlAllocedStr) is native($BIND-XML2) {*}
    method domGetAttributeNodeNS(xmlCharP $uri, xmlCharP $name --> xmlAttr) is native($BIND-XML2) {*}
    method domSetAttribute(Str, Str --> int32) is native($BIND-XML2) {*}
    method domSetAttributeNode(xmlAttr --> xmlAttr) is native($BIND-XML2) {*}
    method domSetAttributeNodeNS(xmlAttr --> xmlAttr) is native($BIND-XML2) {*}
    method domSetAttributeNS(Str $URI, Str $name, Str $value --> xmlAttr) is native($BIND-XML2) {*}
    method domGenNsPrefix(Str $base-prefix --> xmlCharP) is native($BIND-XML2) {*}

    # The content field may hold the index of the element in the
    # document, if doc.IndexElements has been run
    method content(--> int32) is native($BIND-XML2) is symbol('xml6_node_get_elem_index') {*}
    method ValidElements(xmlElem, CArray[Str], int32 $max --> int32)  is native($XML2) is symbol('xmlValidGetValidElements') {*}

    our sub New(xmlNs, Str $name --> xmlElem) is native($XML2) is symbol('xmlNewNode') {*}
    multi method new(Str:D :$name!, xmlNs:D :$ns, xmlDoc:D :$doc!) {
        $doc.new-node(:$name, :$ns);
    }
    multi method new(Str:D :$name!, xmlNs :$ns) {
        given New($ns, $name) -> xmlElem:D $node {
            $node.nsDef = $_ with $ns;
            $node;
        }
    }

}

#| xmlNode of type: XML_TEXT_NODE
class xmlTextNode is xmlNode is repr('CStruct') is export {
    also does domNode[$?CLASS, XML_TEXT_NODE];

    our sub New(Str $content --> xmlTextNode) is native($XML2) is symbol('xmlNewText') {*}
    method new(Str :$content!, xmlDoc :$doc) {
        given New($content) -> xmlTextNode:D $node {
            $node.doc = $_ with $doc;
            $node;
        }
    }

}

#| xmlNode of type: XML_COMMENT_NODE
class xmlCommentNode is xmlNode is repr('CStruct') is export {
    also does domNode[$?CLASS, XML_COMMENT_NODE];

    our sub New(Str $content --> xmlCommentNode) is native($XML2) is symbol('xmlNewComment') {*}
    method new(Str :$content!, xmlDoc :$doc) {
        given New($content) -> xmlCommentNode:D $node {
            $node.doc = $_ with $doc;
            $node;
        }
    }
}

#| xmlNode of type: XML_CDATA_SECTION_NODE
class xmlCDataNode is xmlNode is repr('CStruct') is export {
    also does domNode[$?CLASS, XML_CDATA_SECTION_NODE];

    our sub New(xmlDoc, Blob $content, int32 $len --> xmlCDataNode) is native($XML2) is symbol('xmlNewCDataBlock') {*}
    multi method new(Str :content($string)!, xmlDoc :$doc --> xmlCDataNode:D) {
        my Blob $content = $string.encode;
        self.new: :$content, :$doc;
    }
    multi method new(Blob :content($buf)!, xmlDoc :$doc --> xmlCDataNode:D) {
        my $len = $buf.elems;
        New($doc, $buf, $len);
    }
}

#| xmlNode of type: XML_PI_NODE
class xmlPINode is xmlNode is repr('CStruct') is export {
    also does domNode[$?CLASS, XML_PI_NODE];

    method new(xmlDoc :$doc, Str:D :$name!, Str :$content) {
        $doc.new-pi(:$name, :$content);
    }

}

#| xmlNode of type: XML_ENTITY_REF_NODE
class xmlEntityRefNode is xmlNode is repr('CStruct') is export {
    also does domNode[$?CLASS, XML_ENTITY_REF_NODE];

    multi method new(xmlDoc:D :$doc!, Str:D :$name!) {
        $doc.new-ent-ref(:$name);
    }
}

#| An attribute on an XML node (type: XML_ATTRIBUTE_NODE)
class xmlAttr is anyNode does LibXML::Raw::DOM::Attr is export {
    also does domNode[$?CLASS, XML_ATTRIBUTE_NODE];

    has xmlNs       $.ns; # the associated namespace
    has int32    $.atype; # the attribute type if validating
    has Pointer   $.psvi; # for type/PSVI information
    ## todo Only available in newer libxml2 versions!
    has Pointer $!id;     # the ID struct

    method Free is native($XML2) is symbol('xmlFreeProp') {*}
    method xmlCopyProp(--> xmlAttr) is native($XML2) {*}
    method copy() { $.xmlCopyProp }
    method new(Str :$name!, Str :$value!, xmlDoc :$doc --> xmlAttr:D) {
        $doc.NewProp($name, $value);
    }
    method domAttrSerializeContent(--> xmlAllocedStr) is native($BIND-XML2) {*}
}

#| An XML document (type: XML_DOCUMENT_NODE)
class xmlDoc is anyNode does LibXML::Raw::DOM::Document is export {
    # note: htmlDoc is based on this class
    @ClassMap[XML_DOCUMENT_NODE] = $?CLASS;

    has int32           $.compression; # level of zlib compression
    has int32           $.standalone is rw;  # standalone document (no external refs)
                                       # 1 if standalone="yes"
                                       # 0 if standalone="no"
                                       # -1 if there is no XML declaration
                                       # -2 if there is an XML declaration, but no
                                       #    standalone attribute was specified
    has xmlDtd          $.intSubset;   # the document internal subset
    has xmlDtd          $.extSubset;   # the document external subset
    has xmlNs           $.oldNs;       # Global namespace, the old way
    has xmlCharP        $.version      # the XML version string
             is rw-str(method xml6_doc_set_version(Str) is native($BIND-XML2) {*});
    has xmlCharP        $.encoding     # external initial encoding, if any
             is rw-str(method xml6_doc_set_encoding(Str) is native($BIND-XML2) {*});
    has Pointer         $.ids;         # Hash table for ID attributes if any
    has Pointer         $.refs;        # Hash table for IDREFs attributes if any
    has xmlCharP        $.URI          # The URI for that document
             is rw-str(method xml6_doc_set_URI(Str) is native($BIND-XML2) {*});
    has int32           $.charset;     # Internal flag for charset handling,
                                       # actually an xmlCharEncoding 
    has xmlDict         $.dict;        # dict used to allocate names or NULL
    has Pointer         $.psvi;        # for type/PSVI information
    has int32           $.parseFlags;  # set of xmlParserOption used to parse the
                                       # document
    has int32           $.properties;  # set of xmlDocProperties for this document
                                       # set at the end of parsing

    method DumpFormatMemoryEnc(Pointer[uint8] $ is rw, int32 $ is rw, Str, int32 ) is symbol('xmlDocDumpFormatMemoryEnc') is native($XML2) {*}
    sub xmlSaveFormatFile(Str $filename, xmlDoc $doc, int32 $format --> int32) is native($XML2) is export {*}
    # this method can save documents with compression
    method write(Str:D $filename, Int() :$format = 0) {
         xmlSaveFormatFile($filename, self, $format);
    }
    method GetRootElement(--> xmlElem) handles<nsDef> is symbol('xmlDocGetRootElement') is native($XML2) { * }
    method SetRootElement(xmlElem --> xmlElem) is symbol('xmlDocSetRootElement') is native($XML2) { * }
    method Copy(int32 $deep --> xmlDoc) is symbol('xmlCopyDoc') is native($XML2) {*}
    method copy(Bool :$deep = True) { $.Copy(+$deep) }
    method Free is native($XML2) is symbol('xmlFreeDoc') {*}
    method xmlParseBalancedChunkMemory(xmlSAXHandler $sax-handler, Pointer $user-data, int32 $depth, xmlCharP $string, Pointer[anyNode] $list is rw --> int32) is native($XML2) {*}
    method xmlParseBalancedChunkMemoryRecover(xmlSAXHandler $sax-handler, Pointer $user-data, int32 $depth, xmlCharP $string, Pointer[anyNode] $list is rw, int32 $repair --> int32) is native($XML2) {*}
    method NewNode(xmlNs, xmlCharP $name, xmlCharP $content --> xmlElem) is native($XML2) is symbol('xmlNewDocNode') {*}
    method NewDtd(Str, Str, Str --> xmlDtd) is native($XML2) is symbol('xmlNewDtd') {*}
    method CreateIntSubset(Str, Str, Str --> xmlDtd) is native($XML2) is symbol('xmlCreateIntSubset') {*}
    method GetCompressMode(--> int32) is native($XML2) is symbol('xmlGetDocCompressMode') {*}
    method SetCompressMode(int32) is native($XML2) is symbol('xmlSetDocCompressMode') {*}
    method GetEntity(Str --> xmlEntity) is native($XML2) is symbol('xmlGetDocEntity') {*}

    method new-node(Str:D :$name!, xmlNs :$ns, Str :$content --> xmlElem:D) {
        given self.NewNode($ns, $name, $content) -> xmlElem:D $node {
            $node.nsDef = $_ with $ns;
            $node;
        }
    }
    method NewPI(xmlCharP $name, xmlCharP $content --> xmlPINode) is native($XML2) is symbol('xmlNewDocPI') {*}
    method new-pi(Str:D :$name!, Str :$content --> xmlPINode:D) {
       self.NewPI($name, $content);
    }
    method NewEntityRef(xmlCharP $name --> xmlEntityRefNode) is native($XML2) is symbol('xmlNewReference') {*}
    method new-ent-ref(Str:D :$name! --> xmlEntityRefNode:D) {
       self.NewEntityRef($name);
    }

    method NodeGetBase(anyNode --> xmlAllocedStr) is native($XML2) is symbol('xmlNodeGetBase') {*}
    method EncodeEntitiesReentrant(xmlCharP --> xmlAllocedStr) is native($XML2) is symbol('xmlEncodeEntitiesReentrant') {*}
    method NewProp(xmlCharP $name, xmlCharP $value --> xmlAttr) is symbol('xmlNewDocProp') is native($XML2) {*}
    method XIncludeProcessFlags(uint32 $flags --> int32) is symbol('xmlXIncludeProcessFlags') is native($XML2) {*}
    method SearchNs(anyNode, Str --> xmlNs) is native($XML2) is symbol('xmlSearchNs') {*}
    method SearchNsByHref(anyNode, Str --> xmlNs) is native($XML2) is symbol('xmlSearchNsByHref') {*}
    method GetID(Str --> xmlAttr) is native($XML2) is symbol('xmlGetID') {*}
    method IsID(xmlElem, xmlAttr --> int32) is native($XML2) is symbol('xmlIsID') {*}
    method IndexElements(--> long) is symbol('xmlXPathOrderDocElems') is native($XML2) {*}

    our sub New(xmlCharP $version --> xmlDoc) is native($XML2) is symbol('xmlNewDoc') {*}
    method new(Str:D() :$version = '1.0') {
        New($version);
    }

    method domCreateAttribute(Str, Str --> xmlAttr) is native($BIND-XML2) {*}
    method domCreateAttributeNS(Str, Str, Str --> xmlAttr) is native($BIND-XML2) {*}
    method domImportNode(anyNode, int32, int32 --> anyNode) is native($BIND-XML2) {*}
    method domGetInternalSubset(--> xmlDtd) is native($BIND-XML2) {*}
    method domGetExternalSubset(--> xmlDtd) is native($BIND-XML2) {*}
    method domSetInternalSubset(xmlDtd --> xmlDtd) is native($BIND-XML2) {*}
    method domSetExternalSubset(xmlDtd --> xmlDtd) is native($BIND-XML2) {*}

    method set-flags(int32 --> int32) is native($BIND-XML2) is symbol('xml6_doc_set_flags') {*}
    method get-flags(--> int32) is native($BIND-XML2) is symbol('xml6_doc_get_flags') {*}
}

#| xmlDoc of type: XML_HTML_DOCUMENT_NODE
class htmlDoc is xmlDoc is repr('CStruct') is export {
    also does domNode[$?CLASS, XML_HTML_DOCUMENT_NODE];

    method DumpFormat(xmlAllocedStr $ is rw, int32 $ is rw, int32 ) is symbol('htmlDocDumpMemoryFormat') is native($XML2) {*}
    our sub New(xmlCharP $