#![allow(unused_variables)]
use alloc::{boxed::Box, collections::BTreeMap, string::String, vec::Vec};
use core::fmt;
#[cfg(feature = "std")]
use std::path::Path;
#[cfg(feature = "svg")]
pub mod svg;
fn decode_xml_entities(s: &str) -> String {
let mut result = String::with_capacity(s.len());
let mut chars = s.chars().peekable();
while let Some(c) = chars.next() {
if c == '&' {
let mut entity = String::new();
let mut found_semicolon = false;
while let Some(&next) = chars.peek() {
if next == ';' {
chars.next();
found_semicolon = true;
break;
}
if !next.is_alphanumeric() && next != '#' {
break;
}
entity.push(chars.next().unwrap());
if entity.len() > 10 {
break;
}
}
if found_semicolon {
match entity.as_str() {
"lt" => result.push('<'),
"gt" => result.push('>'),
"amp" => result.push('&'),
"apos" => result.push('\''),
"quot" => result.push('"'),
"nbsp" => result.push('\u{00A0}'),
s if s.starts_with('#') => {
let num_str = &s[1..];
let code_point = if num_str.starts_with('x') || num_str.starts_with('X') {
u32::from_str_radix(&num_str[1..], 16).ok()
} else {
num_str.parse::<u32>().ok()
};
if let Some(cp) = code_point {
if let Some(ch) = char::from_u32(cp) {
result.push(ch);
} else {
result.push('&');
result.push_str(&entity);
result.push(';');
}
} else {
result.push('&');
result.push_str(&entity);
result.push(';');
}
}
_ => {
result.push('&');
result.push_str(&entity);
result.push(';');
}
}
} else {
result.push('&');
result.push_str(&entity);
}
} else {
result.push(c);
}
}
result
}
pub use azul_core::xml::*;
use azul_core::{dom::Dom, impl_from, styled_dom::StyledDom, window::StringPairVec};
#[cfg(feature = "parser")]
use azul_css::parser2::CssParseError;
use azul_css::{css::Css, AzString, OptionString, U8Vec};
use xmlparser::Tokenizer;
#[cfg(feature = "xml")]
pub fn domxml_from_str(xml: &str, component_map: &mut XmlComponentMap) -> DomXml {
let error_css = Css::empty();
let parsed = match parse_xml_string(&xml) {
Ok(parsed) => parsed,
Err(e) => {
return DomXml {
parsed_dom: Dom::create_body()
.with_children(vec![Dom::create_text(format!("{}", e))].into())
.style(error_css.clone()),
};
}
};
let parsed_dom = match str_to_dom(parsed.as_ref(), component_map, None) {
Ok(o) => o,
Err(e) => {
return DomXml {
parsed_dom: Dom::create_body()
.with_children(vec![Dom::create_text(format!("{}", e))].into())
.style(error_css.clone()),
};
}
};
DomXml { parsed_dom }
}
#[cfg(all(feature = "std", feature = "xml"))]
pub fn domxml_from_file<I: AsRef<Path>>(
file_path: I,
component_map: &mut XmlComponentMap,
) -> DomXml {
use std::fs;
let error_css = Css::empty();
let xml = match fs::read_to_string(file_path.as_ref()) {
Ok(xml) => xml,
Err(e) => {
return DomXml {
parsed_dom: Dom::create_body()
.with_children(
vec![Dom::create_text(format!(
"Error reading: \"{}\": {}",
file_path.as_ref().to_string_lossy(),
e
))]
.into(),
)
.style(error_css.clone()),
};
}
};
domxml_from_str(&xml, component_map)
}
#[cfg(feature = "xml")]
pub fn parse_xml_string(xml: &str) -> Result<Vec<XmlNodeChild>, XmlError> {
use xmlparser::{ElementEnd::*, Token::*, Tokenizer};
use self::XmlParseError::*;
let mut root_node = XmlNode::default();
let mut xml = xml.trim();
if xml.starts_with("<?") {
let pos = xml.find("?>").ok_or(XmlError::MalformedHierarchy(
azul_core::xml::MalformedHierarchyError {
expected: "<?xml".into(),
got: "?>".into(),
},
))?;
xml = &xml[(pos + 2)..];
}
let mut xml = xml.trim();
if xml.len() > 9 && xml[..9].to_ascii_lowercase().starts_with("<!doctype") {
let pos = xml.find(">").ok_or(XmlError::MalformedHierarchy(
azul_core::xml::MalformedHierarchyError {
expected: "<!DOCTYPE".into(),
got: ">".into(),
},
))?;
xml = &xml[(pos + 1)..];
} else if xml.starts_with("<!--") {
if let Some(end) = xml.find("-->") {
xml = &xml[(end + 3)..];
xml = xml.trim();
}
}
let tokenizer = Tokenizer::from_fragment(xml, 0..xml.len());
let mut node_stack: Vec<*mut XmlNode> = vec![&mut root_node as *mut XmlNode];
const VOID_ELEMENTS: &[&str] = &[
"area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param",
"source", "track", "wbr",
];
const AUTO_CLOSE_RULES: &[(&str, &[&str])] = &[
("li", &["li"]),
("td", &["td", "th", "tr"]),
("th", &["td", "th", "tr"]),
("tr", &["tr"]),
(
"p",
&[
"address",
"article",
"aside",
"blockquote",
"div",
"dl",
"fieldset",
"footer",
"form",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"header",
"hr",
"main",
"nav",
"ol",
"p",
"pre",
"section",
"table",
"ul",
],
),
("option", &["option", "optgroup"]),
("optgroup", &["optgroup"]),
("dd", &["dd", "dt"]),
("dt", &["dd", "dt"]),
];
let mut last_was_void = false;
for token in tokenizer {
let token = token.map_err(|e| XmlError::ParserError(translate_xmlparser_error(e)))?;
match token {
ElementStart { local, .. } => {
let tag_name = local.to_string();
let is_void_element = VOID_ELEMENTS.contains(&tag_name.as_str());
if last_was_void {
node_stack.pop();
last_was_void = false;
}
if node_stack.len() > 1 {
let current_element = unsafe { &*node_stack[node_stack.len() - 1] };
let current_tag = current_element.node_type.as_str();
for (element, closes_on) in AUTO_CLOSE_RULES {
if current_tag == *element && closes_on.contains(&tag_name.as_str()) {
node_stack.pop();
break;
}
}
}
if let Some(¤t_parent_ptr) = node_stack.last() {
let current_parent = unsafe { &mut *current_parent_ptr };
current_parent.children.push(XmlNodeChild::Element(XmlNode {
node_type: tag_name.into(),
attributes: StringPairVec::new().into(),
children: Vec::new().into(),
}));
let children_len = current_parent.children.len();
if let Some(XmlNodeChild::Element(ref mut new_child)) = current_parent.children.as_mut().get_mut(children_len - 1) {
node_stack.push(new_child as *mut XmlNode);
}
last_was_void = is_void_element;
}
}
ElementEnd { end: Empty, .. } => {
if node_stack.len() > 1 {
node_stack.pop();
}
last_was_void = false;
}
ElementEnd {
end: Close(_, close_value),
..
} => {
if last_was_void {
node_stack.pop();
last_was_void = false;
}
let is_void_element = VOID_ELEMENTS.contains(&close_value.as_str());
if is_void_element {
continue;
}
let close_value_str = close_value.as_str();
let mut found_idx = None;
for i in (1..node_stack.len()).rev() {
let node = unsafe { &*node_stack[i] };
if node.node_type.as_str() == close_value_str {
found_idx = Some(i);
break;
}
}
if let Some(idx) = found_idx {
node_stack.truncate(idx);
}
last_was_void = false;
}
Attribute { local, value, .. } => {
if let Some(&last_ptr) = node_stack.last() {
let last = unsafe { &mut *last_ptr };
last.attributes.push(azul_core::window::AzStringPair {
key: local.to_string().into(),
value: decode_xml_entities(value.as_str()).into(),
});
}
}
Text { text } => {
if last_was_void {
node_stack.pop();
last_was_void = false;
}
let text_str = text.as_str();
if !text_str.is_empty() {
if let Some(¤t_parent_ptr) = node_stack.last() {
let current_parent = unsafe { &mut *current_parent_ptr };
let decoded_text = decode_xml_entities(text_str);
current_parent
.children
.push(XmlNodeChild::Text(decoded_text.into()));
}
}
}
_ => {}
}
}
if last_was_void {
node_stack.pop();
}
Ok(root_node.children.into())
}
#[cfg(feature = "xml")]
pub fn parse_xml(s: &str) -> Result<Xml, XmlError> {
Ok(Xml {
root: parse_xml_string(s)?.into(),
})
}
#[cfg(not(feature = "xml"))]
pub fn parse_xml(s: &str) -> Result<Xml, XmlError> {
Err(XmlError::NoParserAvailable)
}
#[cfg(feature = "xml")]
pub fn translate_roxmltree_expandedname<'a, 'b>(
e: roxmltree::ExpandedName<'a, 'b>,
) -> XmlQualifiedName {
let ns: Option<AzString> = e.namespace().map(|e| e.to_string().into());
XmlQualifiedName {
local_name: e.name().to_string().into(),
namespace: ns.into(),
}
}
#[cfg(feature = "xml")]
fn translate_roxmltree_attribute(e: roxmltree::Attribute) -> XmlQualifiedName {
XmlQualifiedName {
local_name: e.name().to_string().into(),
namespace: e.namespace().map(|e| e.to_string().into()).into(),
}
}
#[cfg(feature = "xml")]
fn translate_xmlparser_streamerror(e: xmlparser::StreamError) -> XmlStreamError {
match e {
xmlparser::StreamError::UnexpectedEndOfStream => XmlStreamError::UnexpectedEndOfStream,
xmlparser::StreamError::InvalidName => XmlStreamError::InvalidName,
xmlparser::StreamError::InvalidReference => XmlStreamError::InvalidReference,
xmlparser::StreamError::InvalidExternalID => XmlStreamError::InvalidExternalID,
xmlparser::StreamError::InvalidCommentData => XmlStreamError::InvalidCommentData,
xmlparser::StreamError::InvalidCommentEnd => XmlStreamError::InvalidCommentEnd,
xmlparser::StreamError::InvalidCharacterData => XmlStreamError::InvalidCharacterData,
xmlparser::StreamError::NonXmlChar(c, tp) => XmlStreamError::NonXmlChar(NonXmlCharError {
ch: c.into(),
pos: translate_xmlparser_textpos(tp),
}),
xmlparser::StreamError::InvalidChar(a, b, tp) => {
XmlStreamError::InvalidChar(InvalidCharError {
expected: a,
got: b,
pos: translate_xmlparser_textpos(tp),
})
}
xmlparser::StreamError::InvalidCharMultiple(a, b, tp) => {
XmlStreamError::InvalidCharMultiple(InvalidCharMultipleError {
expected: a,
got: b.to_vec().into(),
pos: translate_xmlparser_textpos(tp),
})
}
xmlparser::StreamError::InvalidQuote(a, tp) => {
XmlStreamError::InvalidQuote(InvalidQuoteError {
got: a.into(),
pos: translate_xmlparser_textpos(tp),
})
}
xmlparser::StreamError::InvalidSpace(a, tp) => {
XmlStreamError::InvalidSpace(InvalidSpaceError {
got: a.into(),
pos: translate_xmlparser_textpos(tp),
})
}
xmlparser::StreamError::InvalidString(a, tp) => {
XmlStreamError::InvalidString(InvalidStringError {
got: a.to_string().into(),
pos: translate_xmlparser_textpos(tp),
})
}
}
}
#[cfg(feature = "xml")]
fn translate_xmlparser_error(e: xmlparser::Error) -> XmlParseError {
match e {
xmlparser::Error::InvalidDeclaration(se, tp) => {
XmlParseError::InvalidDeclaration(XmlTextError {
stream_error: translate_xmlparser_streamerror(se),
pos: translate_xmlparser_textpos(tp),
})
}
xmlparser::Error::InvalidComment(se, tp) => XmlParseError::InvalidComment(XmlTextError {
stream_error: translate_xmlparser_streamerror(se),
pos: translate_xmlparser_textpos(tp),
}),
xmlparser::Error::InvalidPI(se, tp) => XmlParseError::InvalidPI(XmlTextError {
stream_error: translate_xmlparser_streamerror(se),
pos: translate_xmlparser_textpos(tp),
}),
xmlparser::Error::InvalidDoctype(se, tp) => XmlParseError::InvalidDoctype(XmlTextError {
stream_error: translate_xmlparser_streamerror(se),
pos: translate_xmlparser_textpos(tp),
}),
xmlparser::Error::InvalidEntity(se, tp) => XmlParseError::InvalidEntity(XmlTextError {
stream_error: translate_xmlparser_streamerror(se),
pos: translate_xmlparser_textpos(tp),
}),
xmlparser::Error::InvalidElement(se, tp) => XmlParseError::InvalidElement(XmlTextError {
stream_error: translate_xmlparser_streamerror(se),
pos: translate_xmlparser_textpos(tp),
}),
xmlparser::Error::InvalidAttribute(se, tp) => {
XmlParseError::InvalidAttribute(XmlTextError {
stream_error: translate_xmlparser_streamerror(se),
pos: translate_xmlparser_textpos(tp),
})
}
xmlparser::Error::InvalidCdata(se, tp) => XmlParseError::InvalidCdata(XmlTextError {
stream_error: translate_xmlparser_streamerror(se),
pos: translate_xmlparser_textpos(tp),
}),
xmlparser::Error::InvalidCharData(se, tp) => XmlParseError::InvalidCharData(XmlTextError {
stream_error: translate_xmlparser_streamerror(se),
pos: translate_xmlparser_textpos(tp),
}),
xmlparser::Error::UnknownToken(tp) => {
XmlParseError::UnknownToken(translate_xmlparser_textpos(tp))
}
}
}
#[cfg(feature = "xml")]
pub fn translate_roxmltree_error(e: roxmltree::Error) -> XmlError {
match e {
roxmltree::Error::InvalidXmlPrefixUri(s) => {
XmlError::InvalidXmlPrefixUri(translate_roxml_textpos(s))
}
roxmltree::Error::UnexpectedXmlUri(s) => {
XmlError::UnexpectedXmlUri(translate_roxml_textpos(s))
}
roxmltree::Error::UnexpectedXmlnsUri(s) => {
XmlError::UnexpectedXmlnsUri(translate_roxml_textpos(s))
}
roxmltree::Error::InvalidElementNamePrefix(s) => {
XmlError::InvalidElementNamePrefix(translate_roxml_textpos(s))
}
roxmltree::Error::DuplicatedNamespace(s, tp) => {
XmlError::DuplicatedNamespace(DuplicatedNamespaceError {
ns: s.into(),
pos: translate_roxml_textpos(tp),
})
}
roxmltree::Error::UnknownNamespace(s, tp) => {
XmlError::UnknownNamespace(UnknownNamespaceError {
ns: s.into(),
pos: translate_roxml_textpos(tp),
})
}
roxmltree::Error::UnexpectedCloseTag(expected, actual, pos) => {
XmlError::UnexpectedCloseTag(UnexpectedCloseTagError {
expected: expected.into(),
actual: actual.into(),
pos: translate_roxml_textpos(pos),
})
}
roxmltree::Error::UnexpectedEntityCloseTag(s) => {
XmlError::UnexpectedEntityCloseTag(translate_roxml_textpos(s))
}
roxmltree::Error::UnknownEntityReference(s, tp) => {
XmlError::UnknownEntityReference(UnknownEntityReferenceError {
entity: s.into(),
pos: translate_roxml_textpos(tp),
})
}
roxmltree::Error::MalformedEntityReference(s) => {
XmlError::MalformedEntityReference(translate_roxml_textpos(s))
}
roxmltree::Error::EntityReferenceLoop(s) => {
XmlError::EntityReferenceLoop(translate_roxml_textpos(s))
}
roxmltree::Error::InvalidAttributeValue(s) => {
XmlError::InvalidAttributeValue(translate_roxml_textpos(s))
}
roxmltree::Error::DuplicatedAttribute(s, tp) => {
XmlError::DuplicatedAttribute(DuplicatedAttributeError {
attribute: s.into(),
pos: translate_roxml_textpos(tp),
})
}
roxmltree::Error::NoRootNode => XmlError::NoRootNode,
roxmltree::Error::DtdDetected => XmlError::DtdDetected,
roxmltree::Error::UnclosedRootNode => XmlError::UnclosedRootNode,
roxmltree::Error::UnexpectedDeclaration(tp) => {
XmlError::UnexpectedDeclaration(translate_roxml_textpos(tp))
}
roxmltree::Error::NodesLimitReached => XmlError::NodesLimitReached,
roxmltree::Error::AttributesLimitReached => XmlError::AttributesLimitReached,
roxmltree::Error::NamespacesLimitReached => XmlError::NamespacesLimitReached,
roxmltree::Error::InvalidName(tp) => XmlError::InvalidName(translate_roxml_textpos(tp)),
roxmltree::Error::NonXmlChar(_, tp) => XmlError::NonXmlChar(translate_roxml_textpos(tp)),
roxmltree::Error::InvalidChar(_, _, tp) => {
XmlError::InvalidChar(translate_roxml_textpos(tp))
}
roxmltree::Error::InvalidChar2(_, _, tp) => {
XmlError::InvalidChar2(translate_roxml_textpos(tp))
}
roxmltree::Error::InvalidString(_, tp) => {
XmlError::InvalidString(translate_roxml_textpos(tp))
}
roxmltree::Error::InvalidExternalID(tp) => {
XmlError::InvalidExternalID(translate_roxml_textpos(tp))
}
roxmltree::Error::InvalidComment(tp) => {
XmlError::InvalidComment(translate_roxml_textpos(tp))
}
roxmltree::Error::InvalidCharacterData(tp) => {
XmlError::InvalidCharacterData(translate_roxml_textpos(tp))
}
roxmltree::Error::UnknownToken(tp) => XmlError::UnknownToken(translate_roxml_textpos(tp)),
roxmltree::Error::UnexpectedEndOfStream => XmlError::UnexpectedEndOfStream,
roxmltree::Error::EntityResolver(tp, s) => {
XmlError::UnknownEntityReference(UnknownEntityReferenceError {
entity: s.into(),
pos: translate_roxml_textpos(tp),
})
}
}
}
#[cfg(feature = "xml")]
#[inline(always)]
const fn translate_xmlparser_textpos(o: xmlparser::TextPos) -> XmlTextPos {
XmlTextPos {
row: o.row,
col: o.col,
}
}
#[cfg(feature = "xml")]
#[inline(always)]
const fn translate_roxml_textpos(o: roxmltree::TextPos) -> XmlTextPos {
XmlTextPos {
row: o.row,
col: o.col,
}
}
#[cfg(feature = "xml")]
pub trait DomXmlExt {
fn from_xml_string<S: AsRef<str>>(xml: S) -> StyledDom;
}
#[cfg(feature = "xml")]
impl DomXmlExt for Dom {
fn from_xml_string<S: AsRef<str>>(xml: S) -> StyledDom {
let mut component_map = XmlComponentMap::default();
let dom_xml = domxml_from_str(xml.as_ref(), &mut component_map);
dom_xml.parsed_dom
}
}