use crate::namespace::KeyMap;
use crate::build_common::make_span;
use crate::define_function::{FunctionDefSpec, FunctionPropSpec, ord_argument};
use crate::dom_tree::HtmlDomNode;
use crate::mathml_tree::MathDomNode;
use crate::options::Options;
use crate::parser::parse_node::{NodeType, ParseNode, ParseNodeHtml};
use crate::types::{ArgType, ErrorLocationProvider, ParseError, ParseErrorKind};
use crate::{ClassList, KatexContext, TrustContext, build_html, build_mathml};
const HTML_COMMANDS: &[&str] = &["\\htmlClass", "\\htmlId", "\\htmlStyle", "\\htmlData"];
pub fn define_html(ctx: &mut crate::KatexContext) {
ctx.define_function(FunctionDefSpec {
node_type: Some(NodeType::Html),
names: HTML_COMMANDS,
props: FunctionPropSpec {
num_args: 2,
arg_types: Some(vec![ArgType::Raw, ArgType::Original]),
allowed_in_text: true,
..Default::default()
},
handler: Some(|context, args, _opt_args| {
let value = match &args[0] {
ParseNode::Raw(raw) => raw.string.clone(),
_ => {
return Err(ParseError::new(
ParseErrorKind::ExpectedRawStringFirstArgument,
));
}
};
let body = args[1].clone();
context.parser.settings.report_nonstrict(
"htmlExtension",
"HTML extension is disabled on strict mode",
context
.token
.as_ref()
.map(|t| *t as &dyn ErrorLocationProvider),
)?;
let mut attributes = KeyMap::default();
let mut trust_context = match context.func_name {
"\\htmlClass" => {
attributes.insert("class".to_owned(), value.to_owned_string());
TrustContext {
command: "\\htmlClass".to_owned(),
class: Some(value.to_string()),
..Default::default()
}
}
"\\htmlId" => {
attributes.insert("id".to_owned(), value.to_owned_string());
TrustContext {
command: "\\htmlId".to_owned(),
id: Some(value.to_string()),
..Default::default()
}
}
"\\htmlStyle" => {
attributes.insert("style".to_owned(), value.to_owned_string());
TrustContext {
command: "\\htmlStyle".to_owned(),
style: Some(value.to_string()),
..Default::default()
}
}
"\\htmlData" => {
let data_parts: Vec<&str> = value.split(',').collect();
for part in data_parts {
let key_val: Vec<&str> = part.split('=').collect();
if key_val.len() != 2 {
return Err(ParseError::new(
ParseErrorKind::HtmlDataKeyValueParseError,
));
}
let key = format!("data-{}", key_val[0].trim());
let val = key_val[1].trim().to_owned();
attributes.insert(key, val);
}
TrustContext {
command: "\\htmlData".to_owned(),
attributes: Some(attributes.clone()),
..Default::default()
}
}
_ => {
return Err(ParseError::new(ParseErrorKind::UnrecognizedHtmlCommand));
}
};
if !context.parser.settings.is_trusted(&mut trust_context) {
return Ok(context
.parser
.format_unsupported_cmd(context.func_name)
.into());
}
Ok(ParseNode::Html(ParseNodeHtml {
mode: context.parser.mode,
loc: context.loc(),
attributes,
body: ord_argument(&body),
}))
}),
html_builder: Some(html_builder),
mathml_builder: Some(mathml_builder),
});
}
fn html_builder(
node: &ParseNode,
options: &Options,
ctx: &KatexContext,
) -> Result<HtmlDomNode, ParseError> {
let ParseNode::Html(html_node) = node else {
return Err(ParseError::new(ParseErrorKind::ExpectedNode {
node: NodeType::Html,
}));
};
let elements = build_html::build_expression(
ctx,
&html_node.body,
options,
build_html::GroupType::False,
(None, None),
)?;
let mut classes = ClassList::Static("enclosing");
if let Some(class) = html_node.attributes.get("class") {
classes.extend(class.split_whitespace().map(|s| s.to_owned().into()));
}
let mut span = make_span(classes, elements, Some(options), None);
for (attr, value) in &html_node.attributes {
if attr == "class" {
continue;
}
span.attributes.insert(attr.clone(), value.clone());
}
Ok(span.into())
}
fn mathml_builder(
node: &ParseNode,
options: &Options,
ctx: &KatexContext,
) -> Result<MathDomNode, ParseError> {
let ParseNode::Html(html_node) = node else {
return Err(ParseError::new(ParseErrorKind::ExpectedNode {
node: NodeType::Html,
}));
};
let base_group = build_mathml::build_expression_row(ctx, &html_node.body, options, None)?;
Ok(base_group)
}