use anyhow::Result;
use rusty_v8 as v8;
fn is_supported_content_type(content_type: &str) -> bool {
matches!(
content_type,
"text/html" | "text/xml" | "application/xml" | "application/xhtml+xml" | "image/svg+xml"
)
}
fn throw_type_error(scope: &mut v8::PinScope, message: &str) {
let error_message = v8::String::new(scope, message).unwrap();
let error = v8::Exception::type_error(scope, error_message);
scope.throw_exception(error);
}
pub fn setup_dom_parser_api(
scope: &mut v8::ContextScope<v8::HandleScope>,
context: &v8::Local<v8::Context>,
) -> Result<()> {
let global = context.global(scope);
let parse_from_string_key = v8::String::new(scope, "parseFromString").unwrap();
let parse_from_string_fn_template = v8::FunctionTemplate::new(
scope,
|scope: &mut v8::PinScope,
args: v8::FunctionCallbackArguments,
mut retval: v8::ReturnValue| {
let string_arg = args.get(0);
let content_type_arg = args.get(1);
let string_str = if string_arg.is_string() {
string_arg
.to_string(scope)
.map(|s| s.to_rust_string_lossy(scope))
.unwrap_or_default()
} else {
String::new()
};
if args.length() < 2 || !content_type_arg.is_string() {
throw_type_error(scope, "DOMParser.parseFromString: contentType is required");
return;
}
let content_type_str = content_type_arg
.to_string(scope)
.map(|s| s.to_rust_string_lossy(scope))
.unwrap_or_default();
if !is_supported_content_type(&content_type_str) {
throw_type_error(scope, "DOMParser.parseFromString: unsupported contentType");
return;
}
let document_obj: v8::Local<v8::Object> = v8::Object::new(scope);
let document_key = v8::String::new(scope, "document").unwrap();
let document_content = format!(
"<Document contentType='{}'>\n{}\n</Document>",
content_type_str,
escape_html(&string_str)
);
let document_content_str = v8::String::new(scope, &document_content).unwrap();
document_obj.set(scope, document_key.into(), document_content_str.into());
if content_type_str.contains("html") {
let body_key = v8::String::new(scope, "body").unwrap();
let body_content = format!("<Body>\n{}\n</Body>", escape_html(&string_str));
let body_content_str = v8::String::new(scope, &body_content).unwrap();
document_obj.set(scope, body_key.into(), body_content_str.into());
}
let children_key = v8::String::new(scope, "children").unwrap();
let children_array: v8::Local<v8::Array> = v8::Array::new(scope, 0);
document_obj.set(scope, children_key.into(), children_array.into());
if content_type_str.contains("html") {
let body_key = v8::String::new(scope, "body").unwrap();
let body_obj: v8::Local<v8::Object> = v8::Object::new(scope);
let body_inner_html_key = v8::String::new(scope, "innerHTML").unwrap();
let inner_html_content = escape_html(&string_str);
let inner_html_value = v8::String::new(scope, &inner_html_content).unwrap();
body_obj.set(scope, body_inner_html_key.into(), inner_html_value.into());
document_obj.set(scope, body_key.into(), body_obj.into());
}
let url_key = v8::String::new(scope, "URL").unwrap();
let url_value = v8::String::new(scope, "data:text/html,domparser").unwrap();
document_obj.set(scope, url_key.into(), url_value.into());
retval.set(document_obj.into());
},
);
let parse_from_string_fn = parse_from_string_fn_template.get_function(scope).unwrap();
let dom_parser_template = v8::FunctionTemplate::new(
scope,
|_scope: &mut v8::PinScope,
_args: v8::FunctionCallbackArguments,
mut retval: v8::ReturnValue| {
let dom_parser_obj: v8::Local<v8::Object> = v8::Object::new(_scope);
retval.set(dom_parser_obj.into());
},
);
let dom_parser_constructor: v8::Local<v8::Function> =
dom_parser_template.get_function(scope).unwrap();
dom_parser_constructor.set(
scope,
parse_from_string_key.clone().into(),
parse_from_string_fn.into(),
);
let prototype_key = v8::String::new(scope, "prototype").unwrap();
let prototype_obj: v8::Local<v8::Object> = v8::Object::new(scope);
prototype_obj.set(
scope,
parse_from_string_key.clone().into(),
parse_from_string_fn.into(),
);
dom_parser_constructor.set(scope, prototype_key.into(), prototype_obj.into());
let dom_parser_key: v8::Local<v8::String> = v8::String::new(scope, "DOMParser").unwrap();
global.set(scope, dom_parser_key.into(), dom_parser_constructor.into());
Ok(())
}
fn escape_html(input: &str) -> String {
input
.replace('&', "&")
.replace('<', "<")
.replace('>', ">")
.replace('"', """)
.replace('\'', "'")
}