use crate::namespace::KeyMap;
use crate::build_common::make_span;
use crate::define_function::{FunctionContext, 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};
use crate::{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: FunctionContext, args, _opt_args| {
let value = match &args[0] {
ParseNode::Raw(raw) => raw.string.clone(),
_ => return Err(ParseError::new("First argument must be raw string")),
};
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.as_str() {
"\\htmlClass" => {
attributes.insert("class".to_owned(), value.clone());
TrustContext {
command: "\\htmlClass".to_owned(),
class: Some(value),
..Default::default()
}
}
"\\htmlId" => {
attributes.insert("id".to_owned(), value.clone());
TrustContext {
command: "\\htmlId".to_owned(),
id: Some(value),
..Default::default()
}
}
"\\htmlStyle" => {
attributes.insert("style".to_owned(), value.clone());
TrustContext {
command: "\\htmlStyle".to_owned(),
style: Some(value),
..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("Error parsing key-value for \\htmlData"));
}
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("Unrecognized html command"));
}
};
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("Expected Html node"));
};
let elements = build_html::build_expression(
ctx,
&html_node.body,
options,
build_html::GroupType::False,
(None, None),
)?;
let mut classes = vec!["enclosing".to_owned()];
if let Some(class) = html_node.attributes.get("class") {
classes.extend(class.split_whitespace().map(ToString::to_string));
}
let mut span = make_span(classes, elements, Some(options), None);
span.attributes.extend(html_node.attributes.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("Expected Html node"));
};
let base_group = build_mathml::build_expression_row(ctx, &html_node.body, options, None)?;
Ok(base_group)
}