HeavyThing - xmlmemnode.inc

Jeff Marrison

Table of functions

	; ------------------------------------------------------------------------
	; HeavyThing x86_64 assembly language library and showcase programs
	; Copyright © 2015-2018 2 Ton Digital 
	; Homepage: https://2ton.com.au/
	; Author: Jeff Marrison <jeff@2ton.com.au>
	;       
	; This file is part of the HeavyThing library.
	;       
	; HeavyThing is free software: you can redistribute it and/or modify
	; it under the terms of the GNU General Public License, or
	; (at your option) any later version.
	;       
	; HeavyThing is distributed in the hope that it will be useful, 
	; but WITHOUT ANY WARRANTY; without even the implied warranty of
	; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
	; GNU General Public License for more details.
	;       
	; You should have received a copy of the GNU General Public License along
	; with the HeavyThing library. If not, see <http://www.gnu.org/licenses/>.
	; ------------------------------------------------------------------------
	; xmlmemnode: an XML node representation that is all in-memory.
	;
	; What this means is that if given an xmlparser object as a construction
	; method, that it will scan the entirety of the document, convert all
	; tag names, attribute names and values, text, cdata, etc into native
	; strings, construct child object lists of nested nodes, the whole
	; shooting match.
	;
	; The reason it is called xmlmemnode is so that it is plain to see that
	; it literally loads the entirety into memory (rather than other methods
	; I have that use lazy on-demand based DOM traversal).
	;
	; This design was _not_ made for memory efficiency (read: this uses a lot
	; of memory to do its work). The way the library's default memory manager
	; works is bin-based allocations, minimum size of 64 bytes per alloc,
	; combine that with UTF32 native strings and 8 byte length prefaces, and
	; our node size minimums, well, you get the idea. I don't work in memory
	; constrained environments very often, so this still suits me just fine.
	; YMMV.
	;
	; Depending on your specific needs, you may find the "pull parser" used
	; herein (xmlparser.inc) will be much friendlier memory-wise (it uses
	; none aside from its state and a single tag space). Searching this file
	; for xmlparser$next reveals how the parser state works.
	;
	; Also note: this is not an exact DOM spec model, but is close enough to
	; see the resemblance. :-)
	;



	; first things first: namespaces (also memory based, so we include in here)

if used xmlmemns$new | used xmlmemns$new_nocopy | defined include_everything

xmlmemns_prefix_ofs = 0
xmlmemns_uri_ofs = 8

xmlmemns_size = 16

end if

if used xmlmemns$new | defined include_everything

	; two arguments: rdi == string prefix, rsi == string uri
	; returns a new xmlmemns object in rax
	; NOTE: copies both strings, see xmlmemns$new_nocopy for the alternative
falign
xmlmemns$new:
	prolog	xmlmemns$new
	push	rbx rdi rsi
	mov	edi, xmlmemns_size
	call	heap$alloc
	mov	rbx, rax
	pop	rdi
	call	string$copy
	mov	[rbx+xmlmemns_uri_ofs], rax
	pop	rdi
	call	string$copy
	mov	[rbx+xmlmemns_prefix_ofs], rax
	mov	rax, rbx
	pop	rbx
	epilog

end if


if used xmlmemns$new_nocopy | defined include_everything
	; two arguments: rdi == string prefix, rsi == string uri
	; returns a new xmlmemns object in rax
	; NOTE: assumes ownership of both strings
falign
xmlmemns$new_nocopy:
	prolog	xmlmemns$new_nocopy
	push	rdi rsi
	mov	edi, xmlmemns_size
	call	heap$alloc
	pop	rsi rdi
	mov	[rax+xmlmemns_prefix_ofs], rdi
	mov	[rax+xmlmemns_uri_ofs], rsi
	epilog

end if


if used xmlmemns$destroy | defined include_everything
	; single argument in rdi: an xmlmemns object
falign
xmlmemns$destroy:
	prolog	xmlmemns$destroy
	push	rdi
	push	qword [rdi+xmlmemns_prefix_ofs]
	mov	rdi, [rdi+xmlmemns_uri_ofs]
	call	heap$free
	pop	rdi
	call	heap$free
	pop	rdi
	call	heap$free
	epilog

end if


if used xmlmemns$has_prefix | defined include_everything
	; single argument in rdi: an xmlmemns object
	; returns bool as to whether the prefix string's length is >0
falign
xmlmemns$has_prefix:
	prolog	xmlmemns$has_prefix
	mov	rsi, [rdi+xmlmemns_prefix_ofs]
	mov	eax, 1
	xor	edx, edx
	cmp	qword [rsi], 0
	cmove	eax, edx
	epilog

end if


if used xmlmemns$equals | defined include_everything
	; two arguments: rdi == an xmlmemns object, rsi == another xmlmemns object
	; returns bool as to whether their URIs are the same
falign
xmlmemns$equals:
	prolog	xmlmemns$equals
	mov	rdi, [rdi+xmlmemns_uri_ofs]
	mov	rsi, [rsi+xmlmemns_uri_ofs]
	call	string$equals
	epilog

end if



if used xmlmemnode$new_type | used xmlmemnode$new_string | used xmlmemnode$new_parser | defined include_everything


; first up: possible xmlmemnode types (specified as flags so multiple type compares are easier):
xmlmemnode_type_unknown = 0x0001
xmlmemnode_type_attribute = 0x0002
xmlmemnode_type_text = 0x0004
xmlmemnode_type_cdata = 0x0008
xmlmemnode_type_comment = 0x00010
xmlmemnode_type_pi = 0x0020
xmlmemnode_type_element = 0x0040
xmlmemnode_type_document = 0x0080
xmlmemnode_type_document_fragment = 0x0100
xmlmemnode_type_doctype = 0x0200

; flags that may also be included:
xmlmemnode_flag_name_owner = 0x0400
xmlmemnode_flag_ns_owner = 0x0800

; next up: offsets specific to xmlmemnode objects themselves, noting here that sizes may vary depending on the kind
; of node it is
xmlmemnode_parent_ofs = 0
xmlmemnode_name_ofs = 8
xmlmemnode_ns_ofs = 16
xmlmemnode_flags_ofs = 24		; one of the xmlmemnode_type_* defined above, or xmlmemnode_flag_* above
; all node types will have the above fields

; unioned on purpose (their use is dependent upon what kind of node it is)
xmlmemnode_names_ofs = 32		; document and document_fragment nodes contain element/attribute name caches
xmlmemnode_attributes_ofs = 32
xmlmemnode_value_ofs = 32

; element nodes contain all of the above fields as well as the below two:
xmlmemnode_namespaces_ofs = 40
xmlmemnode_children_ofs = 48

; so an xmlmemnode with a type of element, document, or document_fragment is 56
; all others are 40, and since our HeavyThing library's allocator granularity
; is 64 bytes, it makes little difference to the amount of memory a node will use
; but we distinguish them anyway

; note of course that a given node will use considerably more than this for its other
; various bits (string names, vectors for children, attributes, etc etc)

xmlmemnode_element_size = 56
xmlmemnode_size = 40


end if


if used xmlmemnode$new_type | defined include_everything
	; two arguments: edi == xmlmemnode_type_* value, rsi == pointer to parent
falign
xmlmemnode$new_type:
	prolog	xmlmemnode$new_type
	mov	edx, edi
	mov	edi, xmlmemnode_size
	mov	ecx, xmlmemnode_element_size
	test	edx, xmlmemnode_type_element or xmlmemnode_type_document or xmlmemnode_type_document_fragment
	cmovnz	edi, ecx
	push	rdx rsi
	call	heap$alloc_clear
	pop	rsi rdi
	mov	[rax+xmlmemnode_parent_ofs], rsi
	mov	[rax+xmlmemnode_flags_ofs], edi
	test	edi, xmlmemnode_type_document or xmlmemnode_type_document_fragment
	jnz	.withnames
	epilog
.withnames:
	push	rax
	xor	edi, edi
	call	stringmap$new
	mov	rdi, rax
	pop	rax
	mov	[rax+xmlmemnode_names_ofs], rdi
	epilog

end if


if used xmlmemnode$new_string | defined include_everything
	; single argument in rdi: a string to parse
	; returns either a document node or a document_fragment node in rax (or null on error)
	; Note: this constructs a stack-based xmlparser, then calls xmlmemnode$new_parser
falign
xmlmemnode$new_string:
	prolog	xmlmemnode$new_string
	sub	rsp, xmlparser_size
	mov	rsi, rdi
	mov	rdi, rsp
	mov	edx, xmlparser_ignorewhite or xmlparser_condensewhite		; TODO: make these ht_defaults.inc settings
	call	xmlparser$init_string
	mov	rdi, rsp
	call	xmlmemnode$new_parser
	add	rsp, xmlparser_size
	epilog

end if


if used xmlmemnode$new_parser | defined include_everything
	; single argument in rdi: an xmlparser object
	; returns either a document node or a document_fragment node in rax (or null on error)
	; in the event rax is null, extended error information (such as an error return from xmlparser) will be in edx
falign
xmlmemnode$new_parser:
	prolog	xmlmemnode$new_parser
	push	rbx r12 r13 r14 r15
	mov	rbx, rdi
	sub	rsp, xmltag_size
	mov	rdi, rsp
	call	xmltag$reset
	; we'll use r12 as "this", r13 as "p", r14d as the status return from the xmlparser
	xor	r12d, r12d
	xor	r13d, r13d
calign
.outer:
	mov	rdi, rbx
	mov	rsi, rsp
	call	xmlparser$next

if defined xmlmemnode_debug
	; debug
	mov	rdi, rsp
	push	rax
	call	xmltag$debug
	pop	rax
	; end debug
end if

	mov	ecx, [rsp+xmltag_nodetype_ofs]
	mov	r14d, eax
	cmp	eax, xmlparser_noerror
	jne	.bailout
	jmp	qword [rcx*8+.dispatch]
dalign
.dispatch:
	dq	.bailout, .element, .textnode, .cdata, .pi, .comment, .doctype, .xmldecl
