servo/components/script/dom/bindings/xmlname.rs
Josh Matthews c94d909a86
script: Limit public exports. (#34915)
* script: Restrict reexport visibility of DOM types.

Signed-off-by: Josh Matthews <josh@joshmatthews.net>

* script: Mass pub->pub(crate) conversion.

Signed-off-by: Josh Matthews <josh@joshmatthews.net>

* script: Hide existing dead code warnings.

Signed-off-by: Josh Matthews <josh@joshmatthews.net>

* Formatting.

Signed-off-by: Josh Matthews <josh@joshmatthews.net>

* Fix clippy warnings.

Signed-off-by: Josh Matthews <josh@joshmatthews.net>

* Formatting.

Signed-off-by: Josh Matthews <josh@joshmatthews.net>

* Fix unit tests.

Signed-off-by: Josh Matthews <josh@joshmatthews.net>

* Fix clippy.

Signed-off-by: Josh Matthews <josh@joshmatthews.net>

* More formatting.

Signed-off-by: Josh Matthews <josh@joshmatthews.net>

---------

Signed-off-by: Josh Matthews <josh@joshmatthews.net>
2025-01-10 08:19:19 +00:00

171 lines
4.7 KiB
Rust

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! Functions for validating and extracting qualified XML names.
use html5ever::{namespace_url, ns, LocalName, Namespace, Prefix};
use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
use crate::dom::bindings::str::DOMString;
/// Validate a qualified name. See <https://dom.spec.whatwg.org/#validate> for details.
pub(crate) fn validate_qualified_name(qualified_name: &str) -> ErrorResult {
// Step 2.
match xml_name_type(qualified_name) {
XMLName::Invalid => Err(Error::InvalidCharacter),
XMLName::Name => Err(Error::InvalidCharacter), // see whatwg/dom#671
XMLName::QName => Ok(()),
}
}
/// Validate a namespace and qualified name and extract their parts.
/// See <https://dom.spec.whatwg.org/#validate-and-extract> for details.
pub(crate) fn validate_and_extract(
namespace: Option<DOMString>,
qualified_name: &str,
) -> Fallible<(Namespace, Option<Prefix>, LocalName)> {
// Step 1.
let namespace = namespace_from_domstring(namespace);
// Step 2.
validate_qualified_name(qualified_name)?;
let colon = ':';
// Step 5.
let mut parts = qualified_name.splitn(2, colon);
let (maybe_prefix, local_name) = {
let maybe_prefix = parts.next();
let maybe_local_name = parts.next();
debug_assert!(parts.next().is_none());
if let Some(local_name) = maybe_local_name {
debug_assert!(!maybe_prefix.unwrap().is_empty());
(maybe_prefix, local_name)
} else {
(None, maybe_prefix.unwrap())
}
};
debug_assert!(!local_name.contains(colon));
match (namespace, maybe_prefix) {
(ns!(), Some(_)) => {
// Step 6.
Err(Error::Namespace)
},
(ref ns, Some("xml")) if ns != &ns!(xml) => {
// Step 7.
Err(Error::Namespace)
},
(ref ns, p) if ns != &ns!(xmlns) && (qualified_name == "xmlns" || p == Some("xmlns")) => {
// Step 8.
Err(Error::Namespace)
},
(ns!(xmlns), p) if qualified_name != "xmlns" && p != Some("xmlns") => {
// Step 9.
Err(Error::Namespace)
},
(ns, p) => {
// Step 10.
Ok((ns, p.map(Prefix::from), LocalName::from(local_name)))
},
}
}
/// Results of `xml_name_type`.
#[derive(PartialEq)]
#[allow(missing_docs)]
pub(crate) enum XMLName {
QName,
Name,
Invalid,
}
/// Check if an element name is valid. See <http://www.w3.org/TR/xml/#NT-Name>
/// for details.
pub(crate) fn xml_name_type(name: &str) -> XMLName {
fn is_valid_start(c: char) -> bool {
matches!(c, ':' |
'A'..='Z' |
'_' |
'a'..='z' |
'\u{C0}'..='\u{D6}' |
'\u{D8}'..='\u{F6}' |
'\u{F8}'..='\u{2FF}' |
'\u{370}'..='\u{37D}' |
'\u{37F}'..='\u{1FFF}' |
'\u{200C}'..='\u{200D}' |
'\u{2070}'..='\u{218F}' |
'\u{2C00}'..='\u{2FEF}' |
'\u{3001}'..='\u{D7FF}' |
'\u{F900}'..='\u{FDCF}' |
'\u{FDF0}'..='\u{FFFD}' |
'\u{10000}'..='\u{EFFFF}')
}
fn is_valid_continuation(c: char) -> bool {
is_valid_start(c) ||
matches!(c,
'-' |
'.' |
'0'..='9' |
'\u{B7}' |
'\u{300}'..='\u{36F}' |
'\u{203F}'..='\u{2040}')
}
let mut iter = name.chars();
let mut non_qname_colons = false;
let mut seen_colon = false;
let mut last = match iter.next() {
None => return XMLName::Invalid,
Some(c) => {
if !is_valid_start(c) {
return XMLName::Invalid;
}
if c == ':' {
non_qname_colons = true;
}
c
},
};
for c in iter {
if !is_valid_continuation(c) {
return XMLName::Invalid;
}
if c == ':' {
if seen_colon {
non_qname_colons = true;
} else {
seen_colon = true;
}
}
last = c
}
if last == ':' {
non_qname_colons = true
}
if non_qname_colons {
XMLName::Name
} else {
XMLName::QName
}
}
/// Convert a possibly-null URL to a namespace.
///
/// If the URL is None, returns the empty namespace.
pub(crate) fn namespace_from_domstring(url: Option<DOMString>) -> Namespace {
match url {
None => ns!(),
Some(s) => Namespace::from(s),
}
}