//! E4X values (ECMA-357): the internal methods of XML and XMLList, the XML parser and serializer, //! and the bytecode operations on them. //! //! Tree nodes are heap objects linked by object index. No walk uses native recursion: parsing, //! copying, comparing, normalizing and serializing keep explicit stacks whose depth is at most //! [`crate::Options::max_depth`], which every insertion enforces. Each visited node takes fuel, and //! each new node, name, reference or output unit is charged to the heap before it is made. use std::collections::{HashMap, HashSet}; use std::hash::Hash; use std::rc::Rc; use crate::LimitKind; use crate::builtins::Builder; use crate::chars::{is_id_part, is_id_start}; use crate::error::TOO_DEEP; use crate::heap::{ Binding, Class, Env, Key, OBJECT_BYTES, ObjId, Object, XML_LIST_BYTES, XML_NODE_BYTES, XML_NS_BYTES, XML_REF_BYTES, XmlFilterState, XmlKind, XmlListData, XmlName, XmlNode, XmlNs, key, string_bytes, }; use crate::interp::{Abrupt, ErrorType, HeldBytes, Interp, JsResult}; use crate::string_builtins::{UNITS_PER_FUEL, flat_charged, push_held, reserve_units}; use crate::value::{Str, Value, index_key}; fn parser_units(s: &str) -> Vec { s.encode_utf16().collect() } pub(crate) const STAR: &[u16] = &[0x2A]; /// The estimated bytes, for one attribute, of the copies of two attribute lists and the count of /// names and values that comparing two XML values builds. const ATTR_COUNT_BYTES: usize = 96; /// The estimated bytes of one frame of the work stack that comparing two XML values uses. const EQUALS_FRAME_BYTES: usize = 24; const XML_URI: &str = "http://www.w3.org/XML/1998/namespace"; const XMLNS_URI: &str = "http://www.w3.org/2000/xmlns/"; pub(crate) const RESERVED_NS_BINDING: &str = "a namespace declaration uses a reserved prefix or URI"; /// The hidden binding that holds a function's default XML namespace URI. const DEFAULT_NS_BINDING: &[u16] = &[0, 0x64, 0x78, 0x6E, 0x73]; const LT: u16 = b'<' as u16; const GT: u16 = b'>' as u16; const SLASH: u16 = b'/' as u16; const AMP: u16 = b'&' as u16; const EQ: u16 = b'=' as u16; const DQUOTE: u16 = b'"' as u16; const SQUOTE: u16 = b'\'' as u16; const COLON: u16 = b':' as u16; const QUESTION: u16 = b'?' as u16; const CR: u16 = 0x0D; const LF: u16 = 0x0A; const TAB: u16 = 0x09; const SPACE: u16 = 0x20; /// The values of the XML constructor's settings properties. #[derive(Clone, Copy)] pub(crate) struct Settings { pub(crate) ignore_comments: bool, pub(crate) ignore_pis: bool, pub(crate) ignore_whitespace: bool, pub(crate) pretty: bool, pub(crate) indent: u32, } /// A name an XML value is accessed with. `uri` is `None` for a name that matches any namespace. #[derive(Clone)] pub(crate) struct NameRef { pub(crate) uri: Option, pub(crate) local: Key, pub(crate) attribute: bool, } impl NameRef { pub(crate) fn is_star(&self) -> bool { *self.local == *STAR } fn matches(&self, name: &XmlName) -> bool { (self.is_star() || self.local == name.local) && self.uri.as_ref().is_none_or(|u| name.uri.as_ref() == Some(u)) } fn matches_child(&self, c: &XmlNode) -> bool { let element = c.name.as_ref().filter(|_| c.kind == XmlKind::Element); (self.is_star() || element.is_some_and(|n| n.local == self.local)) && self.uri.as_ref().is_none_or(|u| element.is_some_and(|n| n.uri.as_ref() == Some(u))) } fn to_name(&self) -> XmlName { XmlName { uri: self.uri.clone(), local: self.local.clone(), prefix: None } } } /// How an XML value is accessed: by position or by name. pub(crate) enum Access { Index(u32), Name(NameRef), } /// What a value is, for the operations that treat XML and XMLList differently. #[derive(Clone, Copy)] pub(crate) enum Xv { Node(ObjId), List(ObjId), Other, } /// A value to store in a tree. pub(crate) enum Content { Text(Str), Node(ObjId), List(ObjId), } pub(crate) fn is_xml_space(c: u16) -> bool { matches!(c, SPACE | TAB | CR | LF) } fn is_surrogate(c: u16) -> bool { (0xD800..=0xDFFF).contains(&c) } /// A unit a name may start with (the XMLName grammar with `:` for a prefix). pub(crate) fn is_name_start(c: u16) -> bool { c == COLON || is_surrogate(c) || (c != 0x24 && is_id_start(u32::from(c))) } /// A unit a name may hold after its first. pub(crate) fn is_name_part(c: u16) -> bool { is_name_start(c) || matches!(c, 0x2D | 0x2E | 0xB7) || (c != 0x24 && is_id_part(u32::from(c))) } /// The offset after the XML name that starts at `src[pos]`, when one does. pub(crate) fn scan_name(src: &[u16], pos: usize) -> Option { if !src.get(pos).copied().is_some_and(is_name_start) { return None; } let mut end = pos + 1; while src.get(end).copied().is_some_and(is_name_part) { end += 1; } valid_xml_units(src.get(pos..end).unwrap_or_default()).then_some(end) } fn skip_xml_space(src: &[u16], mut pos: usize) -> usize { while src.get(pos).copied().is_some_and(is_xml_space) { pos += 1; } pos } /// Whether `units` holds only `name="value"` attributes separated by white space. fn is_attribute_run(units: &[u16]) -> bool { let mut at = skip_xml_space(units, 0); while at < units.len() { let Some(name_end) = scan_name(units, at) else { return false }; let eq = skip_xml_space(units, name_end); if units.get(eq) != Some(&EQ) { return false; } let open = skip_xml_space(units, eq + 1); let quote = match units.get(open).copied() { Some(q @ (DQUOTE | SQUOTE)) => q, _ => return false, }; let value = units.get(open + 1..).unwrap_or_default(); let Some(len) = value.iter().position(|&c| c == quote || c == LT) else { return false }; if value.get(len) != Some("e) { return false; } let end = open + len + 2; at = skip_xml_space(units, end); if at == end && at < units.len() { return false; } } true } /// Reads one pseudo-attribute of an XML declaration at `at`: white space, `name`, `=` with /// optional white space around it, and a quoted value. Returns the offset after the closing /// quote and the value. fn decl_attribute<'a>(body: &'a [u16], at: usize, name: &str) -> Option<(usize, &'a [u16])> { let start = skip_xml_space(body, at); let name = parser_units(name); if start == at || !body.get(start..)?.starts_with(&name) { return None; } let eq = skip_xml_space(body, start + name.len()); if body.get(eq) != Some(&EQ) { return None; } let open = skip_xml_space(body, eq + 1); let quote = body.get(open).copied().filter(|&q| q == DQUOTE || q == SQUOTE)?; let len = body.get(open + 1..)?.iter().position(|&c| c == quote)?; Some((open + len + 2, body.get(open + 1..open + 1 + len)?)) } /// Whether `body`, the text between ``, is an XML declaration's version, optional /// encoding, optional standalone flag and optional trailing white space (XML 1.0 productions 23 /// to 26, 32, 80 and 81). fn is_xml_decl_body(body: &[u16]) -> bool { let is_digit = |c: &u16| (u16::from(b'0')..=u16::from(b'9')).contains(c); let is_letter = |c: &u16| (u16::from(b'A')..=u16::from(b'Z')).contains(c) || (u16::from(b'a')..=u16::from(b'z')).contains(c); let Some((mut at, version)) = decl_attribute(body, 0, "version") else { return false }; let digits = version.strip_prefix(parser_units("1.").as_slice()).unwrap_or_default(); if digits.is_empty() || !digits.iter().all(is_digit) { return false; } if let Some((next, encoding)) = decl_attribute(body, at, "encoding") { let Some((first, rest)) = encoding.split_first() else { return false }; if !is_letter(first) || !rest.iter().all(|c| is_letter(c) || is_digit(c) || matches!(*c, 0x2D | 0x2E | 0x5F)) { return false; } at = next; } if let Some((next, standalone)) = decl_attribute(body, at, "standalone") { if standalone != parser_units("yes").as_slice() && standalone != parser_units("no").as_slice() { return false; } at = next; } skip_xml_space(body, at) == body.len() } /// An XML name without a prefix (ECMA-357 section 13.1.2.1). pub(crate) fn is_ncname(units: &[u16]) -> bool { let Some((&first, rest)) = units.split_first() else { return false }; first != COLON && is_name_start(first) && rest.iter().all(|&c| c != COLON && is_name_part(c)) && valid_xml_units(units) } /// Whether every unit is an XML character, with each surrogate part of a pair. pub(crate) fn valid_xml_units(units: &[u16]) -> bool { let mut at = 0; while let Some(&c) = units.get(at) { if starts_pair(c, units.get(at + 1).copied()) { at += 2; } else if is_xml_char(u32::from(c)) { at += 1; } else { return false; } } true } fn starts_pair(c: u16, next: Option) -> bool { (0xD800..=0xDBFF).contains(&c) && next.is_some_and(|n| (0xDC00..=0xDFFF).contains(&n)) } /// The names the global `isXMLName` accepts: JavaScript identifiers without `$`, with `-` allowed /// after the first unit. pub(crate) fn is_script_xml_name(units: &[u16]) -> bool { let Some((&first, rest)) = units.split_first() else { return false }; first != 0x24 && is_id_start(u32::from(first)) && rest.iter().all(|&c| c == 0x2D || (c != 0x24 && is_id_part(u32::from(c)))) } fn is_xml_char(c: u32) -> bool { matches!(c, 0x09 | 0x0A | 0x0D | 0x20..=0xD7FF | 0xE000..=0xFFFD | 0x10000..=0x10FFFF) } /// Reads the reference starting at `src[pos]` (`&`): the offset after it and the character. pub(crate) fn scan_reference(src: &[u16], pos: usize) -> Option<(usize, u32)> { let rest = src.get(pos + 1..)?; let semi = rest.iter().take(64).position(|&c| c == 0x3B)?; let body = String::from_utf16(rest.get(..semi)?).ok()?; let c = match body.as_str() { "amp" => 0x26, "lt" => 0x3C, "gt" => 0x3E, "apos" => 0x27, "quot" => 0x22, _ => { let digits = body.strip_prefix('#')?; let (radix, digits) = digits.strip_prefix('x').map_or((10, digits), |d| (16, d)); if digits.is_empty() || digits.starts_with(['+', '-']) { return None; } let c = u32::from_str_radix(digits, radix).ok()?; if !is_xml_char(c) { return None; } c } }; Some((pos + 1 + semi + 1, c)) } fn push_char(out: &mut Vec, c: u32) { if let Some(ch) = char::from_u32(c) { let mut buf = [0u16; 2]; out.extend_from_slice(ch.encode_utf16(&mut buf)); } } fn name_bytes(n: &XmlName) -> usize { string_bytes(n.local.len()) + n.uri.as_ref().map_or(0, |u| string_bytes(u.len())) + n.prefix.as_ref().map_or(0, |p| string_bytes(p.len())) } /// Inserts `item` into `set`, first holding an upper estimate of the bytes of the set's new table /// when it must grow. Returns whether `item` was new. fn insert_held(interp: &mut Interp, held: &mut HeldBytes, set: &mut HashSet, item: T) -> JsResult { if set.len() == set.capacity() && !set.contains(&item) { let more = set.capacity().max(4); held.add(interp, more.saturating_mul(size_of::() + 1).saturating_mul(16) / 7)?; set.reserve(more); } Ok(set.insert(item)) } /// Makes room in `list` for `more` items, first holding the bytes of its new capacity when it must /// grow. The capacity at least doubles, as a push's would. fn reserve_held(interp: &mut Interp, held: &mut HeldBytes, list: &mut Vec, more: usize) -> JsResult<()> { if list.capacity() - list.len() < more { let cap = list.len().saturating_add(more).max(list.capacity().saturating_mul(2)); held.add(interp, (cap - list.capacity()).saturating_mul(size_of::()))?; list.reserve_exact(cap - list.len()); } Ok(()) } /// The number `n` of a prefix written `ns` in decimal without leading zeros, as /// [`Interp::fresh_prefix`] makes them. fn prefix_number(prefix: &[u16]) -> Option { let digits = prefix.strip_prefix(&[u16::from(b'n'), u16::from(b's')])?; let (&first, _) = digits.split_first()?; if digits.len() > 9 || (first == u16::from(b'0') && digits.len() > 1) { return None; } digits.iter().try_fold(0usize, |n, &c| { let d = c.checked_sub(u16::from(b'0')).filter(|d| *d < 10)?; Some(n * 10 + usize::from(d)) }) } fn prefix_in_scope<'a>(stack: &'a [XmlNs], prefix: &[u16]) -> Option<&'a XmlNs> { stack.iter().rev().find(|ns| ns.prefix.as_deref() == Some(prefix)) } /// Whether Namespaces in XML allows declaring `prefix` for `uri`: `xml` only with its fixed URI, /// `xmlns` never, and neither fixed URI with another prefix or as the default namespace. pub(crate) fn ns_binding_allowed(prefix: &[u16], uri: &[u16]) -> bool { if units_are(prefix, "xmlns") { return false; } let is_xml_uri = units_are(uri, XML_URI); if units_are(prefix, "xml") { return is_xml_uri; } !is_xml_uri && !units_are(uri, XMLNS_URI) } /// Whether a node named `name` can be written as XML: no name is in the `xmlns` namespace, and /// no attribute in no namespace is named `xmlns`. pub(crate) fn xml_name_writable(name: &XmlName, attribute: bool) -> bool { let uri = name.uri.as_deref().unwrap_or_default(); !units_are(uri, XMLNS_URI) && !(attribute && uri.is_empty() && units_are(&name.local, "xmlns")) } fn units_are(units: &[u16], text: &str) -> bool { units.iter().copied().eq(text.encode_utf16()) } /// One open element while serializing. struct OutFrame { node: ObjId, next: usize, indent: u64, child_indent: u64, pretty_children: bool, ns_mark: usize, prefix: Key, local: Key, } impl Interp { // ---- kinds and accessors ---- pub(crate) fn classify(&self, v: &Value) -> Xv { if let Value::Object(o) = v { match self.heap.object(*o).map(|ob| &ob.class) { Some(Class::Xml(_)) => return Xv::Node(*o), Some(Class::XmlList(_)) => return Xv::List(*o), _ => {} } } Xv::Other } pub(crate) fn is_xml(&self, v: &Value) -> bool { !matches!(self.classify(v), Xv::Other) } /// A name made by E4X syntax, as the key of an access. fn is_reference_name(&self, v: &Value) -> bool { matches!(v, Value::Object(o) if matches!(self.heap.object(*o).map(|ob| &ob.class), Some(Class::QName { reference: true, .. }))) } /// The access goes through the E4X operations. pub(crate) fn is_xml_access(&self, base: &Value, k: &Value) -> bool { self.options.e4x && (self.is_xml(base) || self.is_reference_name(k)) } pub(crate) fn xml_node(&self, id: ObjId) -> JsResult<&XmlNode> { match self.heap.object(id).map(|o| &o.class) { Some(Class::Xml(n)) => Ok(n), _ => Err(Abrupt::Internal("not an XML value".into())), } } pub(crate) fn xml_node_mut(&mut self, id: ObjId) -> JsResult<&mut XmlNode> { match self.heap.object_mut(id).map(|o| &mut o.class) { Some(Class::Xml(n)) => Ok(n), _ => Err(Abrupt::Internal("not an XML value".into())), } } pub(crate) fn xml_list(&self, id: ObjId) -> JsResult<&XmlListData> { match self.heap.object(id).map(|o| &o.class) { Some(Class::XmlList(l)) => Ok(l), _ => Err(Abrupt::Internal("not an XMLList value".into())), } } pub(crate) fn xml_list_mut(&mut self, id: ObjId) -> JsResult<&mut XmlListData> { match self.heap.object_mut(id).map(|o| &mut o.class) { Some(Class::XmlList(l)) => Ok(l), _ => Err(Abrupt::Internal("not an XMLList value".into())), } } pub(crate) fn xml_kind(&self, id: ObjId) -> JsResult { Ok(self.xml_node(id)?.kind) } fn child_at(&self, x: ObjId, i: usize) -> JsResult> { Ok(self.xml_node(x)?.children.get(i).copied()) } fn children_len(&self, x: ObjId) -> JsResult { Ok(self.xml_node(x)?.children.len()) } pub(crate) fn list_at(&self, l: ObjId, i: usize) -> JsResult> { Ok(self.xml_list(l)?.items.get(i).copied()) } pub(crate) fn list_len(&self, l: ObjId) -> JsResult { Ok(self.xml_list(l)?.items.len()) } // ---- charging and allocation ---- pub(crate) fn xml_keep(&mut self, id: ObjId) { self.temp_roots.push(Value::Object(id)); } fn xml_take_fuel(&mut self, n: usize) -> JsResult<()> { let n = n as u64; if n == 0 { return Ok(()); } if self.fuel < n { self.fuel = 0; return Err(Abrupt::Limit(LimitKind::Fuel)); } self.fuel -= n - 1; self.use_fuel() } fn xml_text_fuel(&mut self, units: usize) -> JsResult<()> { self.xml_take_fuel(units / UNITS_PER_FUEL + 1) } fn xml_charge(&mut self, bytes: usize) -> JsResult<()> { self.reserve_heap(bytes)?; self.heap.bytes = self.heap.bytes.saturating_add(bytes); Ok(()) } /// Charges for the link that will attach the new node `node`, which is held as a root across /// any collection the charge triggers. fn xml_charge_link(&mut self, node: ObjId) -> JsResult<()> { self.temp_roots.push(Value::Object(node)); let charged = self.xml_charge(XML_REF_BYTES); self.temp_roots.pop(); charged } /// A new node. What `node` refers to must be reachable from the roots. pub(crate) fn new_xml_node(&mut self, node: XmlNode) -> JsResult { if let Some(name) = &node.name && matches!(node.kind, XmlKind::Element | XmlKind::Attribute) && !xml_name_writable(name, node.kind == XmlKind::Attribute) { return Err(self.type_error(RESERVED_NS_BINDING)); } let extra = XML_NODE_BYTES + node.name.as_ref().map_or(0, name_bytes) + string_bytes(node.value.len()); self.reserve_heap(OBJECT_BYTES + extra)?; self.heap.bytes = self.heap.bytes.saturating_add(extra); let proto = self.intrinsics.xml_proto; Ok(self.heap.alloc_object(Object::new(Class::Xml(Box::new(node)), Some(proto)))) } pub(crate) fn new_text_node(&mut self, value: Str, parent: Option) -> JsResult { self.new_xml_node(XmlNode { kind: XmlKind::Text, name: None, parent, value, attrs: Vec::new(), children: Vec::new(), ns: Vec::new() }) } /// A new list. Its target must be reachable from the roots. pub(crate) fn new_xml_list(&mut self, target: Option, target_name: Option<(XmlName, bool)>) -> JsResult { self.reserve_heap(OBJECT_BYTES + XML_LIST_BYTES)?; self.heap.bytes = self.heap.bytes.saturating_add(XML_LIST_BYTES); let proto = self.intrinsics.xml_list_proto; let data = XmlListData { items: Vec::new(), target, target_name }; Ok(self.heap.alloc_object(Object::new(Class::XmlList(Box::new(data)), Some(proto)))) } /// Adds `item` to the end of `list`. Both are held as roots across any collection the charge /// starts. pub(crate) fn list_push(&mut self, list: ObjId, item: ObjId) -> JsResult<()> { self.rooted(&[Value::Object(list), Value::Object(item)], |i| i.xml_charge(XML_REF_BYTES))?; self.xml_list_mut(list)?.items.push(item); Ok(()) } /// A new name object made by E4X syntax. fn new_reference_name(&mut self, name: XmlName, attribute: bool) -> JsResult { let bytes = name_bytes(&name); self.reserve_heap(OBJECT_BYTES + bytes)?; self.heap.bytes = self.heap.bytes.saturating_add(bytes); let proto = self.intrinsics.qname_proto; Ok(self.heap.alloc_object(Object::new(Class::QName { name: Box::new(name), attribute, reference: true }, Some(proto)))) } // ---- settings and the default namespace ---- pub(crate) fn xml_settings(&mut self) -> JsResult { let c = self.intrinsics.xml_ctor; let this = Value::Object(c); let read = |i: &mut Self, name: &str| i.get_from(c, &key(name), &this); let ignore_comments = read(self, "ignoreComments")?.truthy(); let ignore_pis = read(self, "ignoreProcessingInstructions")?.truthy(); let ignore_whitespace = read(self, "ignoreWhitespace")?.truthy(); let pretty = read(self, "prettyPrinting")?.truthy(); let v = read(self, "prettyIndent")?; let n = self.to_number(&v)?; let indent = if n.is_finite() && n > 0.0 { n.min(f64::from(u32::MAX)) as u32 } else { 0 }; Ok(Settings { ignore_comments, ignore_pis, ignore_whitespace, pretty, indent }) } /// The URI of the running code's default XML namespace (ECMA-357 section 12.1.1). pub(crate) fn xml_default_uri(&self) -> JsResult { let mut cur = Some(self.frame()?.env); while let Some(e) = cur { match self.heap.env(e) { Some(Env::Decl { bindings, outer }) => { if let Some(Binding { value: Value::String(s), .. }) = bindings.get(DEFAULT_NS_BINDING) { return Ok(s.flat()); } cur = *outer; } Some(Env::Obj { outer, .. }) => cur = *outer, None => return Err(Abrupt::Internal("environment out of range".into())), } } Ok(self.xml_default_uri.clone()) } /// Sets the default XML namespace of the running function, or of the global code. pub(crate) fn xml_set_default_uri(&mut self, uri: Key) -> JsResult<()> { if units_are(&uri, XMLNS_URI) { return Err(self.type_error(RESERVED_NS_BINDING)); } let env = self.frame()?.var_env; if let Some(Env::Decl { bindings, .. }) = self.heap.env_mut(env) { let value = Value::String(Str::from_rc(uri)); bindings.insert(Rc::from(DEFAULT_NS_BINDING), Binding { value, mutable: true, deletable: false }); } else { self.xml_default_uri = uri; } Ok(()) } // ---- names ---- /// The name `s` gives: an attribute name after `@`, which is copied after its units are /// reserved, and otherwise an element name that shares `s`. fn name_from_string(&mut self, s: &Key) -> JsResult { if let Some((&0x40, rest)) = s.split_first() { let uri = if rest == STAR { None } else { Some(Rc::from(Vec::new())) }; reserve_units(self, rest.len())?; return Ok(NameRef { uri, local: Rc::from(rest), attribute: true }); } let uri = if **s == *STAR { None } else { Some(self.xml_default_uri()?) }; Ok(NameRef { uri, local: s.clone(), attribute: false }) } /// ToXMLName, with a canonical array index read as a position (ECMA-357 section 10.6). pub(crate) fn xml_access(&mut self, k: &Value) -> JsResult { match k { Value::Number(n) if *n >= 0.0 && n.fract() == 0.0 && *n < 4_294_967_295.0 => Ok(Access::Index(*n as u32)), Value::String(s) => { let flat = flat_charged(self, s)?; if let Some(i) = canonical_index(&flat) { return Ok(Access::Index(i)); } Ok(Access::Name(self.name_from_string(&flat)?)) } Value::Object(o) => { if let Some(Class::QName { name, attribute, .. }) = self.heap.object(*o).map(|ob| &ob.class) { return Ok(Access::Name(NameRef { uri: name.uri.clone(), local: name.local.clone(), attribute: *attribute })); } let s = self.to_str(k)?; let s = flat_charged(self, &s)?; if let Some(i) = canonical_index(&s) { return Ok(Access::Index(i)); } Ok(Access::Name(self.name_from_string(&s)?)) } _ => Err(self.type_error("the value is not an XML name")), } } /// The access a bracket, `in` or `delete` key selects. A key that is not a string or object /// converts with ToString first, as a property accessor does (ECMA-357 section 11.2.1). fn key_access(&mut self, k: &Value) -> JsResult { match k { Value::Undefined | Value::Null | Value::Bool(_) | Value::Number(_) => { let s = self.to_str(k)?; self.xml_access(&Value::String(s)) } _ => self.xml_access(k), } } fn xml_name_only(&mut self, k: &Value) -> JsResult { match self.xml_access(k)? { Access::Name(n) => Ok(n), Access::Index(i) => self.name_from_string(&index_key(i)), } } pub(crate) fn xml_method_key(&mut self, k: &Value) -> JsResult { if let Value::Object(o) = k && let Some(Class::QName { name, .. }) = self.heap.object(*o).map(|ob| &ob.class) { return Ok(name.local.clone()); } self.to_key(k) } // ---- [[Get]], [[HasProperty]], [[Delete]] ---- fn not_xml(&mut self) -> Abrupt { self.type_error("the XML operation needs an XML or XMLList value") } fn collect_matching(&mut self, x: ObjId, n: &NameRef, out: ObjId) -> JsResult<()> { let count = if n.attribute { self.xml_node(x)?.attrs.len() } else { self.children_len(x)? }; for i in 0..count { self.use_fuel()?; let node = self.xml_node(x)?; let found = if n.attribute { node.attrs.get(i).copied().filter(|&a| self.xml_node(a).is_ok_and(|an| an.name.as_ref().is_some_and(|q| n.matches(q)))) } else { node.children.get(i).copied().filter(|&c| self.xml_node(c).is_ok_and(|cn| n.matches_child(cn))) }; if let Some(found) = found { self.list_push(out, found)?; } } Ok(()) } /// The items of `base` matching `n`, with `base` and `n` as the list's write target. pub(crate) fn xml_select(&mut self, base: ObjId, n: &NameRef) -> JsResult { self.rooted(&[Value::Object(base)], |i| { let out = i.new_xml_list(Some(base), Some((n.to_name(), n.attribute)))?; i.xml_keep(out); match i.classify(&Value::Object(base)) { Xv::Node(x) => i.collect_matching(x, n, out)?, Xv::List(l) => { for at in 0..i.list_len(l)? { i.use_fuel()?; let Some(item) = i.list_at(l, at)? else { break }; if i.xml_kind(item)? == XmlKind::Element { i.collect_matching(item, n, out)?; } } } Xv::Other => return Err(i.not_xml()), } Ok(out) }) } /// [[Get]] (ECMA-357 sections 9.1.1.1 and 9.2.1.1). pub(crate) fn xml_get(&mut self, base: &Value, k: &Value) -> JsResult { let xv = self.classify(base); if matches!(xv, Xv::Other) { return Err(self.not_xml()); } match self.key_access(k)? { Access::Index(i) => Ok(match xv { Xv::Node(_) if i == 0 => base.clone(), Xv::List(l) => self.list_at(l, i as usize)?.map_or(Value::Undefined, Value::Object), _ => Value::Undefined, }), Access::Name(n) => { let Value::Object(o) = base else { return Err(self.not_xml()) }; Ok(Value::Object(self.xml_select(*o, &n)?)) } } } fn node_has_name(&mut self, x: ObjId, n: &NameRef) -> JsResult { let (attrs, children) = { let node = self.xml_node(x)?; (node.attrs.len(), node.children.len()) }; self.xml_take_fuel((if n.attribute { attrs } else { children }) / UNITS_PER_FUEL + 1)?; let node = self.xml_node(x)?; if n.attribute { return Ok(node.attrs.iter().any(|&a| self.xml_node(a).is_ok_and(|an| an.name.as_ref().is_some_and(|q| n.matches(q))))); } Ok(node.children.iter().any(|&c| self.xml_node(c).is_ok_and(|cn| n.matches_child(cn)))) } /// [[HasProperty]] (ECMA-357 sections 9.1.1.6 and 9.2.1.5). pub(crate) fn xml_has(&mut self, base: &Value, k: &Value) -> JsResult { let xv = self.classify(base); match (xv, self.key_access(k)?) { (Xv::Node(_), Access::Index(i)) => Ok(i == 0), (Xv::Node(x), Access::Name(n)) => self.node_has_name(x, &n), (Xv::List(l), Access::Index(i)) => Ok((i as usize) < self.list_len(l)?), (Xv::List(l), Access::Name(n)) => self.list_has_name(l, &n), (Xv::Other, _) => Err(self.not_xml()), } } fn list_has_name(&mut self, l: ObjId, n: &NameRef) -> JsResult { for at in 0..self.list_len(l)? { self.use_fuel()?; let Some(item) = self.list_at(l, at)? else { break }; if self.xml_kind(item)? == XmlKind::Element && self.node_has_name(item, n)? { return Ok(true); } } Ok(false) } /// Whether a scope object has the variable `name`. pub(crate) fn scope_has(&mut self, object: ObjId, name: &[u16]) -> JsResult { if self.options.e4x && self.is_xml(&Value::Object(object)) { let k = Value::String(Str::from_units(name.to_vec())); return self.xml_has(&Value::Object(object), &k); } self.has_property(object, name) } fn node_delete_at(&mut self, x: ObjId, i: usize) -> JsResult<()> { let len = self.children_len(x)?; if i < len { self.xml_text_fuel(len)?; self.remove_child(x, i)?; } Ok(()) } /// Takes the child at `i` out of `x`. The caller has checked `i` and taken the fuel. fn remove_child(&mut self, x: ObjId, i: usize) -> JsResult<()> { let c = self.xml_node_mut(x)?.children.remove(i); self.xml_node_mut(c)?.parent = None; Ok(()) } /// Takes `gone`, children of `x` in document order, out of `x` in one pass. The caller has /// taken the fuel. fn remove_children(&mut self, x: ObjId, gone: &[ObjId]) -> JsResult<()> { let mut next = gone.iter().peekable(); self.xml_node_mut(x)?.children.retain(|c| { let hit = next.peek() == Some(&c); if hit { next.next(); } !hit }); for &c in gone { self.xml_node_mut(c)?.parent = None; } Ok(()) } fn node_delete_name(&mut self, x: ObjId, n: &NameRef) -> JsResult<()> { if n.attribute { let count = self.xml_node(x)?.attrs.len(); self.xml_take_fuel(count)?; let mut kept = 0; for k in 0..count { let Some(a) = self.xml_node(x)?.attrs.get(k).copied() else { break }; let hit = self.xml_node(a)?.name.as_ref().is_some_and(|q| n.matches(q)); if hit { self.xml_node_mut(a)?.parent = None; } else { self.xml_node_mut(x)?.attrs.swap(kept, k); kept += 1; } } self.xml_node_mut(x)?.attrs.truncate(kept); return Ok(()); } let count = self.children_len(x)?; self.xml_take_fuel(count)?; let mut kept = 0; for k in 0..count { let Some(c) = self.child_at(x, k)? else { break }; if n.matches_child(self.xml_node(c)?) { self.xml_node_mut(c)?.parent = None; } else { self.xml_node_mut(x)?.children.swap(kept, k); kept += 1; } } self.xml_node_mut(x)?.children.truncate(kept); Ok(()) } fn index_in_parent(&mut self, parent: ObjId, child: ObjId) -> JsResult> { let len = self.children_len(parent)?; self.xml_text_fuel(len)?; Ok(self.xml_node(parent)?.children.iter().position(|&c| c == child)) } /// [[Delete]] (ECMA-357 sections 9.1.1.3 and 9.2.1.3). pub(crate) fn xml_delete(&mut self, base: &Value, k: &Value) -> JsResult { let xv = self.classify(base); match (xv, self.key_access(k)?) { (Xv::Node(_), Access::Index(_)) => Err(self.type_error("an XML value's children cannot be deleted by position")), (Xv::Node(x), Access::Name(n)) => { self.node_delete_name(x, &n)?; Ok(true) } (Xv::List(l), Access::Index(i)) => { let Some(item) = self.list_at(l, i as usize)? else { return Ok(true) }; let node = self.xml_node(item)?; let (parent, kind) = (node.parent, node.kind); if let Some(parent) = parent { if kind == XmlKind::Attribute { let attrs = &mut self.xml_node_mut(parent)?.attrs; attrs.retain(|&a| a != item); self.xml_node_mut(item)?.parent = None; } else if let Some(q) = self.index_in_parent(parent, item)? { self.node_delete_at(parent, q)?; } } self.xml_list_mut(l)?.items.remove(i as usize); Ok(true) } (Xv::List(l), Access::Name(n)) => { for at in 0..self.list_len(l)? { self.use_fuel()?; let Some(item) = self.list_at(l, at)? else { break }; if self.xml_kind(item)? == XmlKind::Element { self.node_delete_name(item, &n)?; } } Ok(true) } (Xv::Other, _) => Err(self.not_xml()), } } /// The function a call `base.name(...)` runs and its `this` (ECMA-357 section 11.2.2.1). A /// call looks the name up among the methods, never among the children. pub(crate) fn xml_method(&mut self, base: &Value, name: &[u16]) -> JsResult<(Value, Value)> { let mut base = base.clone(); loop { let Value::Object(o) = base else { return Err(self.not_xml()) }; let f = self.get_from(o, name, &base)?; if !matches!(f, Value::Undefined) { return Ok((f, base)); } match self.classify(&base) { Xv::List(l) if self.list_len(l)? == 1 => { let Some(item) = self.list_at(l, 0)? else { return Ok((f, base)) }; base = Value::Object(item); } Xv::Node(x) if self.node_simple(x)? => { let s = self.node_to_str(x)?; let this = Value::String(s); let f = self.get_value(&this, name)?; return Ok((f, this)); } _ => return Ok((f, base)), } } } /// A method found through an XML value in scope, for a call of a bare name in a filter. pub(crate) fn xml_scope_method(&mut self, name: &[u16]) -> JsResult> { let mut cur = Some(self.frame()?.env); while let Some(e) = cur { match self.heap.env(e) { Some(Env::Decl { bindings, outer }) => { if bindings.contains(name) { return Ok(None); } cur = *outer; } Some(&Env::Obj { object, outer, .. }) => { if self.scope_has(object, name)? { return Ok(None); } let this = Value::Object(object); if self.is_xml(&this) { let f = self.get_from(object, name, &this)?; if self.is_callable(&f) { return Ok(Some((f, this))); } } cur = outer; } None => return Err(Abrupt::Internal("environment out of range".into())), } } Ok(None) } pub(crate) fn xml_enumerate(&mut self, id: ObjId) -> JsResult> { let len = match self.classify(&Value::Object(id)) { Xv::List(l) => self.list_len(l)?, _ => 1, }; self.xml_take_fuel(len)?; self.xml_charge(len.saturating_mul(string_bytes(10)))?; Ok((0..len).map(|i| index_key(i as u32)).collect()) } // ---- insertion ---- fn check_attach_node(&mut self, x: ObjId, v: ObjId) -> JsResult<()> { let mut depth = 0usize; let mut cur = Some(x); while let Some(a) = cur { if a == v { return Err(self.type_error("an XML value cannot be inserted into itself or its descendants")); } depth += 1; self.use_fuel()?; cur = self.xml_node(a)?.parent; } let height = self.rooted(&[Value::Object(x), Value::Object(v)], |i| i.node_height(v))?; if depth.saturating_add(height) > self.options.max_depth { return Err(Abrupt::Limit(LimitKind::Heap)); } Ok(()) } /// Refuses, before anything changes, an insertion of `c` under `x` that would make a cycle or /// a tree past the depth limit. fn check_attach(&mut self, x: ObjId, c: &Content) -> JsResult<()> { match c { Content::Text(_) => Ok(()), Content::Node(v) => { if self.xml_kind(*v)? == XmlKind::Attribute { return Ok(()); } self.check_attach_node(x, *v) } Content::List(l) => { for at in 0..self.list_len(*l)? { let Some(item) = self.list_at(*l, at)? else { break }; if self.xml_kind(item)? != XmlKind::Attribute { self.check_attach_node(x, item)?; } } Ok(()) } } } /// The levels of the tree at `root`, counting it. fn node_height(&mut self, root: ObjId) -> JsResult { let mut held = HeldBytes::new(self); let mut stack = Vec::new(); push_held(self, &mut held, &mut stack, (root, 0usize))?; let mut height = 1; while let Some(&(node, next)) = stack.last() { self.use_fuel()?; match self.child_at(node, next)? { Some(c) => { if let Some(top) = stack.last_mut() { top.1 += 1; } push_held(self, &mut held, &mut stack, (c, 0))?; height = height.max(stack.len()); } None => { stack.pop(); } } } Ok(height) } /// Removes `v` from its parent, so a node is never in two lists of children. Returns the /// position in `x` to use after the removal. The caller has taken [`Self::place_fuel`]. fn detach(&mut self, x: ObjId, v: ObjId, at: usize) -> JsResult { let Some(parent) = self.xml_node(v)?.parent else { return Ok(at) }; let Some(q) = self.xml_node(parent)?.children.iter().position(|&c| c == v) else { return Ok(at) }; self.remove_child(parent, q)?; Ok(if parent == x && q < at { at - 1 } else { at }) } /// Takes the fuel to put `v` among `len` children: the insertion and, when `v` has a parent, /// the scan and removal that take it out. fn place_fuel(&mut self, len: usize, v: ObjId) -> JsResult<()> { self.xml_text_fuel(len + 1)?; if let Some(parent) = self.xml_node(v)?.parent { let old = self.children_len(parent)?; self.xml_text_fuel(old)?; self.xml_text_fuel(old)?; } Ok(()) } /// Moves `v` to position `at` of `x` and returns where it went. The caller has taken the fuel /// and heap. fn place_child(&mut self, x: ObjId, at: usize, v: ObjId) -> JsResult { let at = self.detach(x, v, at)?; let node = self.xml_node_mut(x)?; let at = at.min(node.children.len()); node.children.insert(at, v); self.xml_node_mut(v)?.parent = Some(x); Ok(at) } /// Charges for one more child of `x`. `x` and `v` are held as roots across any collection. fn xml_charge_child(&mut self, x: ObjId, v: ObjId) -> JsResult<()> { self.rooted(&[Value::Object(x), Value::Object(v)], |i| i.xml_charge(XML_REF_BYTES)) } /// Takes the fuel and heap for the insertion first, so a limit leaves `v` where it was. fn insert_node(&mut self, x: ObjId, at: usize, v: ObjId) -> JsResult { self.xml_charge_child(x, v)?; let len = self.children_len(x)?; self.place_fuel(len, v)?; self.place_child(x, at, v) } /// The node to place as a child for `item`: `item` itself, or a new text node holding the /// value when it is an attribute. `roots` are held across any collection. fn child_for_item(&mut self, item: ObjId, roots: &[Value]) -> JsResult { if self.xml_kind(item)? != XmlKind::Attribute { return Ok(item); } let value = self.xml_node(item)?.value.clone(); let t = self.rooted(roots, |i| i.new_text_node(value, None))?; self.xml_keep(t); Ok(t) } /// Takes the fuel and heap to put every item of `l` under `x` and returns the nodes to place, /// from [`Self::child_for_item`], with the hold on the list of them, which the caller keeps /// until they are placed. The tree does not change here, so a limit leaves it as it was. fn reserve_list_insert(&mut self, x: ObjId, l: ObjId) -> JsResult<(Vec, HeldBytes)> { let count = self.list_len(l)?; let roots = [Value::Object(x), Value::Object(l)]; let mut held = HeldBytes::new(self); self.rooted(&roots, |i| held.add(i, size_of::().saturating_mul(count)))?; let mut items = self.xml_list(l)?.items.clone(); let bytes = XML_REF_BYTES.saturating_mul(items.len()); self.rooted(&roots, |i| i.xml_charge(bytes))?; let len = self.children_len(x)?; for (j, item) in items.iter_mut().enumerate() { *item = self.child_for_item(*item, &roots)?; self.place_fuel(len + j, *item)?; } Ok((items, held)) } /// Puts `items` at `at` in order, after [`Self::reserve_list_insert`]. fn place_children(&mut self, x: ObjId, at: usize, items: &[ObjId]) -> JsResult<()> { let mut at = at; for &item in items { at = self.place_child(x, at, item)? + 1; } Ok(()) } /// Puts a new text node holding `s` at position `at` of `x`. fn insert_text(&mut self, x: ObjId, at: usize, s: &Str) -> JsResult<()> { let t = self.new_text_node(s.clone(), Some(x))?; self.xml_keep(t); self.xml_charge(XML_REF_BYTES)?; let node = self.xml_node_mut(x)?; let at = at.min(node.children.len()); node.children.insert(at, t); Ok(()) } /// [[Insert]] (ECMA-357 section 9.1.1.11). pub(crate) fn xml_insert(&mut self, x: ObjId, at: usize, c: &Content) -> JsResult<()> { if self.xml_kind(x)? != XmlKind::Element { return Ok(()); } self.check_attach(x, c)?; match c { Content::Text(s) => self.insert_text(x, at, s)?, Content::Node(v) if self.xml_kind(*v)? == XmlKind::Attribute => { let value = self.xml_node(*v)?.value.clone(); self.insert_text(x, at, &value)?; } Content::Node(v) => { self.insert_node(x, at, *v)?; } Content::List(l) => { let (items, _held) = self.reserve_list_insert(x, *l)?; self.place_children(x, at, &items)?; } } Ok(()) } /// Charges for a [`Self::set_child`] of `v` at `at` that adds a child. fn charge_set_child(&mut self, x: ObjId, at: usize, v: ObjId) -> JsResult<()> { if at < self.children_len(x)? { return Ok(()); } self.xml_charge_child(x, v) } /// Puts `v` at `at`, or last when `at` is past the end, after [`Self::charge_set_child`]. fn set_child(&mut self, x: ObjId, at: usize, v: ObjId) -> JsResult<()> { let node = self.xml_node_mut(x)?; let old = match node.children.get_mut(at) { Some(slot) => Some(std::mem::replace(slot, v)), None => { node.children.push(v); None } }; if let Some(old) = old { self.xml_node_mut(old)?.parent = None; } self.xml_node_mut(v)?.parent = Some(x); Ok(()) } /// [[Replace]] (ECMA-357 section 9.1.1.12). pub(crate) fn xml_replace(&mut self, x: ObjId, at: usize, c: &Content) -> JsResult<()> { if self.xml_kind(x)? != XmlKind::Element { return Ok(()); } let at = at.min(self.children_len(x)?); match c { Content::Node(v) if self.xml_kind(*v)? != XmlKind::Attribute => { self.check_attach(x, c)?; if self.child_at(x, at)? == Some(*v) { return Ok(()); } self.charge_set_child(x, at, *v)?; let len = self.children_len(x)?; self.place_fuel(len, *v)?; let at = self.detach(x, *v, at)?; self.set_child(x, at, *v) } Content::List(l) => { self.check_attach(x, c)?; let (items, _held) = self.reserve_list_insert(x, *l)?; let len = self.children_len(x)?; self.xml_text_fuel(len)?; if at < len { self.remove_child(x, at)?; } self.place_children(x, at, &items) } Content::Node(v) => { let s = self.xml_node(*v)?.value.clone(); let t = self.new_text_node(s, Some(x))?; self.xml_keep(t); self.charge_set_child(x, at, t)?; self.set_child(x, at, t) } Content::Text(s) => { let t = self.new_text_node(s.clone(), Some(x))?; self.xml_keep(t); self.charge_set_child(x, at, t)?; self.set_child(x, at, t) } } } /// Replaces the first of `matches`, children of `x` in document order, with `c` and removes /// the rest; appends `c` when there are none. A limit leaves `x` as it was. The value in `c` /// is never one of `matches`. pub(crate) fn xml_replace_matches(&mut self, x: ObjId, matches: &[ObjId], c: &Content) -> JsResult<()> { let len = self.children_len(x)?; self.xml_text_fuel(len)?; self.xml_text_fuel(len)?; let at = match matches.first() { Some(first) => self.xml_node(x)?.children.iter().position(|c| c == first).unwrap_or(len), None => len, }; self.xml_replace(x, at, c)?; self.remove_children(x, matches.get(1..).unwrap_or_default()) } // ---- [[Put]] ---- /// A value to assign into a tree, copied when it is XML (steps 3 and 4 of section 9.1.1.2). fn content_for_put(&mut self, v: &Value) -> JsResult { match self.classify(v) { Xv::Node(n) if !matches!(self.xml_kind(n)?, XmlKind::Text | XmlKind::Attribute) => { let c = self.node_copy(n)?; self.xml_keep(c); Ok(Content::Node(c)) } Xv::List(l) => { let c = self.list_copy(l)?; self.xml_keep(c); Ok(Content::List(c)) } _ => Ok(Content::Text(self.to_str(v)?)), } } fn content_to_str(&mut self, c: &Content) -> JsResult { match c { Content::Text(s) => Ok(s.clone()), Content::Node(n) => self.node_to_str(*n), Content::List(l) => self.list_to_str(*l), } } fn put_attribute(&mut self, x: ObjId, n: &NameRef, c: &Content) -> JsResult<()> { if !is_ncname(&n.local) { return Ok(()); } let value = if let Content::List(l) = c { let mut s = Builder::new(); for i in 0..self.list_len(*l)? { if i > 0 { s.push_units(self, &[SPACE])?; } let Some(item) = self.list_at(*l, i)? else { break }; let text = self.node_to_str(item)?; let text = flat_charged(self, &text)?; s.push_units(self, &text)?; } let Value::String(s) = s.finish(self) else { return Err(Abrupt::Internal("not a string".into())) }; s } else { self.content_to_str(c)? }; let mut found = None; let count = self.xml_node(x)?.attrs.len(); self.xml_take_fuel(count)?; let mut kept = 0; for k in 0..count { let Some(a) = self.xml_node(x)?.attrs.get(k).copied() else { break }; let hit = self.xml_node(a)?.name.as_ref().is_some_and(|q| n.matches(q)); if hit && found.is_some() { self.xml_node_mut(a)?.parent = None; } else { if hit { found = Some(a); } self.xml_node_mut(x)?.attrs.swap(kept, k); kept += 1; } } self.xml_node_mut(x)?.attrs.truncate(kept); if let Some(a) = found { self.xml_node_mut(a)?.value = value; return Ok(()); } let name = XmlName { uri: Some(n.uri.clone().unwrap_or_else(|| Rc::from(Vec::new()))), local: n.local.clone(), prefix: None }; let a = self.new_xml_node(XmlNode { kind: XmlKind::Attribute, name: Some(name), parent: Some(x), value, attrs: Vec::new(), children: Vec::new(), ns: Vec::new(), })?; self.xml_keep(a); self.xml_charge(XML_REF_BYTES)?; self.xml_node_mut(x)?.attrs.push(a); Ok(()) } fn put_element(&mut self, x: ObjId, n: &NameRef, c: &Content) -> JsResult<()> { if !is_ncname(&n.local) && !n.is_star() { return Ok(()); } let len = self.children_len(x)?; self.xml_take_fuel(len)?; self.xml_keep(x); let mut held = HeldBytes::new(self); let mut matches = Vec::new(); for k in 0..len { let Some(child) = self.child_at(x, k)? else { break }; if n.matches_child(self.xml_node(child)?) { push_held(self, &mut held, &mut matches, child)?; } } match c { Content::Text(s) if !n.is_star() => self.put_element_text(x, n, &matches, s), _ => self.xml_replace_matches(x, &matches, c), } } /// Makes `s` the only content of the first of `matches`, or of a new element named `n` when /// there are none, and removes the other matches. A limit leaves `x` as it was. fn put_element_text(&mut self, x: ObjId, n: &NameRef, matches: &[ObjId], s: &Str) -> JsResult<()> { let at = self.children_len(x)?; self.xml_text_fuel(at)?; let target = match matches.first() { Some(&first) => first, None => { let uri = match &n.uri { Some(u) => u.clone(), None => self.xml_default_uri()?, }; let y = self.new_xml_node(XmlNode { kind: XmlKind::Element, name: Some(XmlName { uri: Some(uri), local: n.local.clone(), prefix: None }), parent: Some(x), value: Str::from(""), attrs: Vec::new(), children: Vec::new(), ns: Vec::new(), })?; self.xml_keep(y); self.check_attach_node(x, y)?; self.charge_set_child(x, at, y)?; y } }; let count = self.children_len(target)?; self.xml_take_fuel(count)?; let text = if s.len() > 0 { let t = self.new_text_node(s.clone(), Some(target))?; self.xml_keep(t); self.xml_charge_child(target, t)?; Some(t) } else { None }; if matches.is_empty() { self.set_child(x, at, target)?; } self.remove_children(x, matches.get(1..).unwrap_or_default())?; let children = std::mem::take(&mut self.xml_node_mut(target)?.children); for child in children { self.xml_node_mut(child)?.parent = None; } self.xml_node_mut(target)?.children.extend(text); Ok(()) } fn node_put(&mut self, x: ObjId, n: &NameRef, v: &Value) -> JsResult<()> { if self.xml_kind(x)? != XmlKind::Element { return Ok(()); } let c = self.content_for_put(v)?; if n.attribute { self.put_attribute(x, n, &c) } else { self.put_element(x, n, &c) } } /// [[ResolveValue]] (ECMA-357 section 9.2.1.10), walking the targets with a loop. Also returns /// the outermost element it added, if any. An error takes the added elements back out. fn resolve_value(&mut self, x: ObjId) -> JsResult)>> { let mut created = None; let r = self.resolve_value_adding(x, &mut created); match (r, created) { (Ok(Some(base)), created) => Ok(Some((base, created))), (Ok(None), _) => Ok(None), (Err(e), Some(c)) => { self.undo_created(c)?; Err(e) } (Err(e), None) => Err(e), } } /// Takes `created`, an element a path assignment added, back out of its parent. It takes no /// fuel or heap. fn undo_created(&mut self, created: ObjId) -> JsResult<()> { if let Some(parent) = self.xml_node(created)?.parent { self.xml_node_mut(parent)?.children.retain(|&c| c != created); self.xml_node_mut(created)?.parent = None; } Ok(()) } /// The steps of [`Self::resolve_value`], setting `created` to the first element they add. fn resolve_value_adding(&mut self, x: ObjId, created: &mut Option) -> JsResult> { self.xml_keep(x); let mut held = HeldBytes::new(self); let mut chain = Vec::new(); let mut cur = x; let base = loop { self.use_fuel()?; let Xv::List(l) = self.classify(&Value::Object(cur)) else { break cur }; if self.list_len(l)? > 0 { break cur; } let data = self.xml_list(l)?; let (Some(target), Some((name, false))) = (data.target, data.target_name.as_ref()) else { return Ok(None) }; if *name.local == *STAR { return Ok(None); } push_held(self, &mut held, &mut chain, l)?; cur = target; }; let mut base = base; self.xml_keep(base); while let Some(l) = chain.pop() { let Some((name, attribute)) = self.xml_list(l)?.target_name.clone() else { return Ok(None) }; let n = NameRef { uri: name.uri, local: name.local, attribute }; let mut target = self.xml_select(base, &n)?; self.xml_keep(target); if self.list_len(target)? == 0 { if let Xv::List(b) = self.classify(&Value::Object(base)) && self.list_len(b)? > 1 { return Ok(None); } let owner = match self.classify(&Value::Object(base)) { Xv::Node(b) => Some(b), Xv::List(b) => self.list_at(b, 0)?, Xv::Other => None, }; let before = match owner { Some(o) => self.children_len(o)?, None => 0, }; self.put_name(base, &n, &Value::str(""))?; if created.is_none() && let Some(o) = owner && self.children_len(o)? > before { *created = self.xml_node(o)?.children.last().copied(); } target = self.xml_select(base, &n)?; self.xml_keep(target); } base = target; } Ok(Some(base)) } fn put_name(&mut self, base: ObjId, n: &NameRef, v: &Value) -> JsResult<()> { match self.classify(&Value::Object(base)) { Xv::Node(x) => self.node_put(x, n, v), Xv::List(l) => { if self.list_len(l)? == 0 { let Some((r, created)) = self.resolve_value(l)? else { return Ok(()) }; let done = self.put_resolved(l, r, n, v); if done.is_err() && let Some(c) = created { self.undo_created(c)?; self.xml_list_mut(l)?.items.clear(); } return done; } if self.list_len(l)? == 1 && let Some(item) = self.list_at(l, 0)? { self.node_put(item, n, v)?; } Ok(()) } Xv::Other => Err(self.not_xml()), } } /// Assigns `v` to the name `n` of `r`, the value [`Self::resolve_value`] gave for the empty /// list `l`, when `r` is one node, and makes that node the item of `l`. fn put_resolved(&mut self, l: ObjId, r: ObjId, n: &NameRef, v: &Value) -> JsResult<()> { let single = match self.classify(&Value::Object(r)) { Xv::List(rl) if self.list_len(rl)? == 1 => self.list_at(rl, 0)?, Xv::Node(rn) => Some(rn), _ => None, }; let Some(single) = single else { return Ok(()) }; self.list_push(l, single)?; self.node_put(single, n, v) } fn list_put_index(&mut self, x: ObjId, index: usize, v: &Value) -> JsResult<()> { let data = self.xml_list(x)?; let target = data.target; let target_name = data.target_name.clone(); let (r, created) = match target { Some(t) => match self.resolve_value(t)? { Some((r, created)) => (Some(r), created), None => return Ok(()), }, None => (None, None), }; let put = self.list_put_resolved(x, index, v, r, target_name.as_ref()); if put.is_err() && let Some(c) = created { self.undo_created(c)?; } put } /// The steps of [`Self::list_put_index`] after the target `r` is resolved. fn list_put_resolved(&mut self, x: ObjId, index: usize, v: &Value, r: Option, target_name: Option<&(XmlName, bool)>) -> JsResult<()> { if index >= self.list_len(x)? { let r = match r { Some(r) => match self.classify(&Value::Object(r)) { Xv::List(rl) => { if self.list_len(rl)? != 1 { return Ok(()); } self.list_at(rl, 0)? } _ => Some(r), }, None => None, }; if let Some(r) = r && self.xml_kind(r)? != XmlKind::Element { return Ok(()); } let (kind, name) = match target_name { Some((name, true)) => { if let Some(r) = r { let n = NameRef { uri: name.uri.clone(), local: name.local.clone(), attribute: true }; if self.node_has_name(r, &n)? { return Ok(()); } } (XmlKind::Attribute, Some(name.clone())) } Some((name, false)) if *name.local != *STAR => (XmlKind::Element, Some(name.clone())), _ => (XmlKind::Text, None), }; if let Some(r) = r { match self.classify(v) { Xv::Node(n) if kind != XmlKind::Attribute => self.check_attach(r, &Content::Node(n))?, Xv::List(l) if kind != XmlKind::Attribute => self.check_attach(r, &Content::List(l))?, _ => {} } } let y = self.new_xml_node(XmlNode { kind, name, parent: r, value: Str::from(""), attrs: Vec::new(), children: Vec::new(), ns: Vec::new() })?; self.xml_keep(y); let i = self.list_len(x)?; let put = self.list_append_new(x, i, r, kind, y).and_then(|()| self.list_put_at(x, i, v)); if put.is_err() { self.undo_list_append(x, y)?; } return put; } self.list_put_at(x, index, v) } /// Adds the new node `y` as item `i` of `x` and, when there is a parent `r`, to the tree. fn list_append_new(&mut self, x: ObjId, i: usize, r: Option, kind: XmlKind, y: ObjId) -> JsResult<()> { if kind == XmlKind::Attribute { if let Some(r) = r { self.xml_charge(XML_REF_BYTES)?; self.xml_node_mut(r)?.attrs.push(y); } } else if let Some(r) = r { let len = self.children_len(r)?; let at = if i > 0 { let prev = self.list_at(x, i - 1)?; let position = match prev { Some(prev) => self.index_in_parent(r, prev)?, None => None, }; position.map_or(len, |j| j + 1) } else { len }; self.check_attach_node(r, y)?; self.insert_node(r, at, y)?; } self.list_push(x, y) } /// Takes `y` out of its parent and of `x` after an assignment that added it stopped. It takes /// no fuel or heap, so it runs after a limit. fn undo_list_append(&mut self, x: ObjId, y: ObjId) -> JsResult<()> { if let Some(parent) = self.xml_node(y)?.parent { let node = self.xml_node_mut(parent)?; node.children.retain(|&c| c != y); node.attrs.retain(|&a| a != y); self.xml_node_mut(y)?.parent = None; } self.xml_list_mut(x)?.items.retain(|&item| item != y); Ok(()) } /// Assigns `v` to item `i` of `x`. fn list_put_at(&mut self, x: ObjId, i: usize, v: &Value) -> JsResult<()> { let Some(item) = self.list_at(x, i)? else { return Ok(()) }; let xv = self.classify(v); let value = match xv { Xv::Node(n) if !matches!(self.xml_kind(n)?, XmlKind::Text | XmlKind::Attribute) => Content::Node(n), Xv::List(l) => Content::List(l), _ => Content::Text(self.to_str(v)?), }; let item_node = self.xml_node(item)?; let (item_kind, parent) = (item_node.kind, item_node.parent); if item_kind == XmlKind::Attribute { let Some(parent) = parent else { return Ok(()) }; let name = self.xml_node(item)?.name.clone(); let Some(name) = name else { return Ok(()) }; let n = NameRef { uri: name.uri, local: name.local, attribute: true }; let s = self.content_to_str(&value)?; self.put_attribute(parent, &n, &Content::Text(s))?; let attrs = self.xml_select(parent, &n)?; if let Some(a) = self.list_at(attrs, 0)? && let Some(slot) = self.xml_list_mut(x)?.items.get_mut(i) { *slot = a; } return Ok(()); } match value { Content::List(l) => { let c = self.list_shallow_copy(l)?; self.xml_keep(c); let place = match parent { Some(parent) => self.index_in_parent(parent, item)?.map(|q| (parent, q)), None => None, }; let count = self.list_len(c)?; let mut held = HeldBytes::new(self); self.rooted(&[Value::Object(x), Value::Object(c)], |i| held.add(i, size_of::().saturating_mul(count)))?; let mut items = self.xml_list(c)?.items.clone(); if place.is_some() { let roots = [Value::Object(x), Value::Object(c)]; for item in &mut items { self.use_fuel()?; *item = self.child_for_item(*item, &roots)?; } self.xml_list_mut(c)?.items.clone_from(&items); } self.xml_charge(XML_REF_BYTES.saturating_mul(items.len()))?; if let Some((parent, q)) = place { self.xml_replace(parent, q, &Content::List(c))?; } self.xml_list_mut(x)?.items.splice(i..=i, items); Ok(()) } Content::Node(_) => self.list_replace_item(x, i, item, parent, &value), Content::Text(_) if matches!(item_kind, XmlKind::Text | XmlKind::Comment | XmlKind::Pi) => { self.list_replace_item(x, i, item, parent, &value) } Content::Text(_) => self.node_put(item, &NameRef { uri: None, local: Rc::from(STAR), attribute: false }, v), } } fn list_replace_item(&mut self, x: ObjId, i: usize, item: ObjId, parent: Option, value: &Content) -> JsResult<()> { let mut replaced = None; if let Some(parent) = parent && let Some(q) = self.index_in_parent(parent, item)? { self.xml_replace(parent, q, value)?; replaced = self.child_at(parent, q)?; } let new_item = match (replaced, value) { (_, Content::Node(n)) => *n, (Some(r), Content::Text(_)) => r, (None, Content::Text(s)) => { let t = self.new_text_node(s.clone(), None)?; self.xml_keep(t); t } (_, Content::List(_)) => return Ok(()), }; if let Some(slot) = self.xml_list_mut(x)?.items.get_mut(i) { *slot = new_item; } Ok(()) } /// [[Put]] (ECMA-357 sections 9.1.1.2 and 9.2.1.2). pub(crate) fn xml_put(&mut self, base: &Value, k: &Value, v: &Value) -> JsResult<()> { self.rooted(&[base.clone(), v.clone()], |i| { let access = i.key_access(k)?; match (i.classify(base), access) { (Xv::Node(_), Access::Index(_)) => Err(i.type_error("an XML value's children cannot be assigned by position")), (Xv::List(l), Access::Index(at)) => i.list_put_index(l, at as usize, v), (_, Access::Name(n)) => { let Value::Object(o) = base else { return Err(i.not_xml()) }; i.put_name(*o, &n, v) } (Xv::Other, _) => Err(i.not_xml()), } }) } // ---- copies ---- fn copy_shallow(&mut self, src: ObjId, parent: Option) -> JsResult { let declared = self.xml_node(src)?.ns.len(); self.xml_charge(XML_NS_BYTES.saturating_mul(declared))?; let node = self.xml_node(src)?; let copy = XmlNode { kind: node.kind, name: node.name.clone(), parent, value: node.value.clone(), attrs: Vec::new(), children: Vec::new(), ns: node.ns.clone(), }; let c = self.new_xml_node(copy)?; self.xml_keep(c); let mut k = 0; while let Some(a) = self.xml_node(src)?.attrs.get(k).copied() { k += 1; self.use_fuel()?; let n = self.xml_node(a)?; let attr = XmlNode { kind: XmlKind::Attribute, name: n.name.clone(), parent: Some(c), value: n.value.clone(), attrs: Vec::new(), children: Vec::new(), ns: Vec::new(), }; let ac = self.new_xml_node(attr)?; self.xml_charge_link(ac)?; self.xml_node_mut(c)?.attrs.push(ac); } Ok(c) } /// [[DeepCopy]] of an XML value (ECMA-357 section 9.1.1.7), with no parent. pub(crate) fn node_copy(&mut self, root: ObjId) -> JsResult { self.rooted(&[Value::Object(root)], |i| { let top = i.copy_shallow(root, None)?; i.xml_keep(top); let mark = i.temp_roots.len(); let mut held = HeldBytes::new(i); let mut stack = Vec::new(); push_held(i, &mut held, &mut stack, (root, top, 0usize))?; while let Some(&(src, dst, next)) = stack.last() { i.use_fuel()?; i.temp_roots.truncate(mark); match i.child_at(src, next)? { Some(child) => { if let Some(frame) = stack.last_mut() { frame.2 += 1; } let c = i.copy_shallow(child, Some(dst))?; i.xml_charge_link(c)?; i.xml_node_mut(dst)?.children.push(c); push_held(i, &mut held, &mut stack, (child, c, 0))?; } None => { stack.pop(); } } } Ok(top) }) } /// A list of copies of the items of `l`, with its target (ECMA-357 section 9.2.1.7). pub(crate) fn list_copy(&mut self, l: ObjId) -> JsResult { self.rooted(&[Value::Object(l)], |i| { let data = i.xml_list(l)?; let (target, name) = (data.target, data.target_name.clone()); let out = i.new_xml_list(target, name)?; i.xml_keep(out); for at in 0..i.list_len(l)? { let Some(item) = i.list_at(l, at)? else { break }; let c = i.node_copy(item)?; i.list_push(out, c)?; } Ok(out) }) } /// A new list holding the same nodes as `l`. pub(crate) fn list_shallow_copy(&mut self, l: ObjId) -> JsResult { self.rooted(&[Value::Object(l)], |i| { let data = i.xml_list(l)?; let (target, name) = (data.target, data.target_name.clone()); let out = i.new_xml_list(target, name)?; i.xml_keep(out); for at in 0..i.list_len(l)? { i.use_fuel()?; let Some(item) = i.list_at(l, at)? else { break }; i.list_push(out, item)?; } Ok(out) }) } // ---- comparison ---- /// The namespace, local name and value of the attribute `id`, flattening a value that is a /// rope with [`flat_charged`]. fn attr_key(&mut self, id: ObjId) -> JsResult<(Option, Key, Key)> { let n = self.xml_node(id)?; let name = n.name.as_ref().ok_or_else(|| Abrupt::Internal("attribute without a name".into()))?; let (uri, local, value) = (name.uri.clone(), name.local.clone(), n.value.clone()); Ok((uri, local, flat_charged(self, &value)?)) } fn same_shallow(&mut self, a: ObjId, b: ObjId) -> JsResult { let (x, y) = (self.xml_node(a)?, self.xml_node(b)?); if x.kind != y.kind || x.attrs.len() != y.attrs.len() || x.children.len() != y.children.len() || x.value.len() != y.value.len() { return Ok(false); } match (&x.name, &y.name) { (None, None) => {} (Some(p), Some(q)) if p.local == q.local && p.uri == q.uri => {} _ => return Ok(false), } let (xv, yv) = (x.value.clone(), y.value.clone()); if flat_charged(self, &xv)? != flat_charged(self, &yv)? { return Ok(false); } let count = self.xml_node(a)?.attrs.len(); if count == 0 { return Ok(true); } self.xml_take_fuel(count)?; let mut held = HeldBytes::new(self); held.add(self, ATTR_COUNT_BYTES.saturating_mul(count))?; let mut counts: HashMap<(Option, Key, Key), usize> = HashMap::new(); for k in 0..count { let Some(id) = self.xml_node(a)?.attrs.get(k).copied() else { break }; *counts.entry(self.attr_key(id)?).or_default() += 1; } for k in 0..count { let Some(id) = self.xml_node(b)?.attrs.get(k).copied() else { break }; match counts.get_mut(&self.attr_key(id)?) { Some(n) if *n > 0 => *n -= 1, _ => return Ok(false), } } Ok(true) } /// [[Equals]] of two XML values (ECMA-357 section 9.1.1.9). The bytes it holds count against /// the heap limit, and `a` and `b` are held as roots. pub(crate) fn node_equals(&mut self, a: ObjId, b: ObjId) -> JsResult { self.rooted(&[Value::Object(a), Value::Object(b)], |i| { if !i.same_shallow(a, b)? { return Ok(false); } let mut held = HeldBytes::new(i); held.add(i, EQUALS_FRAME_BYTES)?; let mut stack = vec![(a, b, 0usize)]; while let Some(&(x, y, next)) = stack.last() { i.use_fuel()?; let (Some(cx), Some(cy)) = (i.child_at(x, next)?, i.child_at(y, next)?) else { stack.pop(); continue; }; if let Some(frame) = stack.last_mut() { frame.2 += 1; } if !i.same_shallow(cx, cy)? { return Ok(false); } if stack.len() == stack.capacity() { held.add(i, EQUALS_FRAME_BYTES.saturating_mul(stack.len()))?; } stack.push((cx, cy, 0)); } Ok(true) }) } fn list_equals(&mut self, l: ObjId, v: &Value) -> JsResult { let len = self.list_len(l)?; if matches!(v, Value::Undefined) && len == 0 { return Ok(true); } if let Xv::List(other) = self.classify(v) { if len != self.list_len(other)? { return Ok(false); } for i in 0..len { let (Some(a), Some(b)) = (self.list_at(l, i)?, self.list_at(other, i)?) else { return Ok(false) }; if !self.loose_eq(&Value::Object(a), &Value::Object(b))? { return Ok(false); } } return Ok(true); } if len == 1 && let Some(item) = self.list_at(l, 0)? { return self.loose_eq(&Value::Object(item), v); } Ok(false) } /// The E4X steps of the `==` operator (ECMA-357 section 11.5.1), or `None` when the ordinary /// steps apply. pub(crate) fn xml_loose_eq(&mut self, a: &Value, b: &Value) -> JsResult> { if let Xv::List(l) = self.classify(a) { return self.list_equals(l, b).map(Some); } if let Xv::List(l) = self.classify(b) { return self.list_equals(l, a).map(Some); } if let (Xv::Node(x), Xv::Node(y)) = (self.classify(a), self.classify(b)) { let (xk, yk) = (self.xml_kind(x)?, self.xml_kind(y)?); let textual = |k| matches!(k, XmlKind::Text | XmlKind::Attribute); if (textual(xk) && self.node_simple(y)?) || (textual(yk) && self.node_simple(x)?) { let (s, other) = (self.node_to_str(x)?, self.node_to_str(y)?); return Ok(Some(flat_charged(self, &s)? == flat_charged(self, &other)?)); } return self.node_equals(x, y).map(Some); } if let (Value::Object(x), Value::Object(y)) = (a, b) { let classes = (self.heap.object(*x).map(|o| &o.class), self.heap.object(*y).map(|o| &o.class)); match classes { (Some(Class::QName { name: p, .. }), Some(Class::QName { name: q, .. })) => return Ok(Some(p.uri == q.uri && p.local == q.local)), (Some(Class::Namespace(p)), Some(Class::Namespace(q))) => return Ok(Some(p.uri == q.uri)), _ => {} } } for (x, y) in [(a, b), (b, a)] { if let Xv::Node(n) = self.classify(x) && self.node_simple(n)? { let s = self.node_to_str(n)?; let other = self.to_str(y)?; return Ok(Some(flat_charged(self, &s)? == flat_charged(self, &other)?)); } } Ok(None) } pub(crate) fn xml_concat(&mut self, a: &Value, b: &Value) -> JsResult { self.rooted(&[a.clone(), b.clone()], |i| { let out = i.new_xml_list(None, None)?; i.xml_keep(out); for v in [a, b] { match i.classify(v) { Xv::Node(n) => i.list_push(out, n)?, Xv::List(l) => { for at in 0..i.list_len(l)? { i.use_fuel()?; let Some(item) = i.list_at(l, at)? else { break }; i.list_push(out, item)?; } } Xv::Other => {} } } Ok(Value::Object(out)) }) } // ---- content tests and walks ---- pub(crate) fn node_simple(&mut self, x: ObjId) -> JsResult { let node = self.xml_node(x)?; if matches!(node.kind, XmlKind::Comment | XmlKind::Pi) { return Ok(false); } let len = node.children.len(); self.xml_text_fuel(len)?; Ok(!self.xml_node(x)?.children.iter().any(|&c| self.xml_node(c).is_ok_and(|n| n.kind == XmlKind::Element))) } pub(crate) fn node_complex(&mut self, x: ObjId) -> JsResult { let node = self.xml_node(x)?; if node.kind != XmlKind::Element { return Ok(false); } Ok(!self.node_simple(x)?) } /// The nodes under `x` matching `n`, in document order ([[Descendants]], section 9.1.1.8). pub(crate) fn node_descendants(&mut self, x: ObjId, n: &NameRef, out: ObjId) -> JsResult<()> { let mut held = HeldBytes::new(self); let mut stack = Vec::new(); push_held(self, &mut held, &mut stack, (x, 0usize))?; if n.attribute { self.collect_matching(x, n, out)?; } while let Some(&(node, next)) = stack.last() { self.use_fuel()?; let Some(child) = self.child_at(node, next)? else { stack.pop(); continue; }; if let Some(frame) = stack.last_mut() { frame.1 += 1; } if n.attribute { self.collect_matching(child, n, out)?; } else if n.matches_child(self.xml_node(child)?) { self.list_push(out, child)?; } push_held(self, &mut held, &mut stack, (child, 0))?; } Ok(()) } /// Merges adjacent text and removes empty text under `root` (ECMA-357 section 13.4.4.26). pub(crate) fn node_normalize(&mut self, root: ObjId) -> JsResult<()> { let mut held = HeldBytes::new(self); let mut stack = Vec::new(); push_held(self, &mut held, &mut stack, (root, 0usize))?; while let Some(&(x, mut i)) = stack.last() { self.use_fuel()?; let Some(child) = self.child_at(x, i)? else { stack.pop(); continue; }; let kind = self.xml_kind(child)?; if kind == XmlKind::Text { while let Some(next) = self.child_at(x, i + 1)? { if self.xml_kind(next)? != XmlKind::Text { break; } let joined = { let (p, q) = (self.xml_node(child)?.value.clone(), self.xml_node(next)?.value.clone()); self.concat(&p, &q)? }; self.node_delete_at(x, i + 1)?; self.xml_node_mut(child)?.value = joined; } if self.xml_node(child)?.value.len() == 0 { self.node_delete_at(x, i)?; } else { i += 1; } if let Some(frame) = stack.last_mut() { frame.1 = i; } continue; } if let Some(frame) = stack.last_mut() { frame.1 = i + 1; } if kind == XmlKind::Element { push_held(self, &mut held, &mut stack, (child, 0))?; } } Ok(()) } // ---- strings ---- /// ToString of an XML value (ECMA-357 section 10.1.1). pub(crate) fn node_to_str(&mut self, x: ObjId) -> JsResult { if matches!(self.xml_kind(x)?, XmlKind::Text | XmlKind::Attribute) { return Ok(self.xml_node(x)?.value.clone()); } if !self.node_simple(x)? { return self.xml_string_of(&Value::Object(x), None); } let mut out = Builder::new(); for i in 0..self.children_len(x)? { self.use_fuel()?; let Some(c) = self.child_at(x, i)? else { break }; let node = self.xml_node(c)?; if !matches!(node.kind, XmlKind::Comment | XmlKind::Pi) { let text = node.value.clone(); let text = flat_charged(self, &text)?; out.push_units(self, &text)?; } } let Value::String(s) = out.finish(self) else { return Err(Abrupt::Internal("not a string".into())) }; Ok(s) } pub(crate) fn list_simple(&mut self, l: ObjId) -> JsResult { let len = self.list_len(l)?; if len == 1 { return match self.list_at(l, 0)? { Some(item) => self.node_simple(item), None => Ok(true), }; } for i in 0..len { self.use_fuel()?; if let Some(item) = self.list_at(l, i)? && self.xml_kind(item)? == XmlKind::Element { return Ok(false); } } Ok(true) } /// ToString of an XMLList value (ECMA-357 section 10.1.2). pub(crate) fn list_to_str(&mut self, l: ObjId) -> JsResult { if !self.list_simple(l)? { return self.xml_string_of(&Value::Object(l), None); } let mut out = Builder::new(); for i in 0..self.list_len(l)? { self.use_fuel()?; let Some(item) = self.list_at(l, i)? else { break }; if !matches!(self.xml_kind(item)?, XmlKind::Comment | XmlKind::Pi) { let text = self.node_to_str(item)?; let text = flat_charged(self, &text)?; out.push_units(self, &text)?; } } let Value::String(s) = out.finish(self) else { return Err(Abrupt::Internal("not a string".into())) }; Ok(s) } pub(crate) fn xml_to_string_value(&mut self, v: &Value) -> JsResult { match self.classify(v) { Xv::Node(n) => self.node_to_str(n), Xv::List(l) => self.list_to_str(l), Xv::Other => Err(self.not_xml()), } } fn write_escaped(&mut self, out: &mut Builder, text: &[u16], attribute: bool) -> JsResult<()> { let mut from = 0; for (i, &c) in text.iter().enumerate() { let rep: Option<&str> = match c { LT => Some("<"), AMP => Some("&"), GT if !attribute => Some(">"), DQUOTE if attribute => Some("""), LF if attribute => Some(" "), CR if attribute => Some(" "), TAB if attribute => Some(" "), _ => None, }; if let Some(rep) = rep { out.push_units(self, text.get(from..i).unwrap_or_default())?; out.push_str(self, rep)?; from = i + 1; } } out.push_units(self, text.get(from..).unwrap_or_default()) } /// The namespace `stack` declares for `uri`, nearest first, skipping prefixes redeclared nearer. /// `prefer` picks that prefix when several match; `need_prefix` rejects the default namespace. /// The set of prefixes it has passed is held against the heap limit. fn ns_lookup(&mut self, stack: &[XmlNs], uri: &Key, prefer: Option<&Key>, need_prefix: bool) -> JsResult> { let mut held = HeldBytes::new(self); let mut seen: HashSet<&[u16]> = HashSet::new(); let mut best = None; for ns in stack.iter().rev() { let Some(p) = &ns.prefix else { continue }; if !insert_held(self, &mut held, &mut seen, &p[..])? { continue; } if ns.uri == *uri && !(need_prefix && p.is_empty()) { if prefer == Some(p) { return Ok(Some(ns.clone())); } if best.is_none() { best = Some(ns.clone()); } } } Ok(best) } /// The first of `ns0`, `ns1`, ... that no entry of `stack` uses. One of the first /// `stack.len() + 1` is free, so one pass over `stack` marks the used ones. fn fresh_prefix(&mut self, stack: &[XmlNs]) -> JsResult { self.xml_take_fuel(stack.len() + 1)?; let mut held = HeldBytes::new(self); held.add(self, stack.len() + 1)?; let mut used = vec![false; stack.len() + 1]; for ns in stack { if let Some(slot) = ns.prefix.as_deref().and_then(prefix_number).and_then(|n| used.get_mut(n)) { *slot = true; } } let n = used.iter().position(|u| !u).unwrap_or(stack.len()); Ok(key(&format!("ns{n}"))) } /// Picks the prefix of a name in `scope`, adding a declaration to `scope` when none is there. fn out_prefix(&mut self, name: &XmlName, scope: &mut Vec, attribute: bool) -> JsResult { self.xml_take_fuel(scope.len() + 1)?; let empty: Key = Rc::from(Vec::new()); let uri = name.uri.clone().unwrap_or_else(|| empty.clone()); if uri.is_empty() { if !attribute && prefix_in_scope(scope, &empty).is_some_and(|ns| !ns.uri.is_empty()) { self.xml_charge(XML_NS_BYTES)?; scope.push(XmlNs { prefix: Some(empty.clone()), uri }); } return Ok(empty); } if *uri == *key(XML_URI) { return Ok(key("xml")); } if let Some(ns) = self.ns_lookup(scope, &uri, name.prefix.as_ref(), attribute)? { return Ok(ns.prefix.unwrap_or(empty)); } let prefix = match &name.prefix { Some(p) if !(attribute && p.is_empty()) && prefix_in_scope(scope, p).is_none() => p.clone(), _ if !attribute && prefix_in_scope(scope, &empty).is_none() => empty, _ => self.fresh_prefix(scope)?, }; self.xml_charge(XML_NS_BYTES)?; scope.push(XmlNs { prefix: Some(prefix.clone()), uri }); Ok(prefix) } fn write_name(&mut self, out: &mut Builder, prefix: &[u16], local: &[u16]) -> JsResult<()> { if !prefix.is_empty() { out.push_units(self, prefix)?; out.push_units(self, &[COLON])?; } out.push_units(self, local) } /// Writes `x`, and returns the frame of an element with children, which is left open. fn write_enter( &mut self, out: &mut Builder, x: ObjId, indent: u64, settings: &Settings, pretty: bool, scope: &mut Vec, ) -> JsResult> { self.use_fuel()?; let node = self.xml_node(x)?; let (kind, value) = (node.kind, node.value.clone()); let value = flat_charged(self, &value)?; self.xml_text_fuel(value.len())?; if pretty { out.push_repeated(self, &[SPACE], indent)?; } match kind { XmlKind::Text => { let trimmed: &[u16] = if pretty { trim_xml_space(&value) } else { &value }; self.write_escaped(out, trimmed, false)?; return Ok(None); } XmlKind::Attribute => { self.write_escaped(out, &value, true)?; return Ok(None); } XmlKind::Comment => { out.push_str(self, "")?; return Ok(None); } XmlKind::Pi => { let target = self.xml_node(x)?.name.as_ref().map(|n| n.local.clone()).unwrap_or_else(|| Rc::from(Vec::new())); out.push_str(self, "")?; return Ok(None); } XmlKind::Element => {} } let ns_mark = scope.len(); let node = self.xml_node(x)?; let declared = node.ns.len(); let name = node.name.clone().ok_or_else(|| Abrupt::Internal("element without a name".into()))?; self.xml_take_fuel(declared.saturating_mul(scope.len() + 1))?; for unnamed in [false, true] { let mut k = 0; while let Some(ns) = self.xml_node(x)?.ns.get(k).cloned() { k += 1; if ns.prefix.is_none() != unnamed { continue; } if !unnamed && !scope.iter().any(|a| a.uri == ns.uri && a.prefix == ns.prefix) { self.xml_charge(XML_NS_BYTES)?; scope.push(ns); } else if unnamed && !scope.iter().any(|a| a.uri == ns.uri) { let prefix = self.fresh_prefix(scope)?; self.xml_charge(XML_NS_BYTES)?; scope.push(XmlNs { prefix: Some(prefix), uri: ns.uri }); } } } let prefix = self.out_prefix(&name, scope, false)?; out.push_units(self, &[LT])?; self.write_name(out, &prefix, &name.local)?; let mut k = 0; while let Some(a) = self.xml_node(x)?.attrs.get(k).copied() { k += 1; self.use_fuel()?; let an = self.xml_node(a)?.name.clone().ok_or_else(|| Abrupt::Internal("attribute without a name".into()))?; let attr_prefix = self.out_prefix(&an, scope, true)?; let value = self.xml_node(a)?.value.clone(); let value = flat_charged(self, &value)?; out.push_units(self, &[SPACE])?; self.write_name(out, &attr_prefix, &an.local)?; out.push_units(self, &[EQ, DQUOTE])?; self.write_escaped(out, &value, true)?; out.push_units(self, &[DQUOTE])?; } for at in ns_mark..scope.len() { let Some(ns) = scope.get(at).cloned() else { break }; let ns_prefix = ns.prefix.unwrap_or_else(|| Rc::from(Vec::new())); out.push_str(self, " xmlns")?; if !ns_prefix.is_empty() { out.push_units(self, &[COLON])?; out.push_units(self, &ns_prefix)?; } out.push_units(self, &[EQ, DQUOTE])?; self.write_escaped(out, &ns.uri, true)?; out.push_units(self, &[DQUOTE])?; } let count = self.children_len(x)?; if count == 0 { scope.truncate(ns_mark); out.push_str(self, "/>")?; return Ok(None); } out.push_units(self, &[GT])?; let first_is_text = match self.child_at(x, 0)? { Some(c) => self.xml_kind(c)? == XmlKind::Text, None => false, }; let pretty_children = pretty && (count > 1 || !first_is_text); let child_indent = if pretty_children { indent.saturating_add(u64::from(settings.indent)) } else { 0 }; Ok(Some(OutFrame { node: x, next: 0, indent, child_indent, pretty_children, ns_mark, prefix, local: name.local })) } /// Writes the tree at `root`. Its open elements are held against the heap limit. fn write_node(&mut self, out: &mut Builder, root: ObjId, indent: u64, settings: &Settings, pretty: bool) -> JsResult<()> { let mut scope: Vec = Vec::new(); let mut held = HeldBytes::new(self); let mut stack: Vec = Vec::new(); if let Some(frame) = self.write_enter(out, root, indent, settings, pretty, &mut scope)? { push_held(self, &mut held, &mut stack, frame)?; } while let Some(frame) = stack.last_mut() { let (node, next, child_indent, pretty_children) = (frame.node, frame.next, frame.child_indent, frame.pretty_children); frame.next += 1; match self.child_at(node, next)? { Some(child) => { if pretty_children { out.push_units(self, &[LF])?; } if let Some(frame) = self.write_enter(out, child, child_indent, settings, pretty && pretty_children, &mut scope)? { push_held(self, &mut held, &mut stack, frame)?; } } None => { let Some(frame) = stack.pop() else { break }; if frame.pretty_children { out.push_units(self, &[LF])?; out.push_repeated(self, &[SPACE], frame.indent)?; } out.push_units(self, &[LT, SLASH])?; self.write_name(out, &frame.prefix, &frame.local)?; out.push_units(self, &[GT])?; scope.truncate(frame.ns_mark); } } } Ok(()) } /// ToXMLString (ECMA-357 section 10.2). `pretty` overrides the setting when it is given. pub(crate) fn xml_string_of(&mut self, v: &Value, pretty: Option) -> JsResult { let settings = self.xml_settings()?; let pretty = pretty.unwrap_or(settings.pretty); let mut out = Builder::new(); match self.classify(v) { Xv::Node(n) => self.write_node(&mut out, n, 0, &settings, pretty)?, Xv::List(l) => { for i in 0..self.list_len(l)? { self.use_fuel()?; let Some(item) = self.list_at(l, i)? else { break }; if i > 0 && pretty { out.push_units(self, &[LF])?; } self.write_node(&mut out, item, 0, &settings, pretty)?; } } Xv::Other => return Err(self.not_xml()), } let Value::String(s) = out.finish(self) else { return Err(Abrupt::Internal("not a string".into())) }; Ok(s) } // ---- the parser ---- fn syntax(&mut self, message: &str) -> Abrupt { self.throw(ErrorType::Syntax, message) } /// Decodes `src[from..to]` onto `out`, holding the room it takes in `out` with `held` first. The /// decoded text is never longer than the source. fn decode_into(&mut self, src: &[u16], from: usize, to: usize, out: &mut Vec, attribute: bool, held: &mut HeldBytes) -> JsResult<()> { self.xml_text_fuel(to.saturating_sub(from))?; reserve_held(self, held, out, to.saturating_sub(from))?; let mut pos = from; while pos < to { let Some(&c) = src.get(pos) else { break }; if c == AMP { let Some((after, ch)) = scan_reference(src, pos).filter(|(after, _)| *after <= to) else { return Err(self.syntax("invalid XML character or entity reference")); }; push_char(out, ch); pos = after; continue; } if pos + 1 < to && starts_pair(c, src.get(pos + 1).copied()) { out.extend_from_slice(src.get(pos..pos + 2).unwrap_or_default()); pos += 2; continue; } if !is_xml_char(u32::from(c)) { return Err(self.syntax("invalid character in XML text")); } pos += 1; match (c, attribute) { (CR, _) => { if src.get(pos) == Some(&LF) { pos += 1; } out.push(if attribute { SPACE } else { LF }); } (TAB | LF, true) => out.push(SPACE), _ => out.push(c), } } Ok(()) } fn find_from(src: &[u16], from: usize, needle: &[u16]) -> Option { src.get(from..)?.windows(needle.len()).position(|w| w == needle).map(|i| from + i) } /// Takes fuel for `units` code units that the parser scanned, on top of the fuel of the step /// that scanned them, so a short run costs nothing extra. fn xml_scan_fuel(&mut self, units: usize) -> JsResult<()> { self.xml_take_fuel(units / UNITS_PER_FUEL) } /// The offset after the white space at `src[pos]`, taking fuel for its length. fn skip_space(&mut self, src: &[u16], pos: usize) -> JsResult { let end = skip_xml_space(src, pos); self.xml_scan_fuel(end - pos)?; Ok(end) } /// The offset after the XML name at `src[pos]`, taking fuel for the units it scanned. fn read_name(&mut self, src: &[u16], pos: usize) -> JsResult { if let Some(end) = scan_name(src, pos) { self.xml_scan_fuel(end - pos)?; return Ok(end); } if src.get(pos).copied().is_some_and(is_name_start) { let scanned = src.get(pos..).unwrap_or_default().iter().skip(1).take_while(|&&c| is_name_part(c)).count(); self.xml_scan_fuel(scanned + 1)?; } Err(self.syntax("invalid XML name")) } fn check_units(&mut self, src: &[u16], from: usize, to: usize) -> JsResult<()> { if valid_xml_units(src.get(from..to).unwrap_or_default()) { return Ok(()); } Err(self.syntax("invalid character in XML text")) } /// Splits `prefix:local`. fn split_name<'a>(&mut self, raw: &'a [u16]) -> JsResult<(Option<&'a [u16]>, &'a [u16])> { let Some(colon) = raw.iter().position(|&c| c == COLON) else { return Ok((None, raw)) }; let (prefix, rest) = raw.split_at(colon); let local = rest.get(1..).unwrap_or_default(); if !is_ncname(prefix) || !is_ncname(local) { return Err(self.syntax("invalid XML qualified name")); } Ok((Some(prefix), local)) } fn resolve_prefix(&mut self, prefix: &[u16], own: &[XmlNs], open: &[ObjId]) -> JsResult> { if prefix == key("xml").as_ref() { return Ok(Some(key(XML_URI))); } self.xml_take_fuel(own.len() + open.len() + 1)?; if let Some(ns) = prefix_in_scope(own, prefix) { return Ok(Some(ns.uri.clone())); } for &e in open.iter().rev() { let node = self.xml_node(e)?; let len = node.ns.len(); if let Some(ns) = prefix_in_scope(&node.ns, prefix) { return Ok(Some(ns.uri.clone())); } self.xml_take_fuel(len / UNITS_PER_FUEL)?; } Ok(None) } fn place_parsed(&mut self, node: ObjId, open: &[ObjId], roots: &mut Vec) -> JsResult<()> { if let Some(&top) = open.last() { self.xml_charge_link(node)?; self.xml_node_mut(top)?.children.push(node); self.xml_node_mut(node)?.parent = Some(top); } else { self.xml_keep(node); self.xml_charge(XML_REF_BYTES)?; roots.push(node); } Ok(()) } /// Makes a text node of `text`, unless it is empty or ignored white space. Taking the buffer /// releases `text_held`, its hold. fn flush_text( &mut self, text: &mut Vec, text_held: &mut HeldBytes, ignore_whitespace: bool, open: &[ObjId], roots: &mut Vec, ) -> JsResult<()> { if text.is_empty() { return Ok(()); } if ignore_whitespace && text.iter().all(|&c| is_xml_space(c)) { text.clear(); return Ok(()); } let value = Str::from_units(std::mem::take(text)); *text_held = HeldBytes::new(self); let t = self.new_text_node(value, None)?; self.place_parsed(t, open, roots) } /// Parses the open tag at `src[pos]` and returns the offset after it. An element left open is /// pushed onto `open` and its name onto `names`, both held with `stack_held`. The attributes, /// sets and copies it works with are held until it returns. fn parse_open_tag( &mut self, src: &[u16], pos: usize, open: &mut Vec, names: &mut Vec<(usize, usize)>, stack_held: &mut HeldBytes, roots: &mut Vec, ) -> JsResult { if open.len() >= self.options.max_depth { return Err(self.syntax(TOO_DEEP)); } let name_start = pos + 1; let name_end = self.read_name(src, name_start)?; let raw_name = src.get(name_start..name_end).unwrap_or_default(); let (elem_prefix, elem_local) = self.split_name(raw_name)?; let mut at = name_end; let mut held = HeldBytes::new(self); let mut raw_attrs: Vec<(usize, usize, Vec)> = Vec::new(); let closes = loop { let before = at; at = self.skip_space(src, at)?; match src.get(at).copied() { None => return Err(self.syntax("unterminated XML tag")), Some(SLASH) if src.get(at + 1) == Some(>) => { at += 2; break true; } Some(GT) => { at += 1; break false; } Some(_) if at == before => return Err(self.syntax("expected white space before an attribute")), Some(_) => { self.use_fuel()?; let end = self.read_name(src, at)?; let p = self.skip_space(src, end)?; if src.get(p) != Some(&EQ) { return Err(self.syntax("expected '=' after an attribute name")); } let p = self.skip_space(src, p + 1)?; let quote = match src.get(p).copied() { Some(q @ (DQUOTE | SQUOTE)) => q, _ => return Err(self.syntax("expected a quoted attribute value")), }; let value_start = p + 1; let Some(value_end) = Self::find_from(src, value_start, &[quote]) else { self.xml_text_fuel(src.len().saturating_sub(value_start))?; return Err(self.syntax("unterminated attribute value")); }; if src.get(value_start..value_end).is_some_and(|v| v.contains(<)) { self.xml_text_fuel(value_end - value_start)?; return Err(self.syntax("'<' cannot appear in an attribute value")); } let mut value = Vec::new(); self.decode_into(src, value_start, value_end, &mut value, true, &mut held)?; push_held(self, &mut held, &mut raw_attrs, (at, end, value))?; at = value_end + 1; } } }; self.xml_charge(XML_NS_BYTES.saturating_mul(raw_attrs.len()))?; let xmlns = key("xmlns"); let declares = |raw: &[u16]| raw == &xmlns[..] || raw.strip_prefix(&xmlns[..]).is_some_and(|rest| rest.first() == Some(&COLON)); let decls = raw_attrs.iter().filter(|(start, end, _)| declares(src.get(*start..*end).unwrap_or_default())).count(); let mut ns: Vec = Vec::with_capacity(decls); let mut seen_decl: HashSet<&[u16]> = HashSet::new(); for (start, end, value) in &raw_attrs { let raw = src.get(*start..*end).unwrap_or_default(); if raw == &xmlns[..] { if !ns_binding_allowed(&[], value) { return Err(self.syntax(RESERVED_NS_BINDING)); } if !insert_held(self, &mut held, &mut seen_decl, &[] as &[u16])? { return Err(self.syntax("a namespace is declared twice")); } self.xml_charge(string_bytes(0).saturating_add(string_bytes(value.len())))?; ns.push(XmlNs { prefix: Some(Rc::from(Vec::new())), uri: Rc::from(value.as_slice()) }); } else if let (Some(p), local) = self.split_name(raw)? && p == &xmlns[..] { if value.is_empty() { return Err(self.syntax("a namespace prefix cannot be declared with an empty URI")); } if !ns_binding_allowed(local, value) { return Err(self.syntax(RESERVED_NS_BINDING)); } if !insert_held(self, &mut held, &mut seen_decl, local)? { return Err(self.syntax("a namespace is declared twice")); } self.xml_charge(string_bytes(local.len()).saturating_add(string_bytes(value.len())))?; ns.push(XmlNs { prefix: Some(Rc::from(local)), uri: Rc::from(value.as_slice()) }); } } let uri = match elem_prefix { Some(p) => match self.resolve_prefix(p, &ns, open)? { Some(u) => u, None => return Err(self.syntax("an XML name uses an undeclared namespace prefix")), }, None => match self.resolve_prefix(&[], &ns, open)? { Some(u) => u, None => self.xml_default_uri()?, }, }; let name = XmlName { uri: Some(uri), local: Rc::from(elem_local), prefix: Some(Rc::from(elem_prefix.unwrap_or_default())) }; let element = self.new_xml_node(XmlNode { kind: XmlKind::Element, name: Some(name), parent: None, value: Str::from(""), attrs: Vec::new(), children: Vec::new(), ns, })?; self.place_parsed(element, open, roots)?; let mut unique: HashSet<(Key, &[u16])> = HashSet::new(); let mut own: Option> = None; for (start, end, value) in raw_attrs { self.use_fuel()?; let raw = src.get(start..end).unwrap_or_default(); let (prefix, local) = self.split_name(raw)?; if raw == key("xmlns").as_ref() || prefix.is_some_and(|p| p == key("xmlns").as_ref()) { continue; } let (auri, stored_prefix): (Key, Key) = match prefix { Some(p) => { if own.is_none() { let count = self.xml_node(element)?.ns.len(); held.add(self, size_of::().saturating_mul(count))?; own = Some(self.xml_node(element)?.ns.clone()); } match self.resolve_prefix(p, own.as_deref().unwrap_or_default(), open)? { Some(u) => (u, Rc::from(p)), None => return Err(self.syntax("an XML name uses an undeclared namespace prefix")), } } None => (Rc::from(Vec::new()), Rc::from(Vec::new())), }; if !insert_held(self, &mut held, &mut unique, (auri.clone(), local))? { return Err(self.type_error("an XML element has two attributes with the same name")); } let a = self.new_xml_node(XmlNode { kind: XmlKind::Attribute, name: Some(XmlName { uri: Some(auri), local: Rc::from(local), prefix: Some(stored_prefix) }), parent: Some(element), value: Str::from_units(value), attrs: Vec::new(), children: Vec::new(), ns: Vec::new(), })?; self.xml_charge_link(a)?; self.xml_node_mut(element)?.attrs.push(a); } if !closes { push_held(self, stack_held, open, element)?; push_held(self, stack_held, names, (name_start, name_end))?; } Ok(at) } fn parse_close_tag(&mut self, src: &[u16], pos: usize, open: &mut Vec, names: &mut Vec<(usize, usize)>) -> JsResult { let name_start = pos + 2; let name_end = self.read_name(src, name_start)?; let at = self.skip_space(src, name_end)?; if src.get(at) != Some(>) { return Err(self.syntax("expected '>' at the end of a closing tag")); } let Some((open_start, open_end)) = names.pop() else { return Err(self.syntax("a closing tag has no matching open tag")); }; if src.get(open_start..open_end) != src.get(name_start..name_end) { return Err(self.syntax("the closing tag does not match the open tag")); } open.pop(); Ok(at + 1) } /// Parses XML text into nodes, kept reachable through the temporary roots until the caller's /// scope ends. The open elements and their names, the text being gathered and each tag's /// attributes are held as they grow. The allowance held from the source's length covers the /// copy a value's units take as it becomes a string, before the node that holds it is charged. pub(crate) fn xml_parse(&mut self, source: &[u16]) -> JsResult> { let mut held = HeldBytes::new(self); held.add(self, string_bytes(source.len()).saturating_mul(2))?; let mut stack_held = HeldBytes::new(self); let mut text_held = HeldBytes::new(self); let settings = self.xml_settings()?; let mut src = source; let offset_start = skip_xml_space(src, 0); if src.get(offset_start..).is_some_and(|r| r.starts_with(&parser_units("")) else { self.xml_text_fuel(src.len())?; return Err(self.syntax("unterminated XML declaration")); }; self.xml_text_fuel(end + 2)?; self.check_units(src, offset_start, end)?; if !is_xml_decl_body(src.get(offset_start + 5..end).unwrap_or_default()) { return Err(self.syntax("invalid XML declaration")); } src = src.get(end + 2..).unwrap_or_default(); } let mut pos = 0; let mut roots = Vec::new(); let mut open: Vec = Vec::new(); let mut names: Vec<(usize, usize)> = Vec::new(); let mut text: Vec = Vec::new(); while let Some(&c) = src.get(pos) { self.use_fuel()?; if c != LT { let end = Self::find_from(src, pos, &[LT]).unwrap_or(src.len()); let section_end = parser_units("]]>"); if src.get(pos..end).is_some_and(|run| run.windows(3).any(|w| w == section_end)) { self.xml_text_fuel(end - pos)?; return Err(self.syntax("']]>' cannot appear in XML text")); } self.decode_into(src, pos, end, &mut text, false, &mut text_held)?; pos = end; continue; } let rest = src.get(pos..).unwrap_or_default(); if rest.starts_with(&parser_units("")) else { self.xml_text_fuel(src.len() - pos)?; return Err(self.syntax("unterminated CDATA section")); }; self.xml_text_fuel(end - pos)?; self.check_units(src, pos + 9, end)?; let section = src.get(pos + 9..end).unwrap_or_default(); reserve_held(self, &mut text_held, &mut text, section.len())?; text.extend_from_slice(section); pos = end + 3; continue; } self.flush_text(&mut text, &mut text_held, settings.ignore_whitespace, &open, &mut roots)?; if rest.starts_with(&parser_units("