falign
.findnamespace:
	; local function that gets called during new element and element close
	; on entry, rdi == node
	; r15 is still a valid copy of our tag's text, and the one that will be passed to
	; setqname_nocopy (on element create, else it is checked on close)
	; we will return -1 on badqname, -2 on prefixnotbound, otherwise, either 0 or an xmlmemns pointer
	; first up, find the colon (ignoring the xmltag's idea since that is pre-escapign/conversion)
	push	rbx r13 r14
	mov	rbx, rdi			; hangon to the node
	mov	rdi, r15
	mov	esi, ':'
	call	string$indexof_charcode
	cmp	rax, 0
	jg	.findnamespace_prefix
	je	.findnamespace_badqname
	; no colon in the tag text, so no prefix ... so we have to search for an empty (default) prefix in our
	; namespace scope chain
	; walk the node up in rbx looking for it
calign
.findnamespace_finddefault_outer:
	mov	r13, [rbx+xmlmemnode_namespaces_ofs]
	test	r13, r13
	jz	.findnamespace_finddefault_nextouter
	cmp	qword [r13+vector_count_ofs], 0
	je	.findnamespace_finddefault_nextouter
	xor	r14d, r14d
calign
.findnamespace_finddefault_inner:
	mov	rdi, r13
	mov	esi, r14d
	add	r14d, 1
	call	vector$at
	mov	rdi, [rax+xmlmemns_prefix_ofs]
	cmp	qword [rdi], 0
	je	.findnamespace_founddefault
	cmp	r14d, [r13+vector_count_ofs]
	jb	.findnamespace_finddefault_inner
.findnamespace_finddefault_nextouter:
	mov	rbx, [rbx+xmlmemnode_parent_ofs]
	test	rbx, rbx
	jnz	.findnamespace_finddefault_outer
	; otherwise, no default namespace was found
	pop	r14 r13 rbx
	xor	eax, eax
	ret
calign
.findnamespace_badqname:
	pop	r14 r13 rbx
	mov	rax, -1
	ret
calign
.findnamespace_founddefault:
	; xmlmemns object in rax is our return
	pop	r14 r13 rbx
	ret
calign
.findnamespace_prefix:
	; split our prefix and tag text on rax
	mov	r13, rax
	mov	rdi, r15
	xor	esi, esi
	mov	rdx, rax
	call	string$substring
	mov	r14, rax		; temporarily hangon to our prefix
	mov	rdi, r15
	lea	rsi, [r13+1]
	mov	rdx, [r15]
	call	string$substring
	mov	rdi, r15
	mov	r15, rax
	call	heap$free
	; make sure the remaining name is not empty
	cmp	qword [r15], 0
	je	.findnamespace_badqname
	; so we are done with the tag text since we are only looking for the namespace
	; preserve r15 and use it for our prefix search
	push	r15
	mov	r15, r14
calign
.findnamespace_prefix_outer:
	mov	r13, [rbx+xmlmemnode_namespaces_ofs]
	test	r13, r13
	jz	.findnamespace_prefix_nextouter
	cmp	qword [r13+vector_count_ofs], 0
	je	.findnamespace_prefix_nextouter
	xor	r14d, r14d
calign
.findnamespace_prefix_inner:
	mov	rdi, r13
	mov	esi, r14d
	add	r14d, 1
	call	vector$at
	mov	rdi, [rax+xmlmemns_prefix_ofs]
	mov	rsi, r15
	push	rax
	call	string$equals
	pop	rdx
	test	eax, eax
	jnz	.findnamespace_foundprefix
	cmp	r14d, [r13+vector_count_ofs]
	jb	.findnamespace_prefix_inner
.findnamespace_prefix_nextouter:
	mov	rbx, [rbx+xmlmemnode_parent_ofs]
	test	rbx, rbx
	jnz	.findnamespace_prefix_outer
	; otherwise, no default namespace was found, get rid of our prefix string
	mov	rdi, r15
	call	heap$free
	pop	r15 r14 r13 rbx
	mov	rax, -2		; prefix not bound
	ret
calign
.findnamespace_foundprefix:
	; xmlmemns object in rdx is our return, but we have to get rid of the prefix string in r15
	mov	r14, rdx
	mov	rdi, r15
	call	heap$free
	mov	rax, r14
	pop	r15 r14 r13 rbx
	ret
falign
.copyattrsandns:
	; local function that gets called during new element construction
	; on entry, rdi == node, rsi == xmltag
	; our job is to iterate through the attributes (if any, if none, just return)
	; paying attention to xmlns entries, and if they aren't namespace attributes
	; then add them as normal attribute nodes to the node passed in rdi
	; if we puke on something, we'll set r14d to our xmlparser_ error code
	cmp	dword [rsi+xmltag_attrcount_ofs], 0
	je	.copyattrsandns_ret
	push	rbx r12 r13 r14 r15
	mov	rbx, rdi
	mov	r12, rsi
	xor	r13d, r13d
calign
.copyattrsandns_loop:
	mov	rdi, r12
	mov	esi, r13d
	call	xmltag$getattr
	mov	r14, rax
	mov	rdi, r12
	mov	rsi, [rax+xmltagattr_namestart_ofs]
	mov	rdx, [rax+xmltagattr_nameend_ofs]
	call	xmltag$unescape
	mov	r15, rax
	cmp	qword [rax], 5
	jb	.copyattrsandns_loop_normalattr
	mov	rdi, rax
	mov	rsi, .xmlns
	call	string$starts_with
	test	eax, eax
	jz	.copyattrsandns_loop_normalattr
	cmp	qword [r15], 5
	je	.copyattrsandns_loop_defaultns
	mov	rdi, r15
	mov	esi, 5
	call	string$charat
	cmp	eax, ':'
	jne	.copyattrsandns_loop_normalattr
	cmp	qword [r15], 6
	je	.copyattrsandns_loop_badqname
	; otherwise, prefix is from 6 ->, attribute value is the URI
	mov	rdi, r15
	mov	esi, 6
	mov	rdx, -1
	call	string$substr
	mov	rdi, r15
	mov	r15, rax
	call	heap$free
	; so now, get the attribute value out of the tag (unescaped)
.copyattrsandns_loop_xmlns_doit:
	mov	rdi, r12
	mov	rsi, [r14+xmltagattr_valuestart_ofs]
	mov	rdx, [r14+xmltagattr_valueend_ofs]
	call	xmltag$unescape
	mov	rdi, r15		; prefix
	mov	rsi, rax		; uri
	call	xmlmemns$new_nocopy
	; now, we only want to add this prefix/uri namespace if it isn't already in here
	; more than one prefix can reference the same URI, so we don't care if there are multiples
	; if the same prefix already exists, overwrite its URI
	mov	r15, rax		; hangon to our new xmlmemns object
	; we are done with r14 as well
	; so, we know that the node in rbx is an element node (cuz element type construction is the only place we get called from)
	; if the xmlmemnode_namespaces_ofs is null/empty, just add it
	cmp	qword [rbx+xmlmemnode_namespaces_ofs], 0
	je	.copyattrsandns_loop_xmlns_new
	; otherwise, there are already namespaces here, so we have to determine whether there is a prefix mash
	; thanks to markup like this: 
	; we know for certain (thanks to use being in parse stage) that there
	; is at least one namespace here
	; scan for a prefix match
	mov	r14, [rbx+xmlmemnode_namespaces_ofs]
	push	r13
	xor	r13d, r13d
calign
.copyattrsandns_loop_xmlns_prefixsearch:
	mov	rdi, r14
	mov	esi, r13d
	call	vector$at
	mov	rdi, [rax+xmlmemns_prefix_ofs]
	mov	rsi, [r15+xmlmemns_prefix_ofs]
	call	string$equals
	test	eax, eax
	jnz	.copyattrsandns_loop_prefixmash
	; otherwise, no match
	add	r13d, 1
	cmp	r13d, [r14+vector_count_ofs]
	jb	.copyattrsandns_loop_xmlns_prefixsearch
	; if we fell through, no prefix match occurred, so go ahead and add our xmlns:
	pop	r13
	mov	rdi, r14
	mov	rsi, r15
	call	vector$push_back
	; all done, go to the next attribute
	add	r13d, 1
	cmp	r13d, [r12+xmltag_attrcount_ofs]
	jb	.copyattrsandns_loop
	pop	r15 r14 r13 r12 rbx
	ret
calign
.copyattrsandns_loop_normalattr:
	; so when we get here, r14 is the xmltagattr object, r15 is the unescaped attribute name that is _not_ an xmlns
	; we can go ahead and unescape the attribute value:
	mov	rdi, r12
	mov	rsi, [r14+xmltagattr_valuestart_ofs]
	mov	rdx, [r14+xmltagattr_valueend_ofs]
	call	xmltag$unescape
	mov	r14, rax
	; so now, r14 is the unescaped attribute value, r15 is the unescaped attribute name
	; go ahead and create an attribute node, with its parent set to rbx
	mov	edi, xmlmemnode_type_attribute
	mov	rsi, rbx
	call	xmlmemnode$new_type
	; set its text value so we can reuse r14 to hangon to the node
	mov	rdi, rax
	mov	rsi, r14
	mov	r14, rax
	call	xmlmemnode$settextvalue_nocopy
	; so now, r14 is our new attribute node with its value already set, r15 is the unescaped attribute name
	; find the namespace (if any) for this attributeName so we can set its QName
	mov	rdi, r15
	mov	esi, ':'
	call	string$indexof_charcode
	xor	edx, edx
	cmp	rax, 0
	jl	.copyattrsandns_loop_attr_ns_found
	je	.copyattrsandns_loop_attr_badqname
	; otherwise, get our prefix, set our attribute name in r15 to its unprefixed remainder
	push	rax
	mov	rdi, r15
	xor	esi, esi
	mov	rdx, rax
	call	string$substring
	mov	rdi, r15
	mov	rsi, [rsp]
	mov	rdx, [r15]
	add	rsi, 1
	mov	[rsp], rax
	call	string$substring
	mov	rdi, r15
	mov	r15, rax
	call	heap$free
	; when we get here, r14 is our attribute node with its textvalue already set
	; so now, [rsp] is the prefix string, and r15 is the non-prefixed attribute name
	; now, starting at our node in rbx, find our prefix in the namespace scope
	; (scope == rbx, its parent, its parent, etc. etc.)
	push	rbx r13 r14
calign
.copyattrsandns_loop_attr_prefixsearch_outer:
	mov	r14, [rbx+xmlmemnode_namespaces_ofs]
	xor	r13d, r13d
	test	r14, r14
	jz	.copyattrsandns_loop_attr_prefixsearch_nextscope
	; otherwise, there are namespaces here, search for our prefix in [rsp+24]
calign
.copyattrsandns_loop_attr_prefixsearch_nsloop:
	mov	rdi, r14
	mov	esi, r13d
	call	vector$at
	; xmlmemns object in rax, get its prefix
	mov	rdi, [rsp+24]
	mov	rsi, [rax+xmlmemns_prefix_ofs]
	push	rax
	call	string$equals
	pop	rdx
	test	eax, eax
	jnz	.copyattrsandns_loop_attr_ns_found_stackmod
	add	r13d, 1
	cmp	r13d, dword [r14+vector_count_ofs]
	jb	.copyattrsandns_loop_attr_prefixsearch_nsloop
.copyattrsandns_loop_attr_prefixsearch_nextscope:
	mov	rbx, [rbx+xmlmemnode_parent_ofs]
	test	rbx, rbx
	jnz	.copyattrsandns_loop_attr_prefixsearch_outer
	; otherwise, we went all the way up and didn't find the prefix in scope
	; fail with a prefixnotbound error
	pop	r14 r13 rbx
	pop	rdi
	call	heap$free		; the failed prefix string
	mov	rdi, r14
	call	xmlmemnode$destroy
	mov	rdi, r15		; the rest of the attr name
	call	heap$free
	pop	r15 r14 r13 r12 rbx
	mov	r14d, xmlparser_prefixnotbound
	ret
calign
.copyattrsandns_loop_attr_ns_found_stackmod:
	; xmlmemns object pointer sitting in rdx is the one we were after
	; but we have to cleanup our stack and free the prefix string
	pop	r14 r13 rbx
	mov	rdi, [rsp]		; the prefix string
	mov	[rsp], rdx
	call	heap$free
	pop	rdx
	; fallthrough to attr_ns_found:
calign
.copyattrsandns_loop_attr_ns_found:
	; ns sitting in rdx for our call to setqname
	; attribute node is in r14, r15 is our attribute name sans the prefix

	; hangon to our ns in the newly created attribute node:
	mov	[r14+xmlmemnode_ns_ofs], rdx
	; rdx is still okay, but we have to hangon to its value for our dup checking
	push	rbx r13 r14
	mov	r13, rdx
	xor	r14d, r14d
	sub	rsp, 8			; need room to store one more var
	; so now, well-formed xml isn't allowed to contain duplicate attribute names
	; Soooooo, first up, make sure our node in rbx actually has an attribute list
	mov	rax, [rbx+xmlmemnode_attributes_ofs]
	test	rax, rax
	jnz	.copyattrsandns_loop_attr_attrsokay
	call	vector$new
	mov	[rbx+xmlmemnode_attributes_ofs], rax
.copyattrsandns_loop_attr_attrsokay:
	mov	rbx, rax
	; so now, we have to iterate through each attribute and check for duplicate
	cmp	qword [rax+vector_count_ofs], 0
	je	.copyattrsandns_loop_attr_notdup
calign
.copyattrsandns_loop_attr_duploop:
	; get the attribute at position r14d
	mov	rdi, rbx
	mov	esi, r14d
	call	vector$at
	; do a qname match check against it w/ r15 (the non-prefixed name) and the xmlmemns object in r13
	; the xmlmemns object might be zero
	; hangon to the attribute node:
	mov	[rsp], rax
	; compare the names alone first, then check namespaces
	mov	rdi, r15
	mov	rsi, [rax+xmlmemnode_name_ofs]
	call	string$equals
	test	eax, eax
	jz	.copyattrsandns_loop_attr_dupnext
	; localname matches this attribute node's name, see if their namespaces are the same
	mov	rax, [rsp]
	cmp	r13, [rax+xmlmemnode_ns_ofs]		; pointer-exact match before string compare of URI
	je	.copyattrsandns_loop_attr_duplicate
	test	r13, r13
	jnz	.copyattrsandns_loop_attr_dup_withns
	; otherwise, our localname is without an ns, which means the node we are comparing to
	; has a namespace...
	; special case here where we are supposed to check against the "real" default namespace:
	mov	rdx, [rax+xmlmemnode_ns_ofs]
	mov	rdi, [rdx+xmlmemns_uri_ofs]
	mov	rsi, .defaultnamespace
	call	string$equals
	test	eax, eax
	jnz	.copyattrsandns_loop_attr_duplicate
	; otherwise, fallthrough to dupnext:
calign
.copyattrsandns_loop_attr_dupnext:
	add	r14d, 1
	cmp	r14d, [rbx+vector_count_ofs]
	jb	.copyattrsandns_loop_attr_duploop
	; if we made it to the end, go ahead and fallthrough to notdup:
calign
.copyattrsandns_loop_attr_notdup:
	add	rsp, 8
	; add the attribute to the attributes vector in rbx
	pop	r14 r13
	mov	rdi, rbx
	mov	rsi, r14
	call	vector$push_back
	pop	rbx
	; set the attribute's parent to the node in rbx
	mov	[r14+xmlmemnode_parent_ofs], rbx
	; set its qname to r15
	mov	rdi, r14
	mov	rsi, r15
	mov	rdx, [r14+xmlmemnode_ns_ofs]
	call	xmlmemnode$setqname_nocopy
	; all done, go to the next attribute
	add	r13d, 1
	cmp	r13d, [r12+xmltag_attrcount_ofs]
	jb	.copyattrsandns_loop
	pop	r15 r14 r13 r12 rbx
	ret
cleartext .defaultnamespace, 'http://www.w3.org/XML/1998/namespace'
calign
.copyattrsandns_loop_attr_dup_withns:
	; so, r13 (an xmlmemns object) is nonzero, our comparison node at [rsp] may or may not have one
	cmp	qword [rax+xmlmemnode_ns_ofs], 0
	je	.copyattrsandns_loop_attr_dup_checkdefault
	; otherwise, both r13 and the comparison node have xmlmemns objects that are not the same pointer
	mov	rdx, [rax+xmlmemnode_ns_ofs]
	mov	rdi, [r13+xmlmemns_uri_ofs]
	mov	rsi, [rdx+xmlmemns_uri_ofs]
	call	string$equals
	test	eax, eax
	jz	.copyattrsandns_loop_attr_notdup
	jmp	.copyattrsandns_loop_attr_duplicate
calign
.copyattrsandns_loop_attr_dup_checkdefault:
	mov	rdi, [r13+xmlmemns_uri_ofs]
	mov	rsi, .defaultnamespace
	call	string$equals
	test	eax, eax
	jz	.copyattrsandns_loop_attr_notdup
	; otherwise, fallthrough to copyattrsandns_loop_attr_duplicate
calign
.copyattrsandns_loop_attr_duplicate:
	add	rsp, 8
	pop	r14 r13 rbx
	mov	rdi, r14
	call	xmlmemnode$destroy
	; since we didn't yet setqname on the attribute node, we also have to free the string in r15
	mov	rdi, r15
	call	heap$free
	pop	r15 r14 r13 r12 rbx
	mov	r14d, xmlparser_duplicateattribute
	ret
	
calign
.copyattrsandns_loop_attr_badqname:
	; r14 node has to be destroyed
	mov	rdi, r14
	call	xmlmemnode$destroy
	; fallthrough to copyattrsandns_loop_badqname
calign
.copyattrsandns_loop_badqname:
	; when we get here, r15 == a bad qname, something like "xmlns:" or similar
	mov	rdi, r15
	call	heap$free
	pop	r15 r14 r13 r12 rbx
	mov	r14d, xmlparser_badqname
	ret
	
calign
.copyattrsandns_loop_prefixmash:
	; so we found a matching prefix to overwrite, its at offset r13d
	mov	rdi, r14
	mov	esi, r13d
	call	vector$at
	mov	rsi, [r15+xmlmemns_uri_ofs]
	mov	rdi, [rax+xmlmemns_uri_ofs]
	mov	[rax+xmlmemns_uri_ofs], rsi
	mov	[r15+xmlmemns_uri_ofs], rdi	; swapped
	pop	r13
	; now we can destroy our new xmlmemns:
	mov	rdi, r15
	call	xmlmemns$destroy
	; all done, go to the next attribute
	add	r13d, 1
	cmp	r13d, [r12+xmltag_attrcount_ofs]
	jb	.copyattrsandns_loop
	pop	r15 r14 r13 r12 rbx
	ret
calign
.copyattrsandns_loop_xmlns_new:
	; we want a vector with precisely one item in it
	mov	rdi, r15
	call	vector$new_fromunsigned
	mov	[rbx+xmlmemnode_namespaces_ofs], rax

	; all done, go to the next attribute
	add	r13d, 1
	cmp	r13d, [r12+xmltag_attrcount_ofs]
	jb	.copyattrsandns_loop
	pop	r15 r14 r13 r12 rbx
.copyattrsandns_ret:
	ret

calign
.copyattrsandns_loop_defaultns:
	; r15 == "xmlns" and nothing more
	mov	rdi, r15
	call	heap$free
	call	string$new
	mov	r15, rax
	jmp	.copyattrsandns_loop_xmlns_doit
cleartext .xmlns, 'xmlns'

calign
.element:
	; so if we got here and there is no r12, we need to promote ourselves to a document fragment
	; (no doctype or ?xmldecl exists)
	test	r12, r12
	jnz	.element_nopromote
	mov	edi, xmlmemnode_type_document_fragment
	xor	esi, esi
	call	xmlmemnode$new_type
	mov	r12, rax
	mov	r13, rax
.element_nopromote:
	; get the tag's text:
	mov	rdi, rsp
	call	xmltag$text
	mov	r15, rax
	; xmlparser would have errored out if it was a null tag name, so check the first character
if string_bits = 32
	cmp	dword [r15+8], '/'
else
	cmp	word [r15+8], '/'
end if
	je	.element_closing
	; otherwise, create a new element node
	mov	edi, xmlmemnode_type_element
	xor	esi, esi
	call	xmlmemnode$new_type
	push	rax
	mov	rdi, r13
	mov	rsi, rax
	call	xmlmemnode$appendchild
	mov	rax, [rsp]
	cmp	dword [rsp+xmltag_empty_ofs+8], 0
	cmove	r13, rax
	mov	rdi, rax
	lea	rsi, [rsp+8]			; the xmltag object
	call	.copyattrsandns			; do this first because our tag name might reference an attributed xmlns
	mov	rdi, [rsp]
	call	.findnamespace
	pop	rdi
	mov	ecx, xmlparser_badqname
	mov	r8d, xmlparser_prefixnotbound
	cmp	rax, -1
	cmove	r14d, ecx
	je	.bailout
	cmp	rax, -2
	cmove	r14d, r8d
	je	.bailout
	mov	rsi, r15
	mov	rdx, rax
	call	xmlmemnode$setqname_nocopy
	jmp	.outer
calign
.element_closing:
	mov	rdi, r15
	mov	esi, 1
	mov	rdx, -1
	call	string$substr
	mov	rdi, r15
	mov	r15, rax
	call	heap$free
	mov	rdi, r13
	call	.findnamespace
	mov	ecx, xmlparser_badqname
	mov	r8d, xmlparser_prefixnotbound
	cmp	rax, -1
	cmove	r14d, ecx
	je	.bailout
	cmp	rax, -2
	cmove	r14d, r8d
	je	.bailout
	mov	ecx, xmlparser_unterminatedelement
	cmp	rax, [r13+xmlmemnode_ns_ofs]
	cmovne	r14d, ecx
	jne	.bailout
	mov	rdi, [r13+xmlmemnode_name_ofs]
	mov	rsi, r15
	call	string$equals
	mov	ecx, xmlparser_unterminatedelement
	test	eax, eax
	cmovz	r14d, ecx
	jz	.bailout
	; otherwise, if r13 != r12, set r13 = its parent
	mov	rax, [r13+xmlmemnode_parent_ofs]
	cmp	r13, r12
	cmovne	r13, rax
	mov	rdi, r15
	call	heap$free
	jmp	.outer
calign
.textnode:
	mov	edi, xmlmemnode_type_text
.textnode_doit:
	xor	esi, esi
	call	xmlmemnode$new_type
	test	r12, r12
	cmovz	r12, rax
	mov	r15, rax
	; get the tag's text:
	mov	rdi, rsp
	call	xmltag$text
	mov	rdi, r15
	mov	rsi, rax
	call	xmlmemnode$settextvalue_nocopy
	cmp	r15, r12
	je	.outer
	mov	rdi, r13
	mov	rsi, r15
	call	xmlmemnode$appendchild
	jmp	.outer
calign
.cdata:
	mov	edi, xmlmemnode_type_cdata
	jmp	.textnode_doit
calign
.comment:
	mov	edi, xmlmemnode_type_comment
	jmp	.textnode_doit
calign
.pi:
	mov	rdi, rsp
	call	xmltag$text
	mov	r15, rax
	mov	rdi, rax
	mov	esi, ' '
	call	string$indexof_charcode
	cmp	rax, 0
	jl	.pi_nameonly
	; otherwise, name and value, skipping whitespace from rax forward
	push	rax
	mov	rdi, r15
	xor	esi, esi
	mov	rdx, rax
	call	string$substring
	mov	rsi, [rsp]
	mov	[rsp], rax			; name portion
	mov	rdi, r15
	call	string$skip_whitespace
	mov	rdi, r15
	mov	rsi, rax
	mov	rdx, -1
	call	string$substr
	push	rax
	; so now, [rsp] == value, [rsp+8] == name, we are done with string in r15
	mov	rdi, r15
	call	heap$free
	mov	edi, xmlmemnode_type_pi
	xor	esi, esi
	call	xmlmemnode$new_type
	test	r12, r12
	cmovz	r12, rax
	mov	r15, rax
	mov	rdi, rax
	pop	rsi
	call	xmlmemnode$settextvalue_nocopy
	mov	rdi, r15
	pop	rsi
	xor	edx, edx	; no NS
	call	xmlmemnode$setqname_nocopy
	cmp	r15, r12
	je	.outer
	mov	rdi, r13
	mov	rsi, r15
	call	xmlmemnode$appendchild
	jmp	.outer
calign
.pi_nameonly:
	; r15 == name, value needs to be string$new
	mov	edi, xmlmemnode_type_pi
	xor	esi, esi
	call	xmlmemnode$new_type
	test	r12, r12
	cmovz	r12, rax
	mov	rdi, rax
	mov	rsi, r15
	xor	edx, edx	; no NS
	mov	r15, rax
	call	xmlmemnode$setqname_nocopy
	call	string$new
	mov	rdi, r15
	mov	rsi, rax
	call	xmlmemnode$settextvalue_nocopy
	cmp	r15, r12
	je	.outer
	mov	rdi, r13
	mov	rsi, r15
	call	xmlmemnode$appendchild
	jmp	.outer

calign
.doctype:
	; this one works similarly to the text, cdata, etc
	; but like .xmldecl, we might promote ourselves to a document
	test	r12, r12
	jnz	.doctype_nopromote
	mov	edi, xmlmemnode_type_document
	xor	esi, esi
	call	xmlmemnode$new_type
	mov	r12, rax
	mov	r13, rax
.doctype_nopromote:
	mov	edi, xmlmemnode_type_doctype
	xor	esi, esi
	call	xmlmemnode$new_type
	mov	r15, rax
	; get the tag's text:
	mov	rdi, rsp
	call	xmltag$text
	mov	rdi, r15
	mov	rsi, rax
	call	xmlmemnode$settextvalue_nocopy
	cmp	r15, r12
	je	.outer
	mov	rdi, r13
	mov	rsi, r15
	call	xmlmemnode$appendchild
	jmp	.outer
calign
.xmldecl:
	; xmldecls are not really processing instructions, but we preserve them as such
	; so that we can spit it back out the same way we got it
	; if we don't yet have a "this", promote ourselves to a document
	test	r12, r12
	jnz	.xmldecl_nopromote
	mov	edi, xmlmemnode_type_document
	xor	esi, esi
	call	xmlmemnode$new_type
	mov	r12, rax
	mov	r13, rax
.xmldecl_nopromote:
	mov	edi, xmlmemnode_type_pi
	xor	esi, esi
	call	xmlmemnode$new_type
	mov	r15, rax
	; get the tag's text
	mov	rdi, rsp
	call	xmltag$text
	mov	rdi, r15
	mov	rsi, rax
	call	xmlmemnode$settextvalue_nocopy
	; set its qname
	mov	rdi, r15
	mov	rsi, .xmldeclstr
	xor	edx, edx		; no NS
	call	xmlmemnode$setqname
	mov	rdi, r13
	mov	rsi, r15
	call	xmlmemnode$appendchild
	jmp	.outer
cleartext .xmldeclstr, 'xml'
.bailout:
	cmp	r14d, xmlparser_noerror
	je	.bailout_checkparent
	cmp	r14d, xmlparser_endofdocument
	je	.bailout_checkparent
	; otherwise, an error has occurred... r12 should be the root of all evil, destroy it:
	test	r12, r12
	jz	.bailout_nodestroy
	mov	rdi, r12
	call	xmlmemnode$destroy
.bailout_nodestroy:
	; our return needs to be null, with edx == r14d
	mov	edx, r14d
	xor	eax, eax
	add	rsp, xmltag_size
	pop	r15 r14 r13 r12 rbx
	epilog
calign
.bailout_checkparent:
	cmp	r12, r13
	jne	.bailout_unterminated
	mov	rax, r12
	xor	edx, edx
	add	rsp, xmltag_size
	pop	r15 r14 r13 r12 rbx
	epilog
calign
.bailout_unterminated:
	test	r12, r12
	jz	.bailout_unterminated_nodestroy
	mov	rdi, r12
	call	xmlmemnode$destroy
.bailout_unterminated_nodestroy:
	mov	edx, xmlparser_unterminatedelement
	xor	eax, eax
	add	rsp, xmltag_size
	pop	r15 r14 r13 r12 rbx
	epilog

end if


if used xmlmemnode$destroy | defined include_everything
	; single argument in rdi: an xmlmemnode to destroy
falign
xmlmemnode$destroy:
	prolog	xmlmemnode$destroy
	mov	ecx, [rdi+xmlmemnode_flags_ofs]
	push	rbx
	mov	rbx, rdi
	test	ecx, xmlmemnode_type_attribute or xmlmemnode_type_text or xmlmemnode_type_cdata or xmlmemnode_type_comment or xmlmemnode_type_pi
	jnz	.namevalonly
	test	ecx, xmlmemnode_type_document or xmlmemnode_type_document_fragment
	jnz	.docorfrag
	; element or unknown (we'll check for unset)
	test	ecx, xmlmemnode_flag_name_owner
	jz	.element_notnameowner
	mov	rdi, [rdi+xmlmemnode_name_ofs]
	test	rdi, rdi
	jz	.element_notnameowner
	call	heap$free
.element_notnameowner:
	mov	rdi, [rbx+xmlmemnode_attributes_ofs]
	test	rdi, rdi
	jz	.element_noattributes
	mov	rsi, xmlmemnode$destroy
	mov	rdx, rbx
	call	vector$clear_arg
	mov	rdi, [rbx+xmlmemnode_attributes_ofs]
	call	vector$destroy
.element_noattributes:
	mov	rdi, [rbx+xmlmemnode_namespaces_ofs]
	test	rdi, rdi
	jz	.element_nonamespaces
	mov	rsi, xmlmemns$destroy
	mov	rdx, rbx
	call	vector$clear_arg
	mov	rdi, [rbx+xmlmemnode_namespaces_ofs]
	call	vector$destroy
.element_nonamespaces:
	mov	rdi, [rbx+xmlmemnode_children_ofs]
	test	rdi, rdi
	jz	.element_nochildren
	mov	rsi, xmlmemnode$destroy
	mov	rdx, rbx
	call	vector$clear_arg
	mov	rdi, [rbx+xmlmemnode_children_ofs]
	call	vector$destroy
.element_nochildren:
	; done, dusted, free our xmlmemnode object in rbx and be done
	mov	rdi, rbx
	pop	rbx
	call	heap$free
	epilog
.namevalonly:
	test	ecx, xmlmemnode_flag_name_owner
	jz	.namevalonly_notnameowner
	mov	rdi, [rdi+xmlmemnode_name_ofs]
	test	rdi, rdi
	jz	.namevalonly_notnameowner
	call	heap$free
.namevalonly_notnameowner:
	mov	rdi, [rbx+xmlmemnode_value_ofs]
	test	rdi, rdi
	jz	.namevalonly_novalue
	call	heap$free
.namevalonly_novalue:
	mov	rdi, rbx
	pop	rbx
	call	heap$free
	epilog
.docorfrag:
	; these should not have a name, just the names stringmap
	mov	rdi, [rdi+xmlmemnode_names_ofs]
	mov	rsi, heap$free
	call	stringmap$clear
	mov	rdi, [rbx+xmlmemnode_names_ofs]
	call	heap$free
	mov	rdi, rbx
	pop	rbx
	call	heap$free
	epilog

end if


if used xmlmemnode$setqname | defined include_everything
	; three arguments: rdi == xmlmemnode, rsi == string name, rdx == xmlmemns pointer (may be zero)
	; makes a copy of the string in rsi and/or does not assume ownership
	; returns a bool as to whether it was okay to do so (invalid node type check)
falign
xmlmemnode$setqname:
	prolog	xmlmemnode$setqname
	mov	ecx, [rdi+xmlmemnode_flags_ofs]
	xor	eax, eax
	test	ecx, xmlmemnode_type_pi
	jnz	.copy
	test	ecx, xmlmemnode_type_element or xmlmemnode_type_attribute
	jz	.ret
	; otherwise, it is an attribute or element node... and these must all be "rooted (says I from Australia)"
	; under a document or document_fragment node, and that contains an xmlmemnode_names_ofs stringmap
	mov	[rdi+xmlmemnode_ns_ofs], rdx
	mov	rcx, rdi
calign
.findroot:
	mov	rax, [rcx+xmlmemnode_parent_ofs]
	test	rax, rax
	cmovnz	rcx, rax
	jnz	.findroot
	; so now, rcx is the topmost node, which must be a document or document_fragment in order for us to cache it
	test	dword [rcx+xmlmemnode_flags_ofs], xmlmemnode_type_document or xmlmemnode_type_document_fragment
	jz	.copy
	; otherwise, it is held in a doc/docfrag, so find our string in rsi in the map, or make a new entry
	mov	rcx, [rcx+xmlmemnode_names_ofs]	; the stringmap we are interested in
	push	rdi rsi rcx
	mov	rdi, rcx
	call	stringmap$find
	test	rax, rax
	jz	.newmapentry
	; otherwise, there is already a matching string:
	mov	rax, [rax+_avlofs_key]		; grab the key string from the stringmap
	pop	rcx rsi rdi
	mov	[rdi+xmlmemnode_name_ofs], rax
	mov	eax, 1
	epilog
.newmapentry:
	mov	rdi, [rsp+8]
	call	string$copy
	mov	[rsp+8], rax
	mov	rdi, [rsp]
	mov	rsi, rax
	xor	edx, edx
	call	stringmap$insert_unique
	pop	rcx rsi rdi
	mov	[rdi+xmlmemnode_name_ofs], rsi
	mov	eax, 1
	epilog
.copy:
	or	dword [rdi+xmlmemnode_flags_ofs], xmlmemnode_flag_name_owner
	mov	[rdi+xmlmemnode_ns_ofs], rdx
	push	rdi
	cmp	qword [rdi+xmlmemnode_name_ofs], 0
	jne	.oldone
	mov	rdi, rsi
	call	string$copy
	pop	rdi
	mov	[rdi+xmlmemnode_name_ofs], rax
	mov	eax, 1
.ret:
	epilog
.oldone:
	push	rsi
	test	dword [rdi+xmlmemnode_flags_ofs], xmlmemnode_flag_name_owner
	jz	.oldone_nofree
	mov	rdi, [rdi+xmlmemnode_name_ofs]
	call	heap$free
.oldone_nofree:
	pop	rdi
	call	string$copy
	pop	rdi
	mov	[rdi+xmlmemnode_name_ofs], rax
	mov	eax, 1
	epilog

end if


if used xmlmemnode$setqname_nocopy | defined include_everything
	; three arguments: rdi == xmlmemnode, rsi == string name, rdx == xmlmemns pointer (may be zero)
	; assumes ownership of the string in rsi
	; returns a bool as to whether it was okay to do so (invalid node type check)
falign
xmlmemnode$setqname_nocopy:
	prolog	xmlmemnode$setqname_nocopy
	mov	ecx, [rdi+xmlmemnode_flags_ofs]
	xor	eax, eax
	test	ecx, xmlmemnode_type_pi
	jnz	.owned
	test	ecx, xmlmemnode_type_element or xmlmemnode_type_attribute
	jz	.ret
	; otherwise, it is an attribute or element node, and these must all be "rooted (says I from Australia)"
	; under a document or document_fragment node, and that contains an xmlmemnode_names_ofs stringmap
	mov	[rdi+xmlmemnode_ns_ofs], rdx
	mov	rcx, rdi
calign
.findroot:
	mov	rax, [rcx+xmlmemnode_parent_ofs]
	test	rax, rax
	cmovnz	rcx, rax
	jnz	.findroot
	; so now, rcx is the topmost node, which must be a document or document_fragment in order for us to cache it
	test	dword [rcx+xmlmemnode_flags_ofs], xmlmemnode_type_document or xmlmemnode_type_document_fragment
	jz	.owned
	; otherwise, it is held in a doc/docfrag, so find our string in rsi in the map, or make a new entry
	mov	rcx, [rcx+xmlmemnode_names_ofs]	; the stringmap we are interested in
	push	rdi rsi rcx
	mov	rdi, rcx
	call	stringmap$find
	test	rax, rax
	jz	.newmapentry
	; otherwise, there is already a matching string:
	mov	rax, [rax+_avlofs_key]		; grab the key string from the stringmap
	pop	rcx rsi rdi
	mov	[rdi+xmlmemnode_name_ofs], rax
	; free our string since we are to assume ownership fo it
	mov	rdi, rsi
	call	heap$free
	mov	eax, 1
	epilog
.newmapentry:
	mov	rdi, [rsp]
	mov	rsi, [rsp+8]
	xor	edx, edx
	call	stringmap$insert_unique
	pop	rcx rsi rdi
	mov	[rdi+xmlmemnode_name_ofs], rsi
	mov	eax, 1
	epilog
.owned:
	or	dword [rdi+xmlmemnode_flags_ofs], xmlmemnode_flag_name_owner
	mov	[rdi+xmlmemnode_ns_ofs], rdx
	cmp	qword [rdi+xmlmemnode_name_ofs], 0
	jne	.oldone
	mov	[rdi+xmlmemnode_name_ofs], rsi
	mov	eax, 1
.ret:
	epilog
.oldone:
	test	dword [rdi+xmlmemnode_flags_ofs], xmlmemnode_flag_name_owner
	jz	.oldone_nofree
	push	rdi rsi
	mov	rdi, [rdi+xmlmemnode_name_ofs]
	call	heap$free
	pop	rsi rdi
.oldone_nofree:
	mov	[rdi+xmlmemnode_name_ofs], rsi
	mov	eax, 1
	epilog

end if


if used xmlmemnode$settextvalue | defined include_everything
	; two arguments: rdi == xmlmemnode, rsi == string text value
	; returns a bool as to whether it was okay to do so (invalid node type check)
	; makes a copy of the string in rsi
falign
xmlmemnode$settextvalue:
	prolog	xmlmemnode$settextvalue
	xor	eax, eax
	test	dword [rdi+xmlmemnode_flags_ofs], xmlmemnode_type_text or xmlmemnode_type_attribute or xmlmemnode_type_cdata or xmlmemnode_type_comment or xmlmemnode_type_pi or xmlmemnode_type_doctype
	jz	.ret
	push	rdi
	cmp	qword [rdi+xmlmemnode_value_ofs], 0
	jne	.oldone
	mov	rdi, rsi
	call	string$copy
	pop	rdi
	mov	[rdi+xmlmemnode_value_ofs], rax
	mov	eax, 1
.ret:
	epilog
.oldone:
	push	rsi
	mov	rdi, [rdi+xmlmemnode_value_ofs]
	call	heap$free
	pop	rdi
	call	string$copy
	pop	rdi
	mov	[rdi+xmlmemnode_value_ofs], rax
	mov	eax, 1
	epilog

end if


if used xmlmemnode$settextvalue_nocopy | defined include_everything
	; two arguments: rdi == xmlmemnode, rsi == string text value
	; returns a bool as to whether it was okay to do so (invalid node type check)
	; assumes ownership of the string passed (unlike the non _nocopy version which copies it)
falign
xmlmemnode$settextvalue_nocopy:
	prolog	xmlmemnode$settextvalue_nocopy
	xor	eax, eax
	test	dword [rdi+xmlmemnode_flags_ofs], xmlmemnode_type_text or xmlmemnode_type_attribute or xmlmemnode_type_cdata or xmlmemnode_type_comment or xmlmemnode_type_pi or xmlmemnode_type_doctype
	jz	.ret
	cmp	qword [rdi+xmlmemnode_value_ofs], 0
	jne	.oldone
	mov	[rdi+xmlmemnode_value_ofs], rsi
	mov	eax, 1
.ret:
	epilog
.oldone:
	push	rdi rsi
	mov	rdi, [rdi+xmlmemnode_value_ofs]
	call	heap$free
	pop	rsi rdi
	mov	[rdi+xmlmemnode_value_ofs], rsi
	mov	eax, 1
	epilog

end if


if used xmlmemnode$appendchild | defined include_everything
	; two arguments: rdi == xmlmemnode, rsi == child xmlmemnode
	; returns bool as to whether it was okay to do so (invalid node type check)
	; also sets parent of the child to rdi
falign
xmlmemnode$appendchild:
	prolog	xmlmemnode$appendchild
	xor	eax, eax
	test	dword [rdi+xmlmemnode_flags_ofs], xmlmemnode_type_element or xmlmemnode_type_document or xmlmemnode_type_document_fragment
	jz	.ret
	mov	[rsi+xmlmemnode_parent_ofs], rdi
	cmp	qword [rdi+xmlmemnode_children_ofs], 0
	je	.newkids
	mov	rdi, [rdi+xmlmemnode_children_ofs]
	call	vector$push_back
	mov	eax, 1
.ret:
	epilog
.newkids:
	push	rdi
	mov	rdi, rsi
	call	vector$new_fromunsigned
	pop	rdi
	mov	[rdi+xmlmemnode_children_ofs], rax
	mov	eax, 1
	epilog

end if


if used xmlmemnode$tostring | defined include_everything
	; single argument in rdi: xmlmemnode
	; returns heap$alloc'd new string
falign
xmlmemnode$tostring:
	prolog	xmlmemnode$tostring
	push	rbx rdi
	call	buffer$new
	pop	rdi
	mov	rbx, rax
	xor	esi, esi
	call	.descend
	mov	rdi, [rbx+buffer_itself_ofs]
	mov	rsi, [rbx+buffer_length_ofs]
if string_bits = 32
	call	string$from_utf32
else
	call	string$from_utf16
end if
	mov	rdi, rbx
	mov	rbx, rax
	call	buffer$destroy
	mov	rax, rbx
	pop	rbx
	epilog
falign
.descend:
	; on entry: rdi is the node we are sorting, esi == indent level
	push	r12 r13
	mov	r12, rdi
	mov	r13d, esi
	mov	ecx, [rdi+xmlmemnode_flags_ofs]
	test	esi, esi
	jz	.noindent
	test	ecx, xmlmemnode_type_document or xmlmemnode_type_document_fragment
	jnz	.noindent
	push	r14
	mov	r14d, r13d
calign
.indent:
	mov	rdi, rbx
	mov	esi, ' '
if string_bits = 32
	call	buffer$append_dword
else
	call	buffer$append_word
end if
	sub	r14d, 1
	jnz	.indent
	pop	r14
.noindent:
	mov	ecx, [r12+xmlmemnode_flags_ofs]
	test	ecx, xmlmemnode_type_text or xmlmemnode_type_attribute
	jnz	.textnode
	test	ecx, xmlmemnode_type_cdata
	jnz	.cdata
if defined xmlmemnode_attributes_separate
	test	ecx, xmlmemnode_type_attribute
	jnz	.attr
end if
	test	ecx, xmlmemnode_type_comment
	jnz	.comment
	test	ecx, xmlmemnode_type_doctype
	jnz	.doctype
	test	ecx, xmlmemnode_type_pi
	jnz	.pi
	test	ecx, xmlmemnode_type_element
	jz	.notelement

	mov	rdi, rbx
	mov	esi, '<'
if string_bits = 32
	call	buffer$append_dword
else
	call	buffer$append_word
end if
	cmp	qword [r12+xmlmemnode_ns_ofs], 0
	je	.element_no_namespace
	; otherwise, add its prefix
	mov	rdi, rbx
	mov	rsi, [r12+xmlmemnode_ns_ofs]
	mov	rsi, [rsi+xmlmemns_prefix_ofs]
	cmp	qword [rsi], 0
	je	.element_no_namespace
	call	buffer$append_rawstring
	mov	rdi, rbx
	mov	esi, ':'
if string_bits = 32
	call	buffer$append_dword
else
	call	buffer$append_word
end if
.element_no_namespace:
	mov	rdi, rbx
	mov	rsi, [r12+xmlmemnode_name_ofs]
	call	buffer$append_rawstring
	; iterate through attributes if any
	push	r14 r15
	mov	r14, [r12+xmlmemnode_attributes_ofs]
	xor	r15d, r15d
	test	r14, r14
	jz	.element_no_attributes
	cmp	qword [r14+vector_count_ofs], 0
	je	.element_no_attributes
calign
.element_attribute_loop:
	mov	rdi, rbx
	mov	esi, ' '
if string_bits = 32
	call	buffer$append_dword
else
	call	buffer$append_word
end if
	mov	rdi, r14
	mov	esi, r15d
	call	vector$at
	push	rax		; attribute node
	; see if it has a namespace
	cmp	qword [rax+xmlmemnode_ns_ofs], 0
	je	.element_attribute_no_namespace
	mov	rdi, rbx
	mov	rsi, [rax+xmlmemnode_ns_ofs]
	mov	rsi, [rsi+xmlmemns_prefix_ofs]
	cmp	qword [rsi], 0
	je	.element_attribute_no_namespace
	call	buffer$append_rawstring
	mov	rdi, rbx
	mov	esi, ':'
if string_bits = 32
	call	buffer$append_dword
else
	call	buffer$append_word
end if
.element_attribute_no_namespace:
	mov	rax, [rsp]
	mov	rdi, rbx
	mov	rsi, [rax+xmlmemnode_name_ofs]
	call	buffer$append_rawstring
	mov	rdi, rbx
	mov	rsi, .equalquote
	call	buffer$append_rawstring
	; escape its value if any
	pop	rax
	cmp	qword [rax+xmlmemnode_value_ofs], 0
	je	.element_attribute_no_value
	mov	rdi, [rax+xmlmemnode_value_ofs]
	call	xmltag$escape_string
	push	rax
	mov	rdi, rbx
	mov	rsi, rax
	call	buffer$append_rawstring
	pop	rdi
	call	heap$free
.element_attribute_no_value:
	mov	rdi, rbx
	mov	esi, '"'
if string_bits = 32
	call	buffer$append_dword
else
	call	buffer$append_word
end if
	; done with this attribute
	add	r15d, 1
	cmp	r15d, [r14+vector_count_ofs]
	jb	.element_attribute_loop
	; fall through to no attributes

.element_no_attributes:
	; do the same again for namespaces if any
	mov	r14, [r12+xmlmemnode_namespaces_ofs]
	xor	r15d, r15d
	test	r14, r14
	jz	.element_no_namespaces
	cmp	qword [r14+vector_count_ofs], 0
	je	.element_no_namespaces
calign
.element_namespace_loop:
	mov	rdi, rbx
	mov	rsi, .spacexmlns
	call	buffer$append_rawstring
	mov	rdi, r14
	mov	esi, r15d
	call	vector$at
	push	rax
	mov	rsi, [rax+xmlmemns_prefix_ofs]
	cmp	qword [rsi], 0
	je	.element_namespace_noprefix
	mov	rdi, rbx
	mov	esi, ':'
if string_bits = 32
	call	buffer$append_dword
else
	call	buffer$append_word
end if
	mov	rax, [rsp]
	mov	rdi, rbx
	mov	rsi, [rax+xmlmemns_prefix_ofs]
	call	buffer$append_rawstring
.element_namespace_noprefix:
	mov	rdi, rbx
	mov	rsi, .equalquote
	call	buffer$append_rawstring
	pop	rax
	mov	rdi, rbx
	mov	rsi, [rax+xmlmemns_uri_ofs]
	call	buffer$append_rawstring
	mov	rdi, rbx
	mov	esi, '"'
if string_bits = 32
	call	buffer$append_dword
else
	call	buffer$append_word
end if
	add	r15d, 1
	cmp	r15d, [r14+vector_count_ofs]
	jb	.element_namespace_loop

.element_no_namespaces:
	; if we have no children, spew /> and return
	pop	r15 r14
	mov	rdi, [r12+xmlmemnode_children_ofs]
	test	rdi, rdi
	jz	.element_no_children
	cmp	qword [rdi+vector_count_ofs], 0
	je	.element_no_children
	mov	rdi, rbx
	mov	esi, '>'
if string_bits = 32
	call	buffer$append_dword
else
	call	buffer$append_word
end if
	mov	rdi, [r12+xmlmemnode_children_ofs]
	jmp	.with_children
cleartext .spacexmlns, ' xmlns'
calign
.notelement:
	mov	rdi, [r12+xmlmemnode_children_ofs]
	test	rdi, rdi
	jz	.notelement_ret
	cmp	qword [rdi+vector_count_ofs], 0
	je	.notelement_ret
.with_children:
	xor	esi, esi
	call	vector$at
	; rax == first child
	; so here, r12 is our node, r13d is our indent level, rbx is the buffer we are adding to
	push	r13 r14 r15
	mov	rdi, [r12+xmlmemnode_children_ofs]
	xor	r14d, r14d		; our child index
	xor	edx, edx
	lea	esi, [r13d+2]
	mov	r15d, 1			; bool as to whether we should indent children or not

	; if we have an only child that is text or cdata, clear the indent children bool
	cmp	qword [rdi+vector_count_ofs], 1
	ja	.with_children_setindentlevel
	test	dword [rax+xmlmemnode_flags_ofs], xmlmemnode_type_text or xmlmemnode_type_cdata
	cmovnz	r15d, edx
.with_children_setindentlevel:
	; if r15d nonzero and our node is _not_ a document, set r13d = +2
	test	r15d, r15d
	cmovz	r13d, edx		; if !indent children, set next indent level to 0
	jz	.childloop
	test	dword [r12+xmlmemnode_flags_ofs], xmlmemnode_type_document or xmlmemnode_type_document_fragment
	cmovz	r13d, esi
calign
.childloop:
	mov	rdi, [r12+xmlmemnode_children_ofs]
	mov	esi, r14d
	call	vector$at
	test	dword [r12+xmlmemnode_flags_ofs], xmlmemnode_type_document or xmlmemnode_type_document_fragment
	jz	.childloop_notdocument
	test	r15d, r15d
	jz	.childloop_descend
	test	r14d, r14d
	jz	.childloop_descend
	mov	rdi, rbx
	mov	esi, 10
if string_bits = 32
	call	buffer$append_dword
else
	call	buffer$append_word
end if
	jmp	.childloop_descend
calign
.childloop_notdocument:
	test	r15d, r15d
	jz	.childloop_descend
	mov	rdi, rbx
	mov	esi, 10
if string_bits = 32
	call	buffer$append_dword
else
	call	buffer$append_word
end if
calign
.childloop_descend:
	mov	rdi, [r12+xmlmemnode_children_ofs]
	mov	esi, r14d
	call	vector$at
	mov	rdi, rax
	mov	esi, r13d
	call	.descend
	mov	rdi, [r12+xmlmemnode_children_ofs]
	add	r14d, 1
	cmp	r14d, [rdi+vector_count_ofs]
	jb	.childloop
	; restore the value of the original indentlevel for r13d
	mov	r13, [rsp+16]

	test	r15d, r15d
	jz	.childloop_done_noindent
	mov	rdi, rbx
	mov	esi, 10
if string_bits = 32
	call	buffer$append_dword
else
	call	buffer$append_word
end if
	test	r13d, r13d
	jz	.childloop_done_noindent
calign
.childloop_indent:
	mov	rdi, rbx
	mov	esi, ' '
if string_bits = 32
	call	buffer$append_dword
else
	call	buffer$append_word
end if
	sub	r13d, 1
	jnz	.childloop_indent
calign
.childloop_done_noindent:
	pop	r15 r14 r13
	test	dword [r12+xmlmemnode_flags_ofs], xmlmemnode_type_document or xmlmemnode_type_document_fragment
	jnz	.childloop_done_notelement
	; otherwise, 
	mov	rdi, rbx
	mov	rsi, .closetag
	call	buffer$append_rawstring
	cmp	qword [r12+xmlmemnode_ns_ofs], 0
	je	.childloop_done_element_nons
	mov	rdi, [r12+xmlmemnode_ns_ofs]
	mov	rsi, [rdi+xmlmemns_prefix_ofs]
	cmp	qword [rsi], 0
	je	.childloop_done_element_nons
	mov	rdi, rbx
	call	buffer$append_rawstring
	mov	rdi, rbx
	mov	esi, ':'
if string_bits = 32
	call	buffer$append_dword
else
	call	buffer$append_word
end if
.childloop_done_element_nons:
	mov	rdi, rbx
	mov	rsi, [r12+xmlmemnode_name_ofs]
	call	buffer$append_rawstring
	mov	rdi, rbx
	mov	esi, '>'
if string_bits = 32
	call	buffer$append_dword
else
	call	buffer$append_word
end if
.childloop_done_notelement:
	pop	r13 r12
	ret
calign
.element_no_children:
	mov	rdi, rbx
	mov	rsi, .emptyclose
	call	buffer$append_rawstring
	pop	r13 r12
	ret
cleartext .closetag, '
cleartext .emptyclose, '/>'
calign
.notelement_ret:
	ret
cleartext .equalquote, '="'
.textnode:
	; escape the value and add it straight in
	cmp	qword [r12+xmlmemnode_value_ofs], 0
	je	.textnode_ret
	mov	rdi, [r12+xmlmemnode_value_ofs]
	call	xmltag$escape_string
	push	rax
	mov	rdi, rbx
	mov	rsi, rax
	call	buffer$append_rawstring
	pop	rdi
	call	heap$free
.textnode_ret:
	pop	r13 r12
	ret
.cdata:
	mov	rdi, rbx
	mov	rsi, .cdatahead
	call	buffer$append_rawstring
	cmp	qword [r12+xmlmemnode_value_ofs], 0
	je	.cdata_novalue
	mov	rdi, rbx
	mov	rsi, [r12+xmlmemnode_value_ofs]
	call	buffer$append_rawstring
.cdata_novalue:
	mov	rdi, rbx
	mov	rsi, .cdatatail
	call	buffer$append_rawstring
	pop	r13 r12
	ret
cleartext .cdatahead, '
cleartext .cdatatail, ']]>'
.comment:
	mov	rdi, rbx
	mov	rsi, .commenthead
	call	buffer$append_rawstring
	cmp	qword [r12+xmlmemnode_value_ofs], 0
	je	.comment_novalue
	mov	rdi, rbx
	mov	rsi, [r12+xmlmemnode_value_ofs]
	call	buffer$append_rawstring
.comment_novalue:
	mov	rdi, rbx
	mov	rsi, .commenttail
	call	buffer$append_rawstring
	pop	r13 r12
	ret
cleartext .commenthead, ''
.doctype:
	mov	rdi, rbx
	mov	rsi, .doctypehead
	call	buffer$append_rawstring
	cmp	qword [r12+xmlmemnode_value_ofs], 0
	je	.doctype_novalue
	mov	rdi, rbx
	mov	rsi, [r12+xmlmemnode_value_ofs]
	call	buffer$append_rawstring
.doctype_novalue:
	mov	rdi, rbx
	mov	rsi, .doctypetail
	call	buffer$append_rawstring
	pop	r13 r12
	ret
cleartext .doctypehead, '
cleartext .doctypetail, '>'
.pi:
	mov	rdi, rbx
	mov	rsi, .pihead
	call	buffer$append_rawstring
	mov	rdi, rbx
	mov	rsi, [r12+xmlmemnode_name_ofs]
	call	buffer$append_rawstring
	cmp	qword [r12+xmlmemnode_value_ofs], 0
	je	.pi_novalue
	mov	rdi, rbx
	mov	esi, ' '
if string_bits = 32
	call	buffer$append_dword
else
	call	buffer$append_word
end if
	mov	rdi, rbx
	mov	rsi, [r12+xmlmemnode_value_ofs]
	call	buffer$append_rawstring
.pi_novalue:
	mov	rdi, rbx
	mov	rsi, .pitail
	call	buffer$append_rawstring
	pop	r13 r12
	ret
cleartext .pihead, '
cleartext .pitail, '?>'

end if


if used xmlmemnode$foreach_processinginstruction | defined include_everything
	; two arguments: rdi == xmlmemnode object, rsi == function to call
	; function in rsi will get called with rdi == xmlmemnode for every xmlmemnode_type_pi node
falign
xmlmemnode$foreach_processinginstruction:
	prolog	xmlmemnode$foreach_processinginstruction
	xor	eax, eax
	test	dword [rdi+xmlmemnode_flags_ofs], xmlmemnode_type_element or xmlmemnode_type_document or xmlmemnode_type_document_fragment
	jz	.ret
	; otherwise, search by recursive descent
	mov	rdi, [rdi+xmlmemnode_children_ofs]
	mov	rdx, rsi
	mov	rsi, .descend
	test	rdi, rdi
	jz	.ret
	call	vector$foreach_arg
.ret:
	epilog
falign
.descend:
	; rdi == xmlmemnode (child), rsi == function to call
	test	dword [rdi+xmlmemnode_flags_ofs], xmlmemnode_type_pi
	jnz	.descend_call
	; otherwise, if it is not an element or document fragment do nothing
	test	dword [rdi+xmlmemnode_flags_ofs], xmlmemnode_type_element or xmlmemnode_type_document or xmlmemnode_type_document_fragment
	jz	.descend_nothingtodo
	; otherwise, descend
	mov	rdi, [rdi+xmlmemnode_children_ofs]
	mov	rdx, rsi
	mov	rsi, .descend
	test	rdi, rdi
	jz	.descend_nothingtodo
	call	vector$foreach_arg
	; done, return
.descend_nothingtodo:
	ret
calign
.descend_call:
	call	rsi
	; we know that our node that was in rdi was a PI and thus has no children
	; return:
	ret

end if


if used xmlmemnode$foreach_element | defined include_everything
	; two arguments: rdi == xmlmemnode object, rsi == function to call
	; function in rsi will get called with rdi == xmlmemnode for every xmlmemnode_type_element node
falign
xmlmemnode$foreach_element:
	prolog	xmlmemnode$foreach_element
	xor	eax, eax
	test	dword [rdi+xmlmemnode_flags_ofs], xmlmemnode_type_element or xmlmemnode_type_document or xmlmemnode_type_document_fragment
	jz	.ret
	; otherwise, search by recursive descent
	mov	rdi, [rdi+xmlmemnode_children_ofs]
	mov	rdx, rsi
	mov	rsi, .descend
	test	rdi, rdi
	jz	.ret
	call	vector$foreach_arg
.ret:
	epilog
falign
.descend:
	; rdi == xmlmemnode (child), rsi == function to call
	test	dword [rdi+xmlmemnode_flags_ofs], xmlmemnode_type_element
	jnz	.descend_call
	; otherwise, if it is not an element or document fragment do nothing
	test	dword [rdi+xmlmemnode_flags_ofs], xmlmemnode_type_element or xmlmemnode_type_document or xmlmemnode_type_document_fragment
	jz	.descend_nothingtodo
	; otherwise, descend
.descend_deeper:
	mov	rdi, [rdi+xmlmemnode_children_ofs]
	mov	rdx, rsi
	mov	rsi, .descend
	test	rdi, rdi
	jz	.descend_nothingtodo
	call	vector$foreach_arg
	; done, return
.descend_nothingtodo:
	ret
calign
.descend_call:
	push	rdi rsi
	call	rsi
	pop	rsi rdi
	jmp	.descend_deeper

end if


if used xmlmemnode$foreach_bytagname | defined include_everything
	; three arguments: rdi == xmlmemnode object, rsi == function to call, rdx == tagname
	; function in rsi will get called with rdi == xmlmemnode for every element whose tagname matches
	; NOTE: this function ignores namespaces, see foreach_byqname for ns matching
falign
xmlmemnode$foreach_bytagname:
	prolog	xmlmemnode$foreach_bytagname
	xor	eax, eax
	test	dword [rdi+xmlmemnode_flags_ofs], xmlmemnode_type_element or xmlmemnode_type_document or xmlmemnode_type_document_fragment
	jz	.ret
	; otherwise, search by recursive descent
	; NOTE: we cheat here and use a register that doesn't get hammered by vector$foreach_arg:
	; r15: (because we need to additionally store our tagname)
	push	r15
	mov	r15, rdx
	mov	rdi, [rdi+xmlmemnode_children_ofs]
	mov	rdx, rsi
	mov	rsi, .descend
	test	rdi, rdi
	jz	.skip
	call	vector$foreach_arg
.skip:
	pop	r15
.ret:
	epilog
falign
.descend:
	; rdi == xmlmemnode (child), rsi == function to call, r15 is our tagname to compare
	test	dword [rdi+xmlmemnode_flags_ofs], xmlmemnode_type_element
	jnz	.descend_doit
	; otherwise, if it is not an element or document fragment, do nothing
	test	dword [rdi+xmlmemnode_flags_ofs], xmlmemnode_type_element or xmlmemnode_type_document or xmlmemnode_type_document_fragment
	jz	.descend_nothingtodo
	; otherwise, descend:
.descend_deeper:
	mov	rdi, [rdi+xmlmemnode_children_ofs]
	mov	rdx, rsi
	mov	rsi, .descend
	test	rdi, rdi
	jz	.descend_nothingtodo
	call	vector$foreach_arg
	; done, return
.descend_nothingtodo:
	ret
calign
.descend_doit:
	; function in rsi to be called only if the tagname matches that in r15
	cmp	qword [rdi+xmlmemnode_name_ofs], 0
	je	.descend_nothingtodo
	push	rdi rsi
	mov	rdi, [rdi+xmlmemnode_name_ofs]
	mov	rsi, r15
	call	string$equals
	test	eax, eax
	jz	.descend_doit_nocall
	mov	rdi, [rsp+8]
	mov	rsi, [rsp]
	call	rsi
.descend_doit_nocall:
	pop	rsi rdi
	; go ahead and descend:
	jmp	.descend_deeper


end if


if used xmlmemnode$foreach_byqname | defined include_everything
	; four arguments: rdi == xmlmemnode object, rsi == function to call, rdx == tagname, rcx == namespace URI
	; function in rsi will get called with rdi == xmlmemnode for every element whose tagname and namespace matches
	; NOTE: namespace URI can be null, otherwise we do full namespace matching
falign
xmlmemnode$foreach_byqname:
	prolog	xmlmemnode$foreach_byqname
	xor	eax, eax
	test	dword [rdi+xmlmemnode_flags_ofs], xmlmemnode_type_element or xmlmemnode_type_document or xmlmemnode_type_document_fragment
	jz	.ret
	; otherwise, search by recursive descent
	; NOTE: we cheat here and use a register that doesn't get hammered by vector$foreach_arg
	; r15: (because we need to additionally store our tagname and namespace)
	push	r15
	sub	rsp, 16
	mov	r15, rsp
	mov	[rsp], rdx
	mov	[rsp+8], rcx
	mov	rdi, [rdi+xmlmemnode_children_ofs]
	mov	rdx, rsi
	mov	rsi, .descend
	test	rdi, rdi
	jz	.skip
	call	vector$foreach_arg
.skip:
	add	rsp, 16
	pop	r15
.ret:
	epilog
falign
.descend:
	; rdi == xmlmemnode (child), rsi == function to call, r15 == pointer to rsp with our tagname and namespace URI
	test	dword [rdi+xmlmemnode_flags_ofs], xmlmemnode_type_element
	jnz	.descend_doit
	; otherwise, if it is not an element or document fragment, do nothing
	test	dword [rdi+xmlmemnode_flags_ofs], xmlmemnode_type_element or xmlmemnode_type_document or xmlmemnode_type_document_fragment
	jz	.descend_nothingtodo
	; otherwise, descend
.descend_deeper:
	mov	rdi, [rdi+xmlmemnode_children_ofs]
	mov	rdx, rsi
	mov	rsi, .descend
	test	rdi, rdi
	jz	.descend_nothingtodo
	call	vector$foreach_arg
	; done, return
.descend_nothingtodo:
	ret
calign
.descend_doit:
	; function in rsi to be called only if the tagname matches and the NS URI matches
	cmp	qword [rdi+xmlmemnode_name_ofs], 0
	je	.descend_nothingtodo
	cmp	qword [rdi+xmlmemnode_ns_ofs], 0
	je	.descend_nothingtodo
	push	rdi rsi
	mov	rdi, [rdi+xmlmemnode_name_ofs]
	mov	rsi, [r15]
	call	string$equals
	test	eax, eax
	jz	.descend_doit_nocall
	mov	rdi, [rsp+8]
	mov	rsi, [r15+8]
	mov	rdi, [rdi+xmlmemnode_ns_ofs]
	mov	rdi, [rdi+xmlmemns_uri_ofs]
	call	string$equals
	test	eax, eax
	jz	.descend_doit_nocall
	mov	rdi, [rsp+8]
	mov	rsi, [rsp]
	call	rsi
.descend_doit_nocall:
	pop	rsi rdi
	; go ahead and descend:
	jmp	.descend_deeper

end if


if used xmlmemnode$element_byid | defined include_everything
	; two arguments: rdi == xmlmemnode object, rsi == "id" attribute to match
	; returns an xmlmemnode_type_element node in rax or null if not found
	; (returns the first one that matches if duplicate ids exist)
	; NOTE: this searches for elements with an unbound namespace like:
	;  where "xxx" is passed to this function
	; (this does deep searching below the node passed in rdi)
falign
xmlmemnode$element_byid:
	prolog	xmlmemnode$element_byid
	xor	eax, eax
	test	dword [rdi+xmlmemnode_flags_ofs], xmlmemnode_type_element or xmlmemnode_type_document or xmlmemnode_type_document_fragment
	jz	.ret
	cmp	qword [rdi+xmlmemnode_children_ofs], 0
	je	.ret
	call	.descend_and_check
.ret:
	epilog
cleartext .idstr, 'id'
falign
.descend_and_check:
	; rdi == xmlmemnode, rsi == attribute name
	xor	eax, eax
	test	dword [rdi+xmlmemnode_flags_ofs], xmlmemnode_type_element or xmlmemnode_type_document or xmlmemnode_type_document_fragment
	jz	.skipit
	push	rdi rsi
	mov	rsi, .idstr
	call	xmlmemnode$attribute_byname
	pop	rsi rdi
	test	rax, rax
	jnz	.check_attribute_value
.process_children:
	; otherwise, for each one of our children, descend
	cmp	qword [rdi+xmlmemnode_children_ofs], 0
	je	.skipit
	push	rbx r12
	mov	rbx, [rdi+xmlmemnode_children_ofs]
	mov	r12, [rbx+vector_count_ofs]
	mov	rbx, [rbx+vector_array_ofs]
	test	r12, r12
	jz	.norecurse
calign
.loop:
	mov	rdi, [rbx]
	add	rbx, 8
	; rsi still valid and pointing to our id search value
	call	.descend_and_check
	test	rax, rax
	jnz	.norecurse
	sub	r12, 1
	jnz	.loop
.norecurse:
	pop	r12 rbx
.skipit:
	ret
.check_attribute_value:
	; so xmlmemnode$attribute_byname returned us an xmlmemnode_type_attribute for the id
	; check its value for a match in rsi
	push	rdi rsi
	mov	rdi, [rax+xmlmemnode_value_ofs]
	call	string$equals
	pop	rsi rdi
	test	eax, eax
	jz	.process_children
	mov	rax, rdi
	ret

end if


if used xmlmemnode$attribute_byname | defined include_everything
	; two arguments: rdi == xmlmemnode object, rsi == attribute name
	; returns an xmlmemnode_type_attribute node in rax or null if not found
	; NOTE: this function ignores namespaces, see attribute_byqname for ns matching
falign
xmlmemnode$attribute_byname:
	prolog	xmlmemnode$attribute_byname
	xor	eax, eax
	test	dword [rdi+xmlmemnode_flags_ofs], xmlmemnode_type_element
	jz	.ret
	mov	rcx, [rdi+xmlmemnode_attributes_ofs]
	test	rcx, rcx
	jz	.ret
	cmp	qword [rcx+vector_count_ofs], 0
	je	.ret
	; otherwise, we can walk them:
	push	rbx r12 r13
	mov	rbx, [rcx+vector_array_ofs]
	mov	r12, [rcx+vector_count_ofs]
	mov	r13, rsi
calign
.search:
	mov	rdi, [rbx]
	mov	rdi, [rdi+xmlmemnode_name_ofs]
	mov	rsi, r13
	call	string$equals
	test	eax, eax
	jnz	.found
	add	rbx, 8
	sub	r12d, 1
	jnz	.search
	xor	eax, eax
	pop	r13 r12 rbx
.ret:
	epilog
calign
.found:
	mov	rax, [rbx]
	pop	r13 r12 rbx
	epilog

end if


if used xmlmemnode$attribute_byqname | defined include_everything
	; three arguments: rdi == xmlmemnode object, rsi == attribute name, rdx == namespace URI
	; returns an xmlmemnode_type_attribute node in rax or null if not found
	; NOTE: namespace URI can be null, otherwise we do full namespace matching
falign
xmlmemnode$attribute_byqname:
	prolog	xmlmemnode$attribute_byqname
	xor	eax, eax
	test	dword [rdi+xmlmemnode_flags_ofs], xmlmemnode_type_element
	jz	.ret
	mov	rcx, [rdi+xmlmemnode_attributes_ofs]
	test	rcx, rcx
	jz	.ret
	cmp	qword [rcx+vector_count_ofs], 0
	je	.ret
	; otherwise, we can walk them:
	push	rbx r12 r13 r14
	mov	rbx, [rcx+vector_array_ofs]
	mov	r12, [rcx+vector_count_ofs]
	mov	r13, rsi
	mov	r14, rdx
calign
.search:
	mov	rdi, [rbx]
	cmp	qword [rdi+xmlmemnode_ns_ofs], 0
	je	.search_nocheck
	mov	rdi, [rdi+xmlmemnode_name_ofs]
	mov	rsi, r13
	call	string$equals
	test	eax, eax
	jnz	.maybefound
.search_nocheck:
	add	rbx, 8
	sub	r12d, 1
	jnz	.search
	xor	eax, eax
	pop	r14 r13 r12 rbx
.ret:
	epilog
calign
.maybefound:
	mov	rdi, [rbx]
	mov	rdi, [rdi+xmlmemnode_ns_ofs]
	mov	rsi, r14
	mov	rdi, [rdi+xmlmemns_uri_ofs]
	call	string$equals
	test	eax, eax
	jz	.search_nocheck
	mov	rax, [rbx]
	pop	r14 r13 r12 rbx
	epilog

end if


if used xmlmemnode$property_byname | defined include_everything
	; two arguments: rdi == xmlmemnode object, rsi == property name
	; returns an xmlmemnode_type_text node in rax or null if not found
	; NOTE: this function ignores namespaces, see property_byqname for ns matching
	; this searches child elements for element tag names (the "property" name)
	; and if a child element has an only child and its only child is text, that text node
	; is returned
falign
xmlmemnode$property_byname:
	prolog	xmlmemnode$property_byname
	xor	eax, eax
	test	dword [rdi+xmlmemnode_flags_ofs], xmlmemnode_type_element
	jz	.ret
	mov	rcx, [rdi+xmlmemnode_children_ofs]
	test	rcx, rcx
	jz	.ret
	cmp	qword [rcx+vector_count_ofs], 0
	je	.ret
	; otherwise, we can walk them:
	push	rbx r12 r13
	mov	rbx, [rcx+vector_array_ofs]
	mov	r12, [rcx+vector_count_ofs]
	mov	r13, rsi
calign
.search:
	mov	rdi, [rbx]
	test	dword [rdi+xmlmemnode_flags_ofs], xmlmemnode_type_element
	jz	.search_next
	; see if its name matches the one we are looking for:
	mov	rsi, r13
	mov	rdi, [rdi+xmlmemnode_name_ofs]
	call	string$equals
	test	eax, eax
	jnz	.found
.search_next:
	add	rbx, 8
	sub	r12, 1
	jnz	.search
	xor	eax, eax
	pop	r13 r12 rbx
.ret:
	epilog
calign
.found:
	; [rbx] is a name-matched xmlmemnode_type_element, make sure it has
	; an only child and that its only child is text, or no deal
	mov	rdi, [rbx]
	cmp	qword [rdi+xmlmemnode_children_ofs], 0
	je	.search_next
	mov	rdi, [rdi+xmlmemnode_children_ofs]
	cmp	qword [rdi+vector_count_ofs], 1
	jne	.search_next
	mov	rsi, [rdi+vector_array_ofs]
	mov	rax, [rsi]
	; make sure its type is text:
	test	dword [rax+xmlmemnode_flags_ofs], xmlmemnode_type_text
	jz	.search_next
	; otherwise, all good
	pop	r13 r12 rbx
	epilog

end if


if used xmlmemnode$property_byqname | defined include_everything
	; three arguments: rdi == xmlmemnode object, rsi == property name, rdx == namespace URI
	; returns an xmlmemnode_type_text node in rax or null if not found
	; NOTE: namespace URI can be null, otherwise we do full namespace matching
	; this searches child elements for element tag names (the "property" name)
	; and if a child element has an only child and its only child is text, that text node
	; is returned
falign
xmlmemnode$property_byqname:
	prolog	xmlmemnode$property_byqname
	xor	eax, eax
	test	dword [rdi+xmlmemnode_flags_ofs], xmlmemnode_type_element
	jz	.ret
	mov	rcx, [rdi+xmlmemnode_children_ofs]
	test	rcx, rcx
	jz	.ret
	cmp	qword [rcx+vector_count_ofs], 0
	je	.ret
	; otherwise, we can walk them:
	push	rbx r12 r13 r14
	mov	rbx, [rcx+vector_array_ofs]
	mov	r12, [rcx+vector_count_ofs]
	mov	r13, rsi
	mov	r14, rdx
calign
.search:
	mov	rdi, [rbx]
	test	dword [rdi+xmlmemnode_flags_ofs], xmlmemnode_type_element
	jz	.search_next
	; see if its name matches the one we are looking for:
	mov	rsi, r13
	mov	rdi, [rdi+xmlmemnode_name_ofs]
	call	string$equals
	test	eax, eax
	jnz	.found
.search_next:
	add	rbx, 8
	sub	r12, 1
	jnz	.search
	xor	eax, eax
	pop	r14 r13 r12 rbx
.ret:
	epilog
calign
.found:
	; [rbx] is a name-matched xmlmemnode_type_element, make sure it has
	; an only child and that its only child is text, or no deal
	mov	rdi, [rbx]
	mov	rsi, r14
	mov	rdi, [rdi+xmlmemnode_ns_ofs]
	test	rdi, rdi
	jz	.search_next
	mov	rdi, [rdi+xmlmemns_uri_ofs]
	call	string$equals
	test	eax, eax
	jz	.search_next
	mov	rdi, [rbx]
	cmp	qword [rdi+xmlmemnode_children_ofs], 0
	je	.search_next
	mov	rdi, [rdi+xmlmemnode_children_ofs]
	cmp	qword [rdi+vector_count_ofs], 1
	jne	.search_next
	mov	rsi, [rdi+vector_array_ofs]
	mov	rax, [rsi]
	; make sure its type is text:
	test	dword [rax+xmlmemnode_flags_ofs], xmlmemnode_type_text
	jz	.search_next
	; otherwise, all good
	pop	r14 r13 r12 rbx
	epilog

end if