Assigning to a named child counts its list of the matching children, and
assigning through an empty XMLList counts the chain of targets it climbs,
before either changes the tree. copy(), toXMLString(), normalize(), the
`..` selector and the depth check on insertion hold the stack they keep
for each open level of the tree. Inserting an XMLList, and assigning one
to an item of an XMLList, hold their copy of the list's items.
copy() reads attributes in place, and toXMLString() reads attributes and
namespace declarations in place, instead of copying the lists. copy()
charges the declarations it copies before it copies them. Deleting by
name or assigning an attribute compacts the remaining links in place
instead of building a second list.
toXMLString() holds the set of prefixes a namespace lookup passes, and
picks a new nsN prefix in one pass over the declarations in scope
instead of rescanning them for each candidate.
The parser holds its open elements and their names, the text it gathers,
each tag's attribute values, the sets that check declarations and
attribute names, and its one copy of an element's namespaces, and it
charges the strings of the namespace declarations it makes. The
allowance it holds from the source's length covers the copy a value
takes as it becomes a string.
Comparing XML values, converting them to strings, assigning an XMLList
to an attribute, and reading a name, a qualifier, a namespace URI or
embedded text from a string built by joining strings flatten it with
flat_charged. An attribute name read from a string reserves its copy,
and an element name shares the string.
Docs: docs/target-app-parity.md.
xml.xpath(expression[, variables]) evaluates an XPath 1.0 expression
(https://www.w3.org/TR/1999/REC-xpath-19991116/) with the XML value as
the context node and returns the selected nodes as an XMLList, in
document order and without repeats. On an XMLList the expression runs
on each element in turn, and the results are joined.
- All 13 axes and the 27 core functions. id() selects nothing, since
E4X trees carry no DTD data.
- Each property of `variables` binds the variable of its name. A
prefix in a name test resolves through the context element's
in-scope namespaces; `xml` is always bound.
- An expression that breaks the grammar gives a SyntaxError. An
unknown function, a wrong argument count, an unbound prefix or
variable, a result that is not a node-set, and a result that holds
the root or a namespace node give a TypeError.
- The parser and the evaluator keep their state on their own stacks,
so expressions of any depth use no native stack; nesting past
Options::max_depth gives "nesting too deep". Traversal stacks, node
lists, strings and the values that string functions build count
against the heap limit, and every node visited and string read takes
fuel.
Docs: docs/target-app-parity.md (E4X line), crate doc in lib.rs.
A for each loop read the property's value before evaluating its target,
so a target such as (o.p = 2, t).v, t[(o.p = 3, 'w')] or a bare
@[(o.p = 2, 'k')] stored the value from before the target ran. The
target's object and name are now evaluated first and the value is read
after them (ECMA-357 section 12.3).
With Options::e4x set, an E4X name standing alone (@a, @ns::a, @[expr],
ns::a, *) is now a valid target for =, compound assignment, ++, --,
delete and for/for each loops, as well as a read. The target is the
nearest XML or XMLList value in scope whose [[HasProperty]] accepts the
name (ECMA-357 section 11.1). The name is evaluated once, and the
target resolves before the right-hand side runs.
When no XML value in scope holds the name, a read or write throws a
ReferenceError and creates no child or attribute. A read no longer
falls back to the first XML value in scope, so a filter such as
list.(@id == '1') throws when an item has no @id and no outer XML
value holds one, instead of treating that item as not matching.
An XML declaration at the start of XML or XMLList text was skipped up to
the next "?>" without being read. It is now checked against XML 1.0's
grammar (https://www.w3.org/TR/xml/#NT-XMLDecl): a version of "1." and
digits, then an optional encoding name and an optional standalone "yes"
or "no", in that order, with optional white space before "?>". Any other
declaration gives "SyntaxError: invalid XML declaration", and a character
XML does not allow gives "invalid character in XML text". White space
before the declaration is still accepted.
The parser now takes fuel for the length of the declaration and the
white space before it, element and attribute names, white space in open
and closing tags, and the text it searched when an attribute value, CDATA
section, comment or processing instruction is not closed, or when "]]>"
or "<" stops a text run or attribute value. A long run in any of these
used the same fuel as a short one.
`inScopeNamespaces()`, `namespaceDeclarations()` and `namespace()` walk
from the node up to its root. The walk cloned each ancestor's whole node,
children and attributes included, to read its namespaces, and charged
fuel only for the namespaces it found, so a deep tree with no declared
namespaces cost no fuel. It now takes one unit of fuel per ancestor,
copies only the namespace list and parent link, and checks prefixes
already seen with a set. `namespaceDeclarations()` compares the node's
declarations with the ancestors' through a map instead of a scan.
The test for new XML with a one-item list now starts from an item that
has a parent, an attribute and a child. It checks that the copy has no
parent and that changing the copy's attribute and child leaves the
original unchanged, so a copy that drops or shares the children fails.
new XML(list) now returns a deep copy of the list's single item, as
ECMA-357 section 13.4.2 specifies for an XMLList argument; before, it
returned the item itself, so changes to the new value changed the list.
insertChildAfter and insertChildBefore take one unit of fuel for each
child they compare with the reference, as childIndex does.
The test for the missing xpath method now checks the XML and XMLList
prototypes; reading .xpath on a value selects child elements and
returned an empty list in every case.
addNamespace and setNamespace now add a declaration's bytes to the heap total when the element
gains a new prefix, and QName.prototype.toString adds the bytes of the string it returns. Before,
both only checked for room, so a loop of distinct prefixes could grow an element past the heap
limit until a collection recounted it.
- A list copy holds each new copy as a root while the list is charged
for it, so a collection at that point no longer frees the copy. Before,
`x.setChildren(<c/> + <d/>)` near the heap limit could leave one child
twice or end with an internal error.
- An assignment to a path such as `x.a.b = v` or `x.a.b[0] = v` removes
the elements it added for the path when a later step stops at a limit
or throws.
- Comparing XML values counts the attribute tables and the work stack
against the heap limit.
ECMA-357 sends a single attribute given to [[Insert]] through [[Replace]], which makes a text node. A list holding an attribute went into the children as the attribute itself, changed its parent and stayed on its old owner. Each attribute now goes in as a new text node with its value, and it stays on its owner. A list assigned such a value holds the new text nodes, and an attribute does not count against the depth limit.
An embedded expression used as a tag or attribute name must now be one XML
name, and one used as a run of attributes must hold only name="value"
attributes separated by white space. Anything else throws SyntaxError, so a
script string cannot add an attribute, close the tag or add a child.
{name}={value} in a tag now reads as a computed attribute name, as in the
example in ECMA-357 section 11.1.4.
Replacing a child with a list, inserting a list, and assigning or
replacing by name now check the depth and take all fuel and heap before
the first change. A limit no longer leaves the old child removed, part
of a list inserted or one of several matches gone.
appendChild and assignment past the end of a list remove their empty
placeholder when the assignment stops. A list that takes an earlier
sibling at an index keeps that node in its slot.
The XML parser now throws SyntaxError when markup binds the xml or xmlns prefix or either fixed URI against the Namespaces in XML rules. addNamespace, setName, setLocalName and setNamespace throw TypeError for the same bindings and leave the node unchanged.
A new element or attribute cannot be in the xmlns namespace, and an attribute in no namespace cannot be named xmlns. The xmlns URI cannot be the default XML namespace. These assignments throw TypeError.
insertChild, prependChild, replace and list assignment take their heap
and fuel before moving a node out of its old parent. Replace past the
last child charges before the push, so a child is never left without
its parent link. Deleting a child or attribute by name, assigning an
attribute or assigning text to an element take the fuel for the whole
step before emptying the node. normalize removes the next text node
before it stores the joined text.
With `Options::e4x` set, vectorcraft-js runs ECMA-357 (2nd edition):
XML and XMLList literals with embedded expressions, the `..`, `.@`,
`.*`, `.()` and `::` selectors, assignment and `delete` on selected
nodes, `for each`, `default xml namespace`, and the four objects with
their methods and settings. The option is off by default, and without
it the syntax and globals do not exist.
Parsing, serializing, comparing and copying trees use no native stack,
and each takes fuel and counts against the heap limit. Inserting a node
into itself or its descendants throws a TypeError before the tree
changes. A literal or XML string nested past `Options::max_depth` gives
a SyntaxError, and a tree built past that depth stops at the heap limit.
`XML.prototype.xpath` is not provided.