use crate::abi::allocator::xmlFreeImpl;
use crate::abi::exports_xml2::*;
use crate::abi::structs::*;
use crate::abi::types::xmlElementType::*;
use crate::abi::types::xmlXPathObjectType;
use crate::abi::types::*;
use crate::xml::tree::*;
use std::ffi::c_void;
use std::os::raw::{c_char, c_int};
use std::ptr;
use super::compiler::{get_element_name, get_element_ns, is_xslt_element, is_xslt_namespace};
pub const XSLT_MAX_DEPTH: c_int = 3000;
pub const XSLT_MAX_INSERT_DEPTH: c_int = 50;
pub const XSLT_STATE_OK: c_int = 0;
pub const XSLT_STATE_ERROR: c_int = 1;
pub const XSLT_STATE_STOPPED: c_int = 2;
pub(crate) static XSLT_COMP_TYPE_PARAM: c_int = 19;
pub(crate) static XSLT_COMP_TYPE_VARIABLE: c_int = 20;
pub(crate) static XSLT_COMP_TYPE_WITHPARAM: c_int = 24;
#[no_mangle]
pub static mut xsltMaxDepth: c_int = 3000;
#[no_mangle]
pub static mut xsltMaxVars: c_int = 15000;
static mut XSLT_XINCLUDE_DEFAULT: c_int = 0;
#[no_mangle]
pub unsafe extern "C" fn xsltSetXIncludeDefault(xinclude: c_int) {
unsafe { XSLT_XINCLUDE_DEFAULT = if xinclude != 0 { 1 } else { 0 } };
}
#[no_mangle]
pub unsafe extern "C" fn xsltGetXIncludeDefault() -> c_int {
unsafe { XSLT_XINCLUDE_DEFAULT }
}
#[no_mangle]
pub unsafe extern "C" fn xsltNewTransformContext(
style: *mut _xsltStylesheet,
doc: *mut _xmlDoc,
) -> *mut _xsltTransformContext {
if style.is_null() {
return ptr::null_mut();
}
let ctxt = libc::calloc(1, core::mem::size_of::<_xsltTransformContext>())
as *mut _xsltTransformContext;
if ctxt.is_null() {
return ptr::null_mut();
}
(*ctxt).style = style;
(*ctxt).state = XSLT_STATE_OK;
(*ctxt).parserOptions = 2 | 4 | 8 | 16384;
(*ctxt).maxTemplateDepth = unsafe { xsltMaxDepth };
(*ctxt).maxTemplateVars = unsafe { xsltMaxVars };
let xpath_ctxt = xmlXPathNewContext(doc);
if !xpath_ctxt.is_null() {
(*ctxt).xpathCtxt = xpath_ctxt;
let internal = (*xpath_ctxt).extra as *mut crate::xml::xpath::context::XPathContext;
if !internal.is_null() {
(*internal).func_lookup_data = ctxt as *mut c_void;
}
crate::abi::exports_xslt_functions::xsltRegisterAllFunctions(xpath_ctxt);
}
(*ctxt).sec = crate::xslt::security::xsltGetDefaultSecurityPrefs();
if !doc.is_null() {
let docu = libc::calloc(1, core::mem::size_of::<_xsltDocument>()) as *mut _xsltDocument;
if !docu.is_null() {
(*docu).main = 1;
(*docu).doc = doc;
(*ctxt).document = docu;
(*ctxt).docList = docu;
}
(*ctxt).initialContextDoc = doc;
(*ctxt).initialContextNode = doc as *mut _xmlNode;
}
ctxt
}
#[no_mangle]
pub unsafe extern "C" fn xsltFreeTransformContext(ctxt: *mut _xsltTransformContext) {
if ctxt.is_null() {
return;
}
if !(*ctxt).xpathCtxt.is_null() {
xmlXPathFreeContext((*ctxt).xpathCtxt);
}
crate::xslt::variables::xsltFreeGlobalVariables(ctxt);
crate::xslt::keys::xsltFreeKeyTables(ctxt);
crate::xslt::documents::xsltFreeDocCache(ctxt);
crate::xslt::extensions::xsltFreeExts(ctxt);
if !(*ctxt).varsTab.is_null() {
libc::free((*ctxt).varsTab as *mut libc::c_void);
}
if !(*ctxt).templTab.is_null() {
libc::free((*ctxt).templTab as *mut libc::c_void);
}
if !(*ctxt).document.is_null() {
let docu = (*ctxt).document;
(*docu).doc = ptr::null_mut();
libc::free(docu as *mut libc::c_void);
(*ctxt).document = ptr::null_mut();
}
libc::free(ctxt as *mut libc::c_void);
}
#[no_mangle]
pub unsafe extern "C" fn xsltApplyStylesheet(
style: *mut _xsltStylesheet,
doc: *mut _xmlDoc,
params: *mut *const c_char,
) -> *mut _xmlDoc {
xsltApplyStylesheetUser(
style,
doc,
params,
ptr::null(),
ptr::null_mut(),
ptr::null_mut(),
)
}
#[no_mangle]
pub unsafe extern "C" fn xsltApplyStylesheetUser(
style: *mut _xsltStylesheet,
doc: *mut _xmlDoc,
params: *mut *const c_char,
_output: *const c_char,
_profile: *mut c_void,
userCtxt: *mut _xsltTransformContext,
) -> *mut _xmlDoc {
if style.is_null() || doc.is_null() {
return ptr::null_mut();
}
let mut ctxt = userCtxt;
let mut own_ctxt = false;
if ctxt.is_null() {
ctxt = xsltNewTransformContext(style, doc);
if ctxt.is_null() {
return ptr::null_mut();
}
own_ctxt = true;
}
if !params.is_null() {
crate::xslt::parameters::xsltParseStylesheetParams(style, params);
}
crate::xslt::variables::xsltInitGlobalVariables(ctxt);
crate::xslt::keys::xsltInitKeys(ctxt, style);
crate::xslt::whitespace::xsltApplyStripSpaces(style, doc);
let result = libc::calloc(1, core::mem::size_of::<_xmlDoc>()) as *mut _xmlDoc;
if result.is_null() {
if own_ctxt {
xsltFreeTransformContext(ctxt);
}
return ptr::null_mut();
}
(*result).type_ = XML_DOCUMENT_NODE as c_int;
(*result).standalone = -1;
(*result).compression = -1;
(*result).charset = crate::abi::types::xmlCharEncoding::XML_CHAR_ENCODING_UTF8 as c_int;
(*result).properties = crate::abi::types::xmlDocProperties::XML_DOC_USERBUILT as c_int;
(*result).version = crate::xml::string::xml_strdup(c"1.0".as_ptr() as *const xmlChar);
(*result).doc = result;
if !(*style).encoding.is_null() {
(*result).encoding = crate::xml::string::xml_strdup((*style).encoding);
}
if !(*style).version.is_null() {
let v = crate::xml::string::xml_strdup((*style).version);
if !v.is_null() {
if !(*result).version.is_null() {
libc::free((*result).version as *mut libc::c_void);
}
(*result).version = v;
}
}
(*ctxt).initialContextDoc = doc;
(*ctxt).initialContextNode = doc as *mut _xmlNode;
(*ctxt).output = result;
(*ctxt).insert = result as *mut _xmlNode;
(*ctxt).node = doc as *mut _xmlNode;
if !(*ctxt).xpathCtxt.is_null() {
(*(*ctxt).xpathCtxt).node = doc as *mut _xmlNode;
(*(*ctxt).xpathCtxt).doc = doc;
(*(*ctxt).xpathCtxt).contextSize = 1;
(*(*ctxt).xpathCtxt).proximityPosition = 1;
}
let result_code = apply_templates_to_node(ctxt, doc as *mut _xmlNode, ptr::null());
let _ = result_code;
let final_result = if (*ctxt).state == XSLT_STATE_OK {
result
} else {
free_doc(result);
ptr::null_mut()
};
if own_ctxt {
(*ctxt).output = ptr::null_mut();
xsltFreeTransformContext(ctxt);
}
final_result
}
#[no_mangle]
pub unsafe extern "C" fn xsltApplyStylesheetStacked(
style: *mut _xsltStylesheet,
doc: *mut _xmlDoc,
params: *mut *const c_char,
_stack: *mut c_void,
) -> *mut _xmlDoc {
xsltApplyStylesheet(style, doc, params)
}
#[no_mangle]
pub unsafe extern "C" fn xsltFreeTransformResult(result: *mut _xmlDoc) {
if !result.is_null() {
free_doc(result);
}
}
#[no_mangle]
pub unsafe extern "C" fn xsltRunStylesheetUser(
style: *mut _xsltStylesheet,
doc: *mut _xmlDoc,
params: *mut *const c_char,
output: *const c_char,
SAX: *mut crate::abi::structs::_xmlSAXHandler,
IObuf: *mut crate::abi::structs::_xmlOutputBuffer,
profile: *mut c_void,
userCtxt: *mut _xsltTransformContext,
) -> c_int {
if output.is_null() && SAX.is_null() && IObuf.is_null() {
return -1;
}
if !SAX.is_null() && !IObuf.is_null() {
return -1;
}
if !SAX.is_null() {
return -1;
}
let tmp = xsltApplyStylesheetUser(style, doc, params, output, profile, userCtxt);
if tmp.is_null() {
eprintln!("xsltRunStylesheet : run failed");
return -1;
}
let ret = if !IObuf.is_null() {
let mut txt: *mut xmlChar = ptr::null_mut();
let mut len: c_int = 0;
let r = crate::xslt::serialization::xsltSaveResultToString(&mut txt, &mut len, tmp, style);
if r != 0 || txt.is_null() {
-1
} else {
let written = crate::xml::io::output_buffer_write(IObuf, len, txt as *const c_char);
crate::abi::allocator::xmlFreeImpl(txt as *mut c_void);
written
}
} else {
crate::xslt::serialization::xsltSaveResultToFilename(output, tmp, style, 0)
};
free_doc(tmp);
ret
}
#[no_mangle]
pub unsafe extern "C" fn xsltRunStylesheet(
style: *mut _xsltStylesheet,
doc: *mut _xmlDoc,
params: *mut *const c_char,
output: *const c_char,
SAX: *mut crate::abi::structs::_xmlSAXHandler,
IObuf: *mut crate::abi::structs::_xmlOutputBuffer,
) -> c_int {
xsltRunStylesheetUser(
style,
doc,
params,
output,
SAX,
IObuf,
ptr::null_mut(),
ptr::null_mut(),
)
}
#[allow(dead_code)]
pub(crate) unsafe fn apply_root_template(
ctxt: *mut _xsltTransformContext,
doc: *mut _xmlDoc,
) -> c_int {
let style = (*ctxt).style;
if style.is_null() {
return -1;
}
let doc_node = doc as *mut _xmlNode;
(*ctxt).node = doc_node;
if !(*ctxt).xpathCtxt.is_null() {
(*(*ctxt).xpathCtxt).contextSize = 1;
(*(*ctxt).xpathCtxt).proximityPosition = 1;
}
let templ = crate::xslt::templates::xsltFindTemplate(style, doc_node, ptr::null());
if templ.is_null() {
return 0;
}
let mut vars_base = (*ctxt).varsNr;
let _ = &mut vars_base;
(*ctxt).templ = templ;
execute_content(ctxt, (*templ).content);
(*ctxt).templ = ptr::null_mut();
0
}
unsafe fn template_stack_push(ctxt: *mut _xsltTransformContext, templ: *mut _xsltTemplate) {
unsafe {
let nr = (*ctxt).templNr;
if nr >= (*ctxt).templMax {
let new_max = if (*ctxt).templMax == 0 {
32
} else {
(*ctxt).templMax * 2
};
let new_tab = libc::realloc(
(*ctxt).templTab as *mut libc::c_void,
(new_max as usize) * core::mem::size_of::<*mut _xsltTemplate>(),
) as *mut *mut _xsltTemplate;
if new_tab.is_null() {
return;
}
(*ctxt).templTab = new_tab;
(*ctxt).templMax = new_max;
}
*(*ctxt).templTab.add(nr as usize) = templ;
(*ctxt).templNr = nr + 1;
}
}
unsafe fn template_stack_pop(ctxt: *mut _xsltTransformContext) {
unsafe {
if (*ctxt).templNr > 0 {
(*ctxt).templNr -= 1;
*(*ctxt).templTab.add((*ctxt).templNr as usize) = ptr::null_mut();
}
}
}
unsafe fn report_template_recursion(
ctxt: *mut _xsltTransformContext,
node: *mut _xmlNode,
depth_guard: bool,
) {
unsafe {
let limit = if depth_guard {
(*ctxt).maxTemplateDepth
} else {
(*ctxt).maxTemplateVars
};
let text = if depth_guard {
format!(
"xsltApplySequenceConstructor: A potential infinite template recursion was detected.\nYou can adjust $maxTemplateDepth in order to raise the maximum number of nested template calls and variables/params (currently set to {}).\n",
limit
)
} else {
format!(
"xsltApplyXSLTTemplate: A potential infinite template recursion was detected.\nYou can adjust $maxTemplateVars in order to raise the maximum number of variables/params (currently set to {}).\n",
limit
)
};
let Ok(msg) = std::ffi::CString::new(text) else {
return;
};
crate::xslt::errors::xsltTransformError(ctxt, ptr::null_mut(), node, msg.as_ptr());
crate::abi::exports_xslt_exec::xsltDebug(
ctxt,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
);
(*ctxt).state = XSLT_STATE_STOPPED;
}
}
pub(crate) unsafe fn apply_templates_to_node(
ctxt: *mut _xsltTransformContext,
node: *mut _xmlNode,
mode: *const xmlChar,
) -> c_int {
let style = (*ctxt).style;
if style.is_null() {
return -1;
}
let templ = crate::xslt::templates::xsltFindTemplate(style, node, mode);
if templ.is_null() {
let typ = (*node).type_;
if typ == XML_TEXT_NODE as c_int
|| typ == XML_CDATA_SECTION_NODE as c_int
|| typ == XML_ATTRIBUTE_NODE as c_int
{
let content = node_get_content(node);
if !content.is_null() {
append_text_node(ctxt, content);
libc::free(content as *mut libc::c_void);
}
} else if typ == XML_ELEMENT_NODE as c_int
|| typ == XML_DOCUMENT_NODE as c_int
|| typ == XML_HTML_DOCUMENT_NODE as c_int
{
apply_templates_to_children(ctxt, node, mode);
}
return 0;
}
template_stack_push(ctxt, templ);
(*ctxt).depth += 1;
if (*ctxt).depth > (*ctxt).maxTemplateDepth {
report_template_recursion(ctxt, node, true);
(*ctxt).depth -= 1;
template_stack_pop(ctxt);
return 0;
}
if (*ctxt).varsNr >= (*ctxt).maxTemplateVars {
let err_node = if (*templ).content.is_null() {
node
} else {
(*templ).content
};
report_template_recursion(ctxt, err_node, false);
(*ctxt).depth -= 1;
template_stack_pop(ctxt);
return 0;
}
(*ctxt).templ = templ;
(*ctxt).node = node;
if !(*ctxt).xpathCtxt.is_null() {
(*(*ctxt).xpathCtxt).contextSize = 1;
(*(*ctxt).xpathCtxt).proximityPosition = 1;
}
execute_content(ctxt, (*templ).content);
(*ctxt).depth -= 1;
template_stack_pop(ctxt);
(*ctxt).templ = ptr::null_mut();
0
}
pub(crate) unsafe fn apply_templates_to_children(
ctxt: *mut _xsltTransformContext,
node: *mut _xmlNode,
mode: *const xmlChar,
) -> c_int {
let mut children: Vec<*mut _xmlNode> = Vec::new();
let mut child = (*node).children;
while !child.is_null() {
children.push(child);
child = (*child).next;
}
let size = children.len();
if !(*ctxt).xpathCtxt.is_null() {
(*(*ctxt).xpathCtxt).contextSize = size as c_int;
}
for (i, node) in children.iter().enumerate() {
(*ctxt).node = *node;
if !(*ctxt).xpathCtxt.is_null() {
(*(*ctxt).xpathCtxt).proximityPosition = (i + 1) as c_int;
}
apply_templates_to_node(ctxt, *node, mode);
}
0
}
pub unsafe fn execute_content(ctxt: *mut _xsltTransformContext, content: *mut _xmlNode) -> c_int {
let mut cur = content;
while !cur.is_null() {
if (*ctxt).state == XSLT_STATE_ERROR || (*ctxt).state == XSLT_STATE_STOPPED {
return -1;
}
let next = (*cur).next;
xsltProcessInstruction(ctxt, cur);
cur = next;
}
0
}
pub unsafe fn xsltProcessInstruction(
ctxt: *mut _xsltTransformContext,
inst: *mut _xmlNode,
) -> c_int {
if ctxt.is_null() || inst.is_null() {
return -1;
}
let typ = (*inst).type_;
(*ctxt).inst = inst;
match typ {
t if t == XML_TEXT_NODE as c_int || t == XML_CDATA_SECTION_NODE as c_int => {
if !(*inst).content.is_null() {
append_text_node(ctxt, (*inst).content);
}
0
}
t if t == XML_COMMENT_NODE as c_int => {
if !(*inst).content.is_null() {
append_comment_node(ctxt, (*inst).content);
}
0
}
t if t == XML_PI_NODE as c_int => {
if !(*inst).name.is_null() {
let content = if (*inst).content.is_null() {
ptr::null()
} else {
(*inst).content
};
append_pi_node(ctxt, (*inst).name, content);
}
0
}
t if t == XML_ELEMENT_NODE as c_int => {
if is_xslt_namespace(inst) {
process_xslt_instruction(ctxt, inst);
} else {
let ns = get_element_ns(inst);
if let Some(ref ns_uri) = ns {
if ns_uri == crate::exslt::EXSLT_NS_COMMON
&& get_element_name(inst).as_deref() == Some("document")
{
process_exsl_document(ctxt, inst);
return 0;
}
let ns_c = str_to_cstr(ns_uri);
let ns_ptr = ns_c.as_ptr() as *const xmlChar;
if !(*inst).name.is_null() {
let ctx_h =
crate::xslt::extensions::xsltFindExtElement(ctxt, (*inst).name, ns_ptr);
if !ctx_h.is_null() {
let func: crate::abi::exports_xslt_compile::xsltTransformFunction =
core::mem::transmute(ctx_h);
func(ctxt, (*ctxt).node, inst, ptr::null_mut());
return 0;
}
if let Some(handler) =
crate::abi::exports_xslt_ext::xsltExtModuleElementLookup(
(*inst).name,
ns_ptr,
)
{
handler(ctxt, (*ctxt).node, inst, ptr::null_mut());
return 0;
}
}
}
process_literal_element(ctxt, inst);
}
0
}
_ => 0,
}
}
pub(crate) unsafe fn process_xslt_instruction(
ctxt: *mut _xsltTransformContext,
inst: *mut _xmlNode,
) -> c_int {
let name = get_element_name(inst);
match name.as_deref() {
Some("apply-templates") => {
process_apply_templates(ctxt, inst);
}
Some("call-template") => {
process_call_template(ctxt, inst);
}
Some("apply-imports") => {
process_apply_imports(ctxt, inst);
}
Some("for-each") => {
process_for_each(ctxt, inst);
}
Some("value-of") => {
process_value_of(ctxt, inst);
}
Some("copy-of") => {
process_copy_of(ctxt, inst);
}
Some("copy") => {
process_copy(ctxt, inst);
}
Some("element") => {
process_element(ctxt, inst);
}
Some("attribute") => {
process_attribute(ctxt, inst);
}
Some("text") => {
process_text(ctxt, inst);
}
Some("comment") => {
process_comment(ctxt, inst);
}
Some("processing-instruction") => {
process_pi(ctxt, inst);
}
Some("number") => {
process_number(ctxt, inst);
}
Some("choose") => {
process_choose(ctxt, inst);
}
Some("when") | Some("otherwise") => {
}
Some("if") => {
process_if(ctxt, inst);
}
Some("variable") => {
process_variable(ctxt, inst);
}
Some("param") => {
process_param(ctxt, inst);
}
Some("with-param") => {
}
Some("sort") => {
}
Some("message") => {
process_message(ctxt, inst);
}
Some("fallback") => {
}
Some("output")
| Some("decimal-format")
| Some("namespace-alias")
| Some("attribute-set")
| Some("key")
| Some("strip-space")
| Some("preserve-space")
| Some("import")
| Some("include")
| Some("stylesheet")
| Some("transform") => {
}
_ => {
let ns = get_element_ns(inst);
if let Some(ns_uri) = ns {
if ns_uri == crate::exslt::EXSLT_NS_COMMON
&& get_element_name(inst).as_deref() == Some("document")
{
process_exsl_document(ctxt, inst);
return 0;
}
let name_ptr = (*inst).name;
let ns_cstr = str_to_cstr(&ns_uri);
let found = crate::xslt::extensions::xsltFindExtElement(
ctxt,
name_ptr,
ns_cstr.as_ptr() as *const xmlChar,
);
if !found.is_null() {
let func: crate::abi::exports_xslt_compile::xsltTransformFunction =
core::mem::transmute(found);
func(ctxt, (*ctxt).node, inst, ptr::null_mut());
return 0;
}
}
}
}
0
}
fn str_to_cstr(s: &str) -> Vec<u8> {
let mut v = s.as_bytes().to_vec();
v.push(0);
v
}
unsafe fn process_exsl_document(ctxt: *mut _xsltTransformContext, inst: *mut _xmlNode) {
let href = get_prop(inst, c"href".as_ptr() as *const xmlChar);
if href.is_null() {
crate::xslt::errors::xsltTransformError(
ctxt,
(*ctxt).style,
inst,
c"exsl:document: missing href attribute".as_ptr() as *const c_char,
);
return;
}
let frag = libc::calloc(1, core::mem::size_of::<_xmlDoc>()) as *mut _xmlDoc;
if frag.is_null() {
libc::free(href as *mut libc::c_void);
return;
}
(*frag).type_ = XML_DOCUMENT_NODE as c_int;
(*frag).doc = frag;
let saved_insert = (*ctxt).insert;
let saved_output = (*ctxt).output;
(*ctxt).insert = frag as *mut _xmlNode;
(*ctxt).output = frag;
execute_content(ctxt, (*inst).children);
(*ctxt).insert = saved_insert;
(*ctxt).output = saved_output;
let fname = crate::abi::versioning::c_str_to_bytes(href as *const c_char);
if let Some(name) = fname {
let path = String::from_utf8_lossy(name);
let cpath = str_to_cstr(&path);
let out = libc::fopen(
cpath.as_ptr() as *const c_char,
c"wb".as_ptr() as *const c_char,
);
if !out.is_null() {
let buf = crate::xml::io::buf_create(-1);
if !buf.is_null() {
crate::xml::tree::doc_dump(buf, frag);
let content = crate::xml::io::buf_content(buf);
let len = crate::xml::io::buf_length(buf);
if !content.is_null() && len > 0 {
libc::fwrite(content as *const libc::c_void, 1, len as usize, out);
}
crate::xml::io::buf_free(buf);
}
libc::fclose(out);
}
}
libc::free(href as *mut libc::c_void);
free_doc(frag);
}
pub(crate) unsafe fn eval_xpath(
ctxt: *mut _xsltTransformContext,
expr: *const xmlChar,
) -> *mut _xmlXPathObject {
if ctxt.is_null() || expr.is_null() {
return ptr::null_mut();
}
let xpath_ctxt = (*ctxt).xpathCtxt;
if xpath_ctxt.is_null() {
return ptr::null_mut();
}
(*xpath_ctxt).node = (*ctxt).node;
if !(*ctxt).document.is_null() {
(*xpath_ctxt).doc = (*(*ctxt).document).doc;
}
let internal = (*xpath_ctxt).extra as *mut crate::xml::xpath::context::XPathContext;
if !internal.is_null() {
(*internal).context_node = (*ctxt).node;
if !(*ctxt).document.is_null() {
(*internal).document = (*(*ctxt).document).doc;
}
(*internal).context_size = (*xpath_ctxt).contextSize;
(*internal).context_position = (*xpath_ctxt).proximityPosition;
(*internal).proximity_position = (*xpath_ctxt).proximityPosition;
if !(*ctxt).inst.is_null() {
register_in_scope_ns(internal, (*ctxt).inst);
}
}
xmlXPathEvalExpression(expr, xpath_ctxt)
}
pub(crate) unsafe fn register_in_scope_ns(
internal: *mut crate::xml::xpath::context::XPathContext,
node: *mut _xmlNode,
) {
unsafe {
let mut cur = node;
let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();
while !cur.is_null() {
let mut ns = (*cur).nsDef;
while !ns.is_null() {
let prefix = if (*ns).prefix.is_null() {
String::new()
} else {
let l = libc::strlen((*ns).prefix as *const libc::c_char);
String::from_utf8_lossy(core::slice::from_raw_parts((*ns).prefix, l))
.into_owned()
};
if !seen.contains(&prefix) && !(*ns).href.is_null() {
let l = libc::strlen((*ns).href as *const libc::c_char);
let href = String::from_utf8_lossy(core::slice::from_raw_parts((*ns).href, l))
.into_owned();
(*internal).register_namespace(&prefix, &href);
seen.insert(prefix);
}
ns = (*ns).next;
}
cur = (*cur).parent;
}
}
}
pub(crate) unsafe fn report_xpath_eval_failure(
ctxt: *mut _xsltTransformContext,
inst: *mut _xmlNode,
elem_name: &str,
) {
unsafe {
let _ = elem_name;
let style = if ctxt.is_null() {
ptr::null_mut()
} else {
(*ctxt).style
};
crate::xslt::errors::xsltTransformError(
ctxt,
style,
inst,
c"XPath evaluation returned no result.\n".as_ptr() as *const c_char,
);
(*ctxt).state = XSLT_STATE_STOPPED;
}
}
pub(crate) unsafe fn process_apply_templates(
ctxt: *mut _xsltTransformContext,
inst: *mut _xmlNode,
) {
let mode = get_prop(inst, c"mode".as_ptr() as *const xmlChar);
let select = get_prop(inst, c"select".as_ptr() as *const xmlChar);
let params = collect_with_params(ctxt, inst);
let obj = if !select.is_null() {
eval_xpath(ctxt, select)
} else {
build_child_node_set(ctxt)
};
if !select.is_null() {
libc::free(select as *mut libc::c_void);
}
if obj.is_null() {
if !mode.is_null() {
libc::free(mode as *mut libc::c_void);
}
report_xpath_eval_failure(ctxt, inst, "apply-templates");
return;
}
let nodes = if (*obj).type_ == xmlXPathObjectType::XPATH_NODESET as c_int {
(*obj).nodesetval as *mut _xmlNodeSet
} else {
ptr::null_mut()
};
if !nodes.is_null() && (*nodes).nodeNr > 0 {
let sort = find_sort_children(ctxt, inst);
let mut node_ptrs: Vec<*mut _xmlNode> = Vec::new();
let mut i = 0;
while i < (*nodes).nodeNr {
let n = *(*nodes).nodeTab.offset(i as isize);
if !n.is_null() {
node_ptrs.push(n);
}
i += 1;
}
if !sort.is_null() {
let sorted = libc::calloc(1, core::mem::size_of::<_xmlNodeSet>()) as *mut _xmlNodeSet;
if !sorted.is_null() {
(*sorted).nodeNr = node_ptrs.len() as c_int;
(*sorted).nodeMax = node_ptrs.len() as c_int;
let tab = libc::malloc(node_ptrs.len() * core::mem::size_of::<*mut _xmlNode>())
as *mut *mut _xmlNode;
(*sorted).nodeTab = tab;
for (idx, n) in node_ptrs.iter().enumerate() {
if !tab.is_null() {
*tab.add(idx) = *n;
}
}
crate::xslt::sorting::xsltSortNodeSet(ctxt, sorted, sort);
let mut k = 0;
while k < (*sorted).nodeNr {
if (*ctxt).state == XSLT_STATE_ERROR || (*ctxt).state == XSLT_STATE_STOPPED {
break;
}
let n = *(*sorted).nodeTab.offset(k as isize);
if !n.is_null() {
(*ctxt).node = n;
if !(*ctxt).xpathCtxt.is_null() {
(*(*ctxt).xpathCtxt).contextSize = (*sorted).nodeNr;
(*(*ctxt).xpathCtxt).proximityPosition = k + 1;
}
apply_templates_with_params(ctxt, n, mode, params);
}
k += 1;
}
libc::free((*sorted).nodeTab as *mut libc::c_void);
libc::free(sorted as *mut libc::c_void);
}
} else {
if !(*ctxt).xpathCtxt.is_null() {
(*(*ctxt).xpathCtxt).contextSize = node_ptrs.len() as c_int;
}
for (i, n) in node_ptrs.iter().enumerate() {
if (*ctxt).state == XSLT_STATE_ERROR || (*ctxt).state == XSLT_STATE_STOPPED {
break;
}
if !n.is_null() {
(*ctxt).node = *n;
if !(*ctxt).xpathCtxt.is_null() {
(*(*ctxt).xpathCtxt).proximityPosition = (i + 1) as c_int;
}
apply_templates_with_params(ctxt, *n, mode, params);
}
}
}
}
if !mode.is_null() {
libc::free(mode as *mut libc::c_void);
}
xmlXPathFreeObject(obj);
}
pub(crate) unsafe fn apply_templates_with_params(
ctxt: *mut _xsltTransformContext,
node: *mut _xmlNode,
mode: *const xmlChar,
params: *mut _xsltStackElem,
) {
let mut p = params;
while !p.is_null() {
crate::xslt::parameters::xsltPushParam(ctxt, p);
p = (*p).next;
}
apply_templates_to_node(ctxt, node, mode);
let mut p = params;
while !p.is_null() {
crate::xslt::parameters::xsltPopParam(ctxt);
p = (*p).next;
}
}
pub(crate) unsafe fn build_child_node_set(
ctxt: *mut _xsltTransformContext,
) -> *mut _xmlXPathObject {
let ns = xmlXPathNodeSetCreate(ptr::null_mut());
if ns.is_null() {
return ptr::null_mut();
}
let obj = xmlMalloc_zero_obj();
if obj.is_null() {
libc::free(ns as *mut libc::c_void);
return ptr::null_mut();
}
(*obj).type_ = xmlXPathObjectType::XPATH_NODESET as c_int;
(*obj).nodesetval = ns as *mut c_void;
let node = (*ctxt).node;
if !node.is_null() {
let mut child = (*node).children;
while !child.is_null() {
append_to_node_set(ns, child);
child = (*child).next;
}
}
obj
}
unsafe fn xmlMalloc_zero_obj() -> *mut _xmlXPathObject {
libc::calloc(1, core::mem::size_of::<_xmlXPathObject>()) as *mut _xmlXPathObject
}
unsafe fn append_to_node_set(ns: *mut _xmlNodeSet, node: *mut _xmlNode) {
if ns.is_null() || node.is_null() {
return;
}
let mut i = 0;
while i < (*ns).nodeNr {
if *(*ns).nodeTab.offset(i as isize) == node {
return;
}
i += 1;
}
if (*ns).nodeNr >= (*ns).nodeMax {
let new_max = if (*ns).nodeMax == 0 {
8
} else {
(*ns).nodeMax * 2
};
let new_tab = libc::realloc(
(*ns).nodeTab as *mut libc::c_void,
(new_max as usize) * core::mem::size_of::<*mut _xmlNode>(),
) as *mut *mut _xmlNode;
if new_tab.is_null() {
return;
}
(*ns).nodeTab = new_tab;
(*ns).nodeMax = new_max;
}
*(*ns).nodeTab.offset((*ns).nodeNr as isize) = node;
(*ns).nodeNr += 1;
}
pub(crate) unsafe fn collect_with_params(
ctxt: *mut _xsltTransformContext,
inst: *mut _xmlNode,
) -> *mut _xsltStackElem {
let mut head: *mut _xsltStackElem = ptr::null_mut();
let mut tail: *mut _xsltStackElem = ptr::null_mut();
let mut child = (*inst).children;
while !child.is_null() {
let next = (*child).next;
if is_xslt_element(child, "with-param") {
let param = evaluate_with_param(ctxt, child);
if !param.is_null() {
(*param).next = ptr::null_mut();
if tail.is_null() {
head = param;
tail = param;
} else {
(*tail).next = param;
tail = param;
}
}
}
child = next;
}
head
}
pub(crate) unsafe fn evaluate_with_param(
ctxt: *mut _xsltTransformContext,
inst: *mut _xmlNode,
) -> *mut _xsltStackElem {
let name = get_prop(inst, c"name".as_ptr() as *const xmlChar);
if name.is_null() {
return ptr::null_mut();
}
let select = get_prop(inst, c"select".as_ptr() as *const xmlChar);
let param = libc::calloc(1, core::mem::size_of::<_xsltStackElem>()) as *mut _xsltStackElem;
if param.is_null() {
libc::free(name as *mut libc::c_void);
if !select.is_null() {
libc::free(select as *mut libc::c_void);
}
return ptr::null_mut();
}
(*param).name = name;
(*param).flags = 2 | 4; (*param).comp = &XSLT_COMP_TYPE_WITHPARAM as *const c_int as *mut c_void;
if !select.is_null() {
let obj = eval_xpath(ctxt, select);
if !obj.is_null() {
(*param).value = obj;
} else {
report_xpath_eval_failure(ctxt, inst, "with-param");
libc::free(select as *mut libc::c_void);
libc::free(param as *mut libc::c_void);
return ptr::null_mut();
}
libc::free(select as *mut libc::c_void);
} else {
let value = eval_content_fragment(ctxt, (*inst).children);
if !value.is_null() {
(*param).value = value;
}
}
param
}
pub(crate) unsafe fn eval_content_fragment(
ctxt: *mut _xsltTransformContext,
content: *mut _xmlNode,
) -> *mut _xmlXPathObject {
let frag = libc::calloc(1, core::mem::size_of::<_xmlDoc>()) as *mut _xmlDoc;
if frag.is_null() {
return ptr::null_mut();
}
(*frag).type_ = XML_DOCUMENT_NODE as c_int;
(*frag).doc = frag;
let saved_insert = (*ctxt).insert;
let saved_output = (*ctxt).output;
(*ctxt).insert = frag as *mut _xmlNode;
(*ctxt).output = frag;
execute_content(ctxt, content);
(*ctxt).insert = saved_insert;
(*ctxt).output = saved_output;
let obj = xmlMalloc_zero_obj();
if obj.is_null() {
free_doc(frag);
return ptr::null_mut();
}
(*obj).type_ = xmlXPathObjectType::XPATH_XSLT_TREE as c_int;
(*obj).nodesetval = frag as *mut c_void;
obj
}
#[allow(clippy::while_immutable_condition)]
pub(crate) unsafe fn process_call_template(ctxt: *mut _xsltTransformContext, inst: *mut _xmlNode) {
let name = get_prop(inst, c"name".as_ptr() as *const xmlChar);
if name.is_null() {
return;
}
let style = (*ctxt).style;
let templ = crate::xslt::templates::xsltLookupTemplate(style, name);
libc::free(name as *mut libc::c_void);
if templ.is_null() {
return;
}
template_stack_push(ctxt, templ);
(*ctxt).depth += 1;
if (*ctxt).depth > (*ctxt).maxTemplateDepth {
report_template_recursion(ctxt, inst, true);
(*ctxt).depth -= 1;
template_stack_pop(ctxt);
return;
}
let old_vars_nr = (*ctxt).varsNr;
let params = collect_with_params(ctxt, inst);
let mut to_push: Vec<*mut _xsltStackElem> = Vec::new();
let mut p = params;
while !p.is_null() {
to_push.push(p);
p = (*p).next;
}
for param in to_push {
crate::xslt::parameters::xsltPushParam(ctxt, param);
}
if (*ctxt).varsNr >= (*ctxt).maxTemplateVars {
let err_node = if (*templ).content.is_null() {
inst
} else {
(*templ).content
};
report_template_recursion(ctxt, err_node, false);
(*ctxt).depth -= 1;
template_stack_pop(ctxt);
while (*ctxt).varsNr > old_vars_nr {
crate::xslt::parameters::xsltPopParam(ctxt);
}
return;
}
let saved_templ = (*ctxt).templ;
(*ctxt).templ = templ;
execute_content(ctxt, (*templ).content);
(*ctxt).templ = saved_templ;
while (*ctxt).varsNr > old_vars_nr {
crate::xslt::parameters::xsltPopParam(ctxt);
}
(*ctxt).depth -= 1;
template_stack_pop(ctxt);
}
pub(crate) unsafe fn process_apply_imports(ctxt: *mut _xsltTransformContext, inst: *mut _xmlNode) {
let _ = inst;
let style = (*ctxt).style;
let node = (*ctxt).node;
let current_templ = (*ctxt).templ;
if style.is_null() || node.is_null() || current_templ.is_null() {
return;
}
let current_depth = (*current_templ).position;
let mode = (*current_templ).mode;
let mut best: *mut _xsltTemplate = ptr::null_mut();
let mut best_priority: f32 = f32::NEG_INFINITY;
let mut best_depth: c_int = -1;
let mut templ = (*style).templates;
while !templ.is_null() {
if (*templ).position <= current_depth {
templ = (*templ).next;
continue;
}
if !(*templ).mode.is_null() {
if mode.is_null()
|| libc::strcmp(
(*templ).mode as *const libc::c_char,
mode as *const libc::c_char,
) != 0
{
templ = (*templ).next;
continue;
}
} else if !mode.is_null() {
templ = (*templ).next;
continue;
}
let pattern_ptr = (*templ).params as *mut crate::xslt::patterns::_xsltPattern;
if pattern_ptr.is_null() {
templ = (*templ).next;
continue;
}
if crate::xslt::patterns::xsltTestPattern(ctxt, pattern_ptr, node) == 0 {
templ = (*templ).next;
continue;
}
let priority = (*templ).priority;
if priority > best_priority || (priority == best_priority && (*templ).position > best_depth)
{
best = templ;
best_priority = priority;
best_depth = (*templ).position;
}
templ = (*templ).next;
}
if !best.is_null() {
template_stack_push(ctxt, best);
(*ctxt).depth += 1;
if (*ctxt).depth > (*ctxt).maxTemplateDepth {
report_template_recursion(ctxt, inst, true);
(*ctxt).depth -= 1;
template_stack_pop(ctxt);
return;
}
if (*ctxt).varsNr >= (*ctxt).maxTemplateVars {
let err_node = if (*best).content.is_null() {
inst
} else {
(*best).content
};
report_template_recursion(ctxt, err_node, false);
(*ctxt).depth -= 1;
template_stack_pop(ctxt);
return;
}
let saved_templ = (*ctxt).templ;
(*ctxt).templ = best;
execute_content(ctxt, (*best).content);
(*ctxt).templ = saved_templ;
(*ctxt).depth -= 1;
template_stack_pop(ctxt);
}
}
pub(crate) unsafe fn process_for_each(ctxt: *mut _xsltTransformContext, inst: *mut _xmlNode) {
let select = get_prop(inst, c"select".as_ptr() as *const xmlChar);
if select.is_null() {
return;
}
let obj = eval_xpath(ctxt, select);
libc::free(select as *mut libc::c_void);
if obj.is_null() {
report_xpath_eval_failure(ctxt, inst, "for-each");
return;
}
if (*obj).type_ != xmlXPathObjectType::XPATH_NODESET as c_int {
xmlXPathFreeObject(obj);
return;
}
let nodes = (*obj).nodesetval as *mut _xmlNodeSet;
if nodes.is_null() || (*nodes).nodeNr == 0 {
xmlXPathFreeObject(obj);
return;
}
let sort = find_sort_children(ctxt, inst);
let saved_node = (*ctxt).node;
let xpath_ctxt = (*ctxt).xpathCtxt;
let (saved_size, saved_pos) = if xpath_ctxt.is_null() {
(0, 0)
} else {
((*xpath_ctxt).contextSize, (*xpath_ctxt).proximityPosition)
};
let mut node_ptrs: Vec<*mut _xmlNode> = Vec::new();
let mut i = 0;
while i < (*nodes).nodeNr {
let n = *(*nodes).nodeTab.offset(i as isize);
if !n.is_null() {
node_ptrs.push(n);
}
i += 1;
}
if !sort.is_null() {
let sorted = libc::calloc(1, core::mem::size_of::<_xmlNodeSet>()) as *mut _xmlNodeSet;
if !sorted.is_null() {
(*sorted).nodeNr = node_ptrs.len() as c_int;
(*sorted).nodeMax = node_ptrs.len() as c_int;
let tab = libc::malloc(node_ptrs.len() * core::mem::size_of::<*mut _xmlNode>())
as *mut *mut _xmlNode;
(*sorted).nodeTab = tab;
for (idx, n) in node_ptrs.iter().enumerate() {
if !tab.is_null() {
*tab.add(idx) = *n;
}
}
crate::xslt::sorting::xsltSortNodeSet(ctxt, sorted, sort);
if !xpath_ctxt.is_null() {
(*xpath_ctxt).contextSize = (*sorted).nodeNr;
}
let mut k = 0;
while k < (*sorted).nodeNr {
if (*ctxt).state == XSLT_STATE_ERROR || (*ctxt).state == XSLT_STATE_STOPPED {
break;
}
let n = *(*sorted).nodeTab.offset(k as isize);
if !n.is_null() {
(*ctxt).node = n;
if !xpath_ctxt.is_null() {
(*xpath_ctxt).proximityPosition = k + 1;
}
execute_content(ctxt, (*inst).children);
}
k += 1;
}
libc::free((*sorted).nodeTab as *mut libc::c_void);
libc::free(sorted as *mut libc::c_void);
}
} else {
if !xpath_ctxt.is_null() {
(*xpath_ctxt).contextSize = node_ptrs.len() as c_int;
}
for (i, n) in node_ptrs.iter().enumerate() {
if (*ctxt).state == XSLT_STATE_ERROR || (*ctxt).state == XSLT_STATE_STOPPED {
break;
}
if !n.is_null() {
(*ctxt).node = *n;
if !xpath_ctxt.is_null() {
(*xpath_ctxt).proximityPosition = (i + 1) as c_int;
}
execute_content(ctxt, (*inst).children);
}
}
}
(*ctxt).node = saved_node;
if !xpath_ctxt.is_null() {
(*xpath_ctxt).contextSize = saved_size;
(*xpath_ctxt).proximityPosition = saved_pos;
}
xmlXPathFreeObject(obj);
}
unsafe fn find_sort_children(
ctxt: *mut _xsltTransformContext,
inst: *mut _xmlNode,
) -> *mut _xsltSort {
if ctxt.is_null() || inst.is_null() {
return ptr::null_mut();
}
let mut child = (*inst).children;
while !child.is_null() {
if is_xslt_element(child, "sort") {
let style = (*ctxt).style;
let sort = crate::xslt::sorting::xsltCompileSort(style, child);
return sort;
}
child = (*child).next;
}
ptr::null_mut()
}
pub(crate) unsafe fn process_value_of(ctxt: *mut _xsltTransformContext, inst: *mut _xmlNode) {
let select = get_prop(inst, c"select".as_ptr() as *const xmlChar);
if select.is_null() {
return;
}
let obj = eval_xpath(ctxt, select);
libc::free(select as *mut libc::c_void);
if obj.is_null() {
report_xpath_eval_failure(ctxt, inst, "value-of");
return;
}
let strv = xmlXPathCastToString(obj);
xmlXPathFreeObject(obj);
if !strv.is_null() {
if *strv != 0 {
append_text_node(ctxt, strv);
}
libc::free(strv as *mut libc::c_void);
}
}
pub(crate) unsafe fn process_copy_of(ctxt: *mut _xsltTransformContext, inst: *mut _xmlNode) {
let select = get_prop(inst, c"select".as_ptr() as *const xmlChar);
if select.is_null() {
return;
}
let obj = eval_xpath(ctxt, select);
libc::free(select as *mut libc::c_void);
if obj.is_null() {
report_xpath_eval_failure(ctxt, inst, "copy-of");
return;
}
if (*obj).type_ == xmlXPathObjectType::XPATH_NODESET as c_int {
let nodes = (*obj).nodesetval as *mut _xmlNodeSet;
if !nodes.is_null() {
let mut i = 0;
while i < (*nodes).nodeNr {
let n = *(*nodes).nodeTab.offset(i as isize);
if !n.is_null() {
copy_node_deep(ctxt, n);
}
i += 1;
}
}
} else if (*obj).type_ == xmlXPathObjectType::XPATH_XSLT_TREE as c_int {
let frag = (*obj).nodesetval as *mut _xmlDoc;
if !frag.is_null() {
let mut child = (*frag).children;
while !child.is_null() {
let next = (*child).next;
copy_node_deep(ctxt, child);
child = next;
}
}
} else {
let strv = xmlXPathCastToString(obj);
if !strv.is_null() {
if *strv != 0 {
append_text_node(ctxt, strv);
}
libc::free(strv as *mut libc::c_void);
}
}
xmlXPathFreeObject(obj);
}
pub(crate) unsafe fn copy_node_deep(ctxt: *mut _xsltTransformContext, node: *mut _xmlNode) {
if node.is_null() {
return;
}
let typ = (*node).type_;
if typ == XML_TEXT_NODE as c_int || typ == XML_CDATA_SECTION_NODE as c_int {
if !(*node).content.is_null() {
append_text_node(ctxt, (*node).content);
}
} else if typ == XML_COMMENT_NODE as c_int {
if !(*node).content.is_null() {
append_comment_node(ctxt, (*node).content);
}
} else if typ == XML_PI_NODE as c_int {
if !(*node).name.is_null() {
append_pi_node(ctxt, (*node).name, (*node).content);
}
} else if typ == XML_ELEMENT_NODE as c_int {
let name = (*node).name;
let new_elem = new_element_node(ctxt, name, (*node).ns);
if new_elem.is_null() {
return;
}
let mut prop = (*node).properties;
while !prop.is_null() {
let attr_name = (*prop).name;
let attr_val = node_get_content((*prop).children);
if !attr_name.is_null() && !attr_val.is_null() {
set_prop(new_elem, attr_name, attr_val);
libc::free(attr_val as *mut libc::c_void);
}
prop = (*prop).next;
}
let saved_insert = (*ctxt).insert;
(*ctxt).insert = new_elem;
let mut child = (*node).children;
while !child.is_null() {
let next = (*child).next;
copy_node_deep(ctxt, child);
child = next;
}
(*ctxt).insert = saved_insert;
} else if typ == XML_DOCUMENT_NODE as c_int || typ == XML_HTML_DOCUMENT_NODE as c_int {
let mut child = (*node).children;
while !child.is_null() {
let next = (*child).next;
copy_node_deep(ctxt, child);
child = next;
}
}
}
unsafe fn new_element_node(
ctxt: *mut _xsltTransformContext,
name: *const xmlChar,
ns: *mut _xmlNs,
) -> *mut _xmlNode {
let elem = new_node(ns, name);
if elem.is_null() {
return ptr::null_mut();
}
append_to_result(ctxt, elem);
elem
}
pub(crate) unsafe fn append_to_result(ctxt: *mut _xsltTransformContext, node: *mut _xmlNode) {
let insert = (*ctxt).insert;
if insert.is_null() {
return;
}
add_child(insert, node);
let doc = if (*insert).type_ == XML_DOCUMENT_NODE as c_int {
insert as *mut _xmlDoc
} else {
(*insert).doc
};
if !doc.is_null() {
set_node_doc(node, doc);
}
}
unsafe fn set_node_doc(node: *mut _xmlNode, doc: *mut _xmlDoc) {
if node.is_null() {
return;
}
(*node).doc = doc;
let mut prop = (*node).properties;
while !prop.is_null() {
(*prop).doc = doc;
prop = (*prop).next;
}
let mut child = (*node).children;
while !child.is_null() {
set_node_doc(child, doc);
child = (*child).next;
}
}
pub(crate) unsafe fn append_text_node(ctxt: *mut _xsltTransformContext, content: *const xmlChar) {
let insert = (*ctxt).insert;
if insert.is_null() || content.is_null() {
return;
}
let text = new_text(content);
if text.is_null() {
return;
}
append_to_result(ctxt, text);
}
pub(crate) unsafe fn append_comment_node(
ctxt: *mut _xsltTransformContext,
content: *const xmlChar,
) {
let insert = (*ctxt).insert;
if insert.is_null() || content.is_null() {
return;
}
let comment = new_comment(content);
if comment.is_null() {
return;
}
append_to_result(ctxt, comment);
}
pub(crate) unsafe fn append_pi_node(
ctxt: *mut _xsltTransformContext,
name: *const xmlChar,
content: *const xmlChar,
) {
let insert = (*ctxt).insert;
if insert.is_null() || name.is_null() {
return;
}
let pi = new_pi(name, content);
if pi.is_null() {
return;
}
append_to_result(ctxt, pi);
}
pub(crate) unsafe fn process_copy(ctxt: *mut _xsltTransformContext, inst: *mut _xmlNode) {
let node = (*ctxt).node;
if node.is_null() {
return;
}
let typ = (*node).type_;
let saved_insert = (*ctxt).insert;
if typ == XML_ELEMENT_NODE as c_int {
let name = (*node).name;
if name.is_null() {
return;
}
let new_elem = crate::xml::tree::new_node(ptr::null_mut(), name);
if new_elem.is_null() {
return;
}
append_to_result(ctxt, new_elem);
let mut nsdef = (*node).nsDef;
while !nsdef.is_null() {
unsafe {
if !(*nsdef).href.is_null() {
crate::xml::tree::new_ns(new_elem, (*nsdef).href, (*nsdef).prefix);
}
}
nsdef = unsafe { (*nsdef).next };
}
if !(*node).ns.is_null() {
let src_ns = &*(*node).ns;
if src_ns.href.is_null() || *src_ns.href == 0 {
(*new_elem).ns = ptr::null_mut();
} else {
let fixed = crate::abi::exports_xslt_avt::xsltGetSpecialNamespace(
ctxt,
inst,
src_ns.href,
src_ns.prefix,
new_elem,
);
(*new_elem).ns = fixed;
}
} else if !saved_insert.is_null()
&& (*saved_insert).type_ == XML_ELEMENT_NODE as c_int
&& !(*saved_insert).ns.is_null()
{
crate::abi::exports_xslt_avt::xsltGetSpecialNamespace(
ctxt,
inst,
ptr::null(),
ptr::null(),
new_elem,
);
}
(*ctxt).insert = new_elem;
} else if typ == XML_TEXT_NODE as c_int || typ == XML_CDATA_SECTION_NODE as c_int {
if !(*node).content.is_null() {
append_text_node(ctxt, (*node).content);
}
} else if typ == XML_COMMENT_NODE as c_int {
if !(*node).content.is_null() {
append_comment_node(ctxt, (*node).content);
}
} else if typ == XML_PI_NODE as c_int {
if !(*node).name.is_null() {
append_pi_node(ctxt, (*node).name, (*node).content);
}
} else if typ == XML_ATTRIBUTE_NODE as c_int {
let target = (*ctxt).insert;
let copy_ok = if target.is_null() || (*target).type_ != XML_ELEMENT_NODE as c_int {
crate::xslt::errors::xsltTransformError(
ctxt,
ptr::null_mut(),
inst,
c"Cannot add an attribute node to a non-element node.\n"
.as_ptr()
.cast::<libc::c_char>(),
);
false
} else if !(*target).children.is_null() {
crate::xslt::errors::xsltTransformError(
ctxt,
ptr::null_mut(),
inst,
c"Attribute nodes must be added before any child nodes to an element.\n"
.as_ptr()
.cast::<libc::c_char>()
.add(0),
);
false
} else {
let val = node_get_content((*node).children);
let attr = node as *mut _xmlAttr;
let attr_ref = &*attr;
let ns = if attr_ref.ns.is_null() {
ptr::null_mut()
} else {
crate::abi::exports_xslt_avt::xsltGetSpecialNamespace(
ctxt,
inst,
(*attr_ref.ns).href,
(*attr_ref.ns).prefix,
target,
)
};
crate::abi::exports_xml2::xmlSetNsProp(target, ns, attr_ref.name, val);
if !val.is_null() {
libc::free(val as *mut libc::c_void);
}
true
};
let _ = copy_ok;
}
execute_content(ctxt, (*inst).children);
(*ctxt).insert = saved_insert;
}
pub(crate) unsafe fn process_element(ctxt: *mut _xsltTransformContext, inst: *mut _xmlNode) {
let name_attr = get_prop(inst, c"name".as_ptr() as *const xmlChar);
if name_attr.is_null() {
return;
}
let name_str = eval_avt(ctxt, name_attr);
libc::free(name_attr as *mut libc::c_void);
if name_str.is_null() {
return;
}
let ns_attr = get_prop(inst, c"namespace".as_ptr() as *const xmlChar);
let had_ns_attr = !ns_attr.is_null();
let ns_str = if !ns_attr.is_null() {
let v = eval_avt(ctxt, ns_attr);
libc::free(ns_attr as *mut libc::c_void);
v
} else {
ptr::null_mut()
};
let mut borrowed_href: *const xmlChar = ptr::null();
let mut borrowed_prefix: *const xmlChar = ptr::null();
if !ns_str.is_null() && *ns_str != 0 {
borrowed_href = ns_str;
} else if ns_str.is_null() && !had_ns_attr {
let def = crate::abi::exports_xml2::xmlSearchNs((*inst).doc, inst, ptr::null());
if !def.is_null() && !(*def).href.is_null() && *(*def).href != 0 {
borrowed_href = (*def).href;
borrowed_prefix = (*def).prefix;
}
}
let elem = crate::xml::tree::new_node(ptr::null_mut(), name_str);
libc::free(name_str as *mut libc::c_void);
if elem.is_null() {
if !ns_str.is_null() {
libc::free(ns_str as *mut libc::c_void);
}
return;
}
append_to_result(ctxt, elem);
if !borrowed_href.is_null() && *borrowed_href != 0 {
let bound = crate::abi::exports_xslt_avt::xsltGetSpecialNamespace(
ctxt,
inst,
borrowed_href,
borrowed_prefix,
elem,
);
(*elem).ns = bound;
}
if !ns_str.is_null() {
libc::free(ns_str as *mut libc::c_void);
}
let saved_insert = (*ctxt).insert;
(*ctxt).insert = elem;
execute_content(ctxt, (*inst).children);
(*ctxt).insert = saved_insert;
}
pub(crate) unsafe fn process_attribute(ctxt: *mut _xsltTransformContext, inst: *mut _xmlNode) {
let name_attr = get_prop(inst, c"name".as_ptr() as *const xmlChar);
if name_attr.is_null() {
return;
}
let name_str = eval_avt(ctxt, name_attr);
libc::free(name_attr as *mut libc::c_void);
if name_str.is_null() {
return;
}
let insert = (*ctxt).insert;
if insert.is_null() {
xmlFreeImpl(name_str as *mut c_void);
return;
}
let saved_insert = (*ctxt).insert;
let buf = libc::calloc(1, core::mem::size_of::<_xmlBuffer>()) as *mut _xmlBuffer;
if buf.is_null() {
xmlFreeImpl(name_str as *mut c_void);
return;
}
(*buf).content = libc::calloc(1, 64) as *mut xmlChar;
(*buf).size = 64;
(*buf).use_ = 0;
let frag_doc = libc::calloc(1, core::mem::size_of::<_xmlDoc>()) as *mut _xmlDoc;
if frag_doc.is_null() {
libc::free(buf as *mut libc::c_void);
xmlFreeImpl(name_str as *mut c_void);
return;
}
(*frag_doc).type_ = XML_DOCUMENT_NODE as c_int;
(*frag_doc).doc = frag_doc;
(*ctxt).insert = frag_doc as *mut _xmlNode;
execute_content(ctxt, (*inst).children);
let mut value: Vec<u8> = Vec::new();
let mut child = (*frag_doc).children;
while !child.is_null() {
if (*child).type_ == XML_TEXT_NODE as c_int && !(*child).content.is_null() {
let len = libc::strlen((*child).content as *const libc::c_char) as usize;
value.extend_from_slice(core::slice::from_raw_parts((*child).content, len));
}
child = (*child).next;
}
free_doc(frag_doc);
(*ctxt).insert = saved_insert;
let mut cvalue = value.clone();
cvalue.push(0);
set_prop(insert, name_str, cvalue.as_ptr() as *const xmlChar);
xmlFreeImpl(name_str as *mut c_void);
}
pub(crate) unsafe fn process_text(ctxt: *mut _xsltTransformContext, inst: *mut _xmlNode) {
let doe = get_prop(inst, c"disable-output-escaping".as_ptr() as *const xmlChar);
if !doe.is_null() {
libc::free(doe as *mut libc::c_void);
}
let mut child = (*inst).children;
while !child.is_null() {
if (*child).type_ == XML_TEXT_NODE as c_int && !(*child).content.is_null() {
append_text_node(ctxt, (*child).content);
}
child = (*child).next;
}
}
pub(crate) unsafe fn process_comment(ctxt: *mut _xsltTransformContext, inst: *mut _xmlNode) {
let content = node_get_content((*inst).children);
if !content.is_null() {
append_comment_node(ctxt, content);
libc::free(content as *mut libc::c_void);
}
}
pub(crate) unsafe fn process_pi(ctxt: *mut _xsltTransformContext, inst: *mut _xmlNode) {
let name_attr = get_prop(inst, c"name".as_ptr() as *const xmlChar);
if name_attr.is_null() {
return;
}
let name_str = eval_avt(ctxt, name_attr);
libc::free(name_attr as *mut libc::c_void);
if name_str.is_null() {
return;
}
let content = node_get_content((*inst).children);
append_pi_node(ctxt, name_str, content);
if !content.is_null() {
libc::free(content as *mut libc::c_void);
}
xmlFreeImpl(name_str as *mut c_void);
}
pub(crate) unsafe fn process_number(ctxt: *mut _xsltTransformContext, inst: *mut _xmlNode) {
let value_attr = get_prop(inst, c"value".as_ptr() as *const xmlChar);
let mut number: f64 = f64::NAN;
if !value_attr.is_null() {
let obj = eval_xpath(ctxt, value_attr);
libc::free(value_attr as *mut libc::c_void);
if !obj.is_null() {
number = (*obj).floatval;
xmlXPathFreeObject(obj);
} else {
report_xpath_eval_failure(ctxt, inst, "number");
return;
}
} else {
number = 1.0;
let node = (*ctxt).node;
if !node.is_null() {
let mut sib = (*node).prev;
while !sib.is_null() {
if (*sib).type_ == XML_ELEMENT_NODE as c_int {
number += 1.0;
}
sib = (*sib).prev;
}
}
}
let format = get_prop(inst, c"format".as_ptr() as *const xmlChar);
let formatted = crate::xslt::numbering::xsltFormatNumber(number, format);
if !format.is_null() {
libc::free(format as *mut libc::c_void);
}
if !formatted.is_null() {
append_text_node(ctxt, formatted);
libc::free(formatted as *mut libc::c_void);
}
}
unsafe fn xpath_obj_boolean(obj: *mut _xmlXPathObject) -> bool {
if obj.is_null() {
return false;
}
let typ = (*obj).type_;
if typ == xmlXPathObjectType::XPATH_BOOLEAN as c_int {
return (*obj).boolval != 0;
}
if typ == xmlXPathObjectType::XPATH_NUMBER as c_int {
let n = (*obj).floatval;
return n != 0.0 && !n.is_nan();
}
if typ == xmlXPathObjectType::XPATH_STRING as c_int {
return !(*obj).stringval.is_null() && *(*obj).stringval != 0;
}
if typ == xmlXPathObjectType::XPATH_NODESET as c_int {
let ns = (*obj).nodesetval as *mut _xmlNodeSet;
return !ns.is_null() && (*ns).nodeNr > 0;
}
false
}
pub(crate) unsafe fn process_choose(ctxt: *mut _xsltTransformContext, inst: *mut _xmlNode) {
let mut child = (*inst).children;
let mut executed = false;
while !child.is_null() {
let next = (*child).next;
if is_xslt_element(child, "when") {
let test = get_prop(child, c"test".as_ptr() as *const xmlChar);
if !test.is_null() {
let obj = eval_xpath(ctxt, test);
libc::free(test as *mut libc::c_void);
if obj.is_null() {
report_xpath_eval_failure(ctxt, child, "when");
return;
}
let truthy = xpath_obj_boolean(obj);
xmlXPathFreeObject(obj);
if truthy {
execute_content(ctxt, (*child).children);
executed = true;
break;
}
}
} else if is_xslt_element(child, "otherwise") && !executed {
execute_content(ctxt, (*child).children);
executed = true;
}
child = next;
}
}
pub(crate) unsafe fn process_if(ctxt: *mut _xsltTransformContext, inst: *mut _xmlNode) {
let test = get_prop(inst, c"test".as_ptr() as *const xmlChar);
if test.is_null() {
return;
}
let obj = eval_xpath(ctxt, test);
libc::free(test as *mut libc::c_void);
if obj.is_null() {
report_xpath_eval_failure(ctxt, inst, "if");
return;
}
let truthy = xpath_obj_boolean(obj);
xmlXPathFreeObject(obj);
if truthy {
execute_content(ctxt, (*inst).children);
}
}
pub(crate) unsafe fn process_variable(ctxt: *mut _xsltTransformContext, inst: *mut _xmlNode) {
let name = get_prop(inst, c"name".as_ptr() as *const xmlChar);
if name.is_null() {
return;
}
let select = get_prop(inst, c"select".as_ptr() as *const xmlChar);
let var = libc::calloc(1, core::mem::size_of::<_xsltStackElem>()) as *mut _xsltStackElem;
if var.is_null() {
libc::free(name as *mut libc::c_void);
if !select.is_null() {
libc::free(select as *mut libc::c_void);
}
return;
}
(*var).name = name;
(*var).flags = 4; (*var).comp = &XSLT_COMP_TYPE_VARIABLE as *const c_int as *mut c_void;
if !select.is_null() {
let obj = eval_xpath(ctxt, select);
if !obj.is_null() {
(*var).value = obj;
} else {
report_xpath_eval_failure(ctxt, inst, "variable");
libc::free(select as *mut libc::c_void);
libc::free(var as *mut libc::c_void);
return;
}
libc::free(select as *mut libc::c_void);
} else {
let value = eval_content_fragment(ctxt, (*inst).children);
if !value.is_null() {
(*var).value = value;
}
}
crate::xslt::variables::xsltPushVariable(ctxt, var);
}
pub(crate) unsafe fn process_param(ctxt: *mut _xsltTransformContext, inst: *mut _xmlNode) {
let name = get_prop(inst, c"name".as_ptr() as *const xmlChar);
if name.is_null() {
return;
}
let already_bound = {
let xpath_ctxt = (*ctxt).xpathCtxt;
if xpath_ctxt.is_null() {
false
} else {
let internal = (*xpath_ctxt).extra as *mut crate::xml::xpath::context::XPathContext;
if internal.is_null() {
false
} else {
let name_len = libc::strlen(name as *const libc::c_char);
let name_bytes = core::slice::from_raw_parts(name, name_len);
let name_owned = String::from_utf8_lossy(name_bytes).into_owned();
(*internal).variables.contains_key(&name_owned)
}
}
};
if already_bound {
libc::free(name as *mut libc::c_void);
return;
}
let select = get_prop(inst, c"select".as_ptr() as *const xmlChar);
let var = libc::calloc(1, core::mem::size_of::<_xsltStackElem>()) as *mut _xsltStackElem;
if var.is_null() {
libc::free(name as *mut libc::c_void);
if !select.is_null() {
libc::free(select as *mut libc::c_void);
}
return;
}
(*var).name = name;
(*var).flags = 2 | 4; (*var).comp = &XSLT_COMP_TYPE_PARAM as *const c_int as *mut c_void;
if !select.is_null() {
let obj = eval_xpath(ctxt, select);
if !obj.is_null() {
(*var).value = obj;
} else {
report_xpath_eval_failure(ctxt, inst, "param");
libc::free(select as *mut libc::c_void);
libc::free(var as *mut libc::c_void);
return;
}
libc::free(select as *mut libc::c_void);
} else {
let value = eval_content_fragment(ctxt, (*inst).children);
if !value.is_null() {
(*var).value = value;
}
}
crate::xslt::variables::xsltPushVariable(ctxt, var);
}
pub(crate) unsafe fn process_message(ctxt: *mut _xsltTransformContext, inst: *mut _xmlNode) {
let content = node_get_content((*inst).children);
if !content.is_null() {
let len = libc::strlen(content as *const libc::c_char) as usize;
let bytes = core::slice::from_raw_parts(content as *const u8, len);
crate::xslt::errors::xsltEmitMessage(ctxt, bytes);
let terminate = get_prop(inst, c"terminate".as_ptr() as *const xmlChar);
if !terminate.is_null() {
if libc::strcmp(
terminate as *const libc::c_char,
c"yes".as_ptr() as *const libc::c_char,
) == 0
{
(*ctxt).state = XSLT_STATE_ERROR;
}
libc::free(terminate as *mut libc::c_void);
}
libc::free(content as *mut libc::c_void);
}
}
pub(crate) unsafe fn eval_avt(
ctxt: *mut _xsltTransformContext,
value: *const xmlChar,
) -> *mut xmlChar {
if value.is_null() {
return ptr::null_mut();
}
let len = libc::strlen(value as *const libc::c_char);
let bytes = core::slice::from_raw_parts(value, len);
let mut out: Vec<u8> = Vec::new();
let mut i = 0;
while i < bytes.len() {
let b = bytes[i];
if b == b'{' {
if i + 1 < bytes.len() && bytes[i + 1] == b'{' {
out.push(b'{');
i += 2;
continue;
}
if let Some(rel) = bytes[i + 1..].iter().position(|c| *c == b'}') {
let close = i + 1 + rel;
let expr_bytes = &bytes[i + 1..close];
let expr_c = crate::xml::string::bytes_to_xmlstr(expr_bytes);
if !expr_c.is_null() {
let obj = eval_xpath(ctxt, expr_c);
xmlFreeImpl(expr_c as *mut c_void);
if !obj.is_null() {
let strv = xmlXPathCastToString(obj);
xmlXPathFreeObject(obj);
if !strv.is_null() {
let slen = libc::strlen(strv as *const libc::c_char);
out.extend_from_slice(core::slice::from_raw_parts(strv, slen));
xmlFreeImpl(strv as *mut c_void);
}
} else {
report_xpath_eval_failure(ctxt, (*ctxt).inst, "attribute-value-template");
return ptr::null_mut();
}
}
i = close + 1;
continue;
}
out.push(b'{');
i += 1;
continue;
}
if b == b'}' {
if i + 1 < bytes.len() && bytes[i + 1] == b'}' {
out.push(b'}');
i += 2;
continue;
}
out.push(b'}');
i += 1;
continue;
}
out.push(b);
i += 1;
}
crate::xml::string::bytes_to_xmlstr(&out)
}
unsafe fn read_excluded_prefixes(node: *mut _xmlNode) -> Vec<Vec<u8>> {
let mut out = Vec::new();
if node.is_null() {
return out;
}
let mut prop = (*node).properties;
while !prop.is_null() {
let is_exclude = {
let name = (*prop).name;
let is_name = if name.is_null() {
false
} else {
let b = core::slice::from_raw_parts(
name,
libc::strlen(name as *const libc::c_char) as usize,
);
b == b"exclude-result-prefixes" || b == b"extension-element-prefixes"
};
let is_xsl_ns = if (*prop).ns.is_null() {
false
} else {
let h = (*(*prop).ns).href;
!h.is_null() && {
let hb = core::slice::from_raw_parts(
h,
libc::strlen(h as *const libc::c_char) as usize,
);
hb == XSLT_NAMESPACE_BYTES
}
};
is_name && (is_xsl_ns || (*prop).ns.is_null())
};
if is_exclude {
let content = node_get_content((*prop).children);
if !content.is_null() {
let len = libc::strlen(content as *const libc::c_char) as usize;
let bytes = core::slice::from_raw_parts(content, len);
for tok in bytes.split(|&b| b == b' ' || b == b'\t' || b == b'\n' || b == b'\r') {
if !tok.is_empty() {
out.push(tok.to_vec());
}
}
xmlFreeImpl(content as *mut c_void);
}
}
prop = (*prop).next;
}
out
}
unsafe fn find_stylesheet_root(node: *mut _xmlNode) -> *mut _xmlNode {
let mut cur = node;
while !cur.is_null() {
if (*cur).type_ == XML_ELEMENT_NODE as c_int {
if unsafe { is_xslt_namespace(cur) } {
let name = get_element_name(cur);
if matches!(name.as_deref(), Some("stylesheet") | Some("transform")) {
return cur;
}
}
}
let t = (*cur).type_;
cur = (*cur).parent;
if cur.is_null() || t == XML_DOCUMENT_NODE as c_int || t == XML_HTML_DOCUMENT_NODE as c_int
{
break;
}
}
ptr::null_mut()
}
const XSLT_NAMESPACE_BYTES: &[u8] = b"http://www.w3.org/1999/XSL/Transform";
unsafe fn lre_decls_suppressed(node: *mut _xmlNode) -> bool {
let mut cur = if node.is_null() {
return false;
} else {
(*node).parent
};
while !cur.is_null() {
let t = (*cur).type_;
if t == XML_ELEMENT_NODE as c_int && unsafe { is_xslt_namespace(cur) } {
let name = get_element_name(cur);
match name.as_deref() {
Some("for-each") | Some("if") | Some("choose") | Some("when")
| Some("otherwise") => return true,
Some("template") | Some("stylesheet") | Some("transform") => return false,
_ => {}
}
}
if cur.is_null() || t == XML_DOCUMENT_NODE as c_int || t == XML_HTML_DOCUMENT_NODE as c_int
{
break;
}
cur = (*cur).parent;
}
false
}
unsafe fn copy_literal_result_ns(
ctxt: *mut _xsltTransformContext,
elem: *mut _xmlNode,
inst: *mut _xmlNode,
) {
let mut excluded: Vec<Vec<u8>> = Vec::new();
excluded.extend(read_excluded_prefixes(inst));
let root = find_stylesheet_root(inst);
if !root.is_null() && root != inst {
excluded.extend(read_excluded_prefixes(root));
}
let excluded_default = excluded.iter().any(|p| p.is_empty());
struct NsDecl {
prefix: *const xmlChar,
href: *const xmlChar,
}
let mut in_scope: Vec<NsDecl> = Vec::new();
let mut cur: *mut _xmlNode = inst;
while !cur.is_null() {
let mut d = (*cur).nsDef;
while !d.is_null() {
let prefix = (*d).prefix;
let dup = in_scope.iter().any(|e| {
(prefix.is_null() && e.prefix.is_null())
|| (!prefix.is_null()
&& !e.prefix.is_null()
&& unsafe { xmlStrEqual(prefix, e.prefix) != 0 })
});
if !dup {
in_scope.push(NsDecl {
prefix,
href: (*d).href,
});
}
d = (*d).next;
}
let t = (*cur).type_;
cur = (*cur).parent;
if cur.is_null() || t == XML_DOCUMENT_NODE as c_int || t == XML_HTML_DOCUMENT_NODE as c_int
{
break;
}
}
let result_parent = (*elem).parent;
let result_doc = (*elem).doc;
let suppressed = unsafe { lre_decls_suppressed(inst) };
if !suppressed {
for decl in &in_scope {
let href = decl.href;
if href.is_null() {
continue;
}
if !decl.prefix.is_null() {
let pbytes = core::slice::from_raw_parts(
decl.prefix,
libc::strlen(decl.prefix as *const libc::c_char) as usize,
);
if pbytes == b"xml" {
continue;
}
let is_xslt_bound = {
let hbytes = core::slice::from_raw_parts(
href,
libc::strlen(href as *const libc::c_char) as usize,
);
hbytes == XSLT_NAMESPACE_BYTES
};
let is_excluded = excluded.iter().any(|p| p.as_slice() == pbytes);
if is_xslt_bound || is_excluded {
continue;
}
} else if excluded_default {
continue;
}
let parent =
if result_parent.is_null() || (*result_parent).type_ != XML_ELEMENT_NODE as c_int {
ptr::null_mut()
} else {
result_parent
};
if !parent.is_null() {
let ins = search_ns(result_doc, parent, decl.prefix);
if !ins.is_null() && !(*ins).href.is_null() {
let same = unsafe { xmlStrEqual((*ins).href, href) != 0 };
if same {
continue;
}
}
}
unsafe {
new_ns(elem, href, decl.prefix);
}
}
}
let inst_ns = (*inst).ns;
if !inst_ns.is_null() {
let want_prefix = (*inst_ns).prefix;
let want_href = (*inst_ns).href;
let mut own = (*elem).nsDef;
while !own.is_null() {
let prefix_eq = (want_prefix.is_null() && (*own).prefix.is_null())
|| (!want_prefix.is_null()
&& !(*own).prefix.is_null()
&& unsafe { xmlStrEqual(want_prefix, (*own).prefix) != 0 });
if prefix_eq && !(*own).href.is_null() && !want_href.is_null() {
if unsafe { xmlStrEqual((*own).href, want_href) != 0 } {
(*elem).ns = own;
break;
}
}
own = (*own).next;
}
if (*elem).ns.is_null() {
let parent =
if result_parent.is_null() || (*result_parent).type_ != XML_ELEMENT_NODE as c_int {
ptr::null_mut()
} else {
result_parent
};
let mut in_scope_ns: *mut _xmlNs = ptr::null_mut();
if !parent.is_null() {
in_scope_ns = search_ns(result_doc, parent, want_prefix);
}
if !in_scope_ns.is_null()
&& !want_href.is_null()
&& !(*in_scope_ns).href.is_null()
&& unsafe { xmlStrEqual((*in_scope_ns).href, want_href) != 0 }
{
(*elem).ns = in_scope_ns;
} else {
let created = unsafe { new_ns(elem, want_href, want_prefix) };
if !created.is_null() {
(*elem).ns = created;
}
}
}
} else if !result_parent.is_null() && (*result_parent).type_ == XML_ELEMENT_NODE as c_int {
let d = unsafe { search_ns(result_doc, result_parent, ptr::null()) };
if !d.is_null()
&& !(*d).href.is_null()
&& unsafe { *(*d).href } != 0
&& unsafe { (*elem).ns }.is_null()
{
unsafe {
new_ns(elem, c"".as_ptr() as *const xmlChar, ptr::null());
}
}
}
}
pub(crate) unsafe fn process_literal_element(
ctxt: *mut _xsltTransformContext,
inst: *mut _xmlNode,
) {
let elem = new_node(ptr::null_mut(), (*inst).name);
if elem.is_null() {
return;
}
append_to_result(ctxt, elem);
copy_literal_result_ns(ctxt, elem, inst);
let mut prop = (*inst).properties;
while !prop.is_null() {
let attr_name = (*prop).name;
if !attr_name.is_null() {
let name_bytes = core::slice::from_raw_parts(
attr_name,
libc::strlen(attr_name as *const libc::c_char) as usize,
);
if name_bytes != b"xmlns" {
let attr_val = node_get_content((*prop).children);
if !attr_val.is_null() {
let avt_val = eval_avt(ctxt, attr_val);
libc::free(attr_val as *mut libc::c_void);
if !avt_val.is_null() {
set_prop(elem, attr_name, avt_val);
xmlFreeImpl(avt_val as *mut c_void);
}
}
}
}
prop = (*prop).next;
}
let saved_insert = (*ctxt).insert;
(*ctxt).insert = elem;
execute_content(ctxt, (*inst).children);
(*ctxt).insert = saved_insert;
}
unsafe fn emit_transform_error_with_ctxt(tctxt: *mut _xsltTransformContext, msg: &[u8]) {
{
let mut buf = msg.to_vec();
buf.push(0);
crate::xslt::errors::xsltTransformError(
tctxt,
ptr::null_mut(),
ptr::null_mut(),
buf.as_ptr() as *const std::os::raw::c_char,
);
}
}
pub(crate) unsafe fn register_xslt_functions(ctxt: *mut _xsltTransformContext) {
let xpath_ctxt = (*ctxt).xpathCtxt;
if xpath_ctxt.is_null() {
return;
}
let internal = (*xpath_ctxt).extra as *mut crate::xml::xpath::context::XPathContext;
if internal.is_null() {
return;
}
let internal = &mut *internal;
use crate::xml::xpath::context::XPathContext;
use crate::xml::xpath::types::{NodeSet, XPathValue};
internal.register_function("document", |ctx, args| {
let value = match args.first() {
Some(v) => v.as_string(),
None => return Err("document() requires an argument".to_string()),
};
let tctxt = ctx.func_lookup_data as *mut _xsltTransformContext;
if tctxt.is_null() {
return Ok(XPathValue::NodeSet(NodeSet::new()));
}
if value.is_empty() {
unsafe {
let style = (*tctxt).style;
if !style.is_null() && !(*style).doc.is_null() {
let mut ns = NodeSet::new();
ns.push((*style).doc as *mut _xmlNode);
return Ok(XPathValue::NodeSet(ns));
}
}
return Ok(XPathValue::NodeSet(NodeSet::new()));
}
unsafe {
let mut base: *mut xmlChar = ptr::null_mut();
let inst = (*tctxt).inst;
if !inst.is_null() {
base = crate::abi::exports_tree::xmlNodeGetBase((*inst).doc, inst);
} else if !(*tctxt).style.is_null() && !(*(*tctxt).style).doc.is_null() {
base = crate::abi::exports_tree::xmlNodeGetBase(
(*(*tctxt).style).doc,
(*(*tctxt).style).doc as *mut _xmlNode,
);
}
let value_c = crate::xml::string::bytes_to_xmlstr(value.as_bytes());
let resolved = crate::abi::exports_uri::xmlBuildURI(
value_c as *const c_char,
base as *const c_char,
);
if !base.is_null() {
crate::abi::allocator::xmlFreeImpl(base as *mut c_void);
}
if !value_c.is_null() {
crate::abi::allocator::xmlFreeImpl(value_c as *mut c_void);
}
let load_uri: Vec<u8> = if resolved.is_null() {
value.as_bytes().to_vec()
} else {
let l = libc::strlen(resolved as *const libc::c_char);
core::slice::from_raw_parts(resolved as *const u8, l).to_vec()
};
if !resolved.is_null() {
crate::abi::allocator::xmlFreeImpl(resolved as *mut c_void);
}
let mut load_c = load_uri;
load_c.push(0);
let doc =
crate::xslt::documents::xsltLoadDocument(tctxt, load_c.as_ptr() as *const xmlChar);
if doc.is_null() {
return Ok(XPathValue::NodeSet(NodeSet::new()));
}
let mut ns = NodeSet::new();
ns.push(doc as *mut _xmlNode);
Ok(XPathValue::NodeSet(ns))
}
});
internal.register_function("key", |ctx, args| {
let tctxt = ctx.func_lookup_data as *mut _xsltTransformContext;
if tctxt.is_null() {
return Ok(XPathValue::NodeSet(NodeSet::new()));
}
let name_str = match args.first() {
Some(v) => v.as_string(),
None => return Err("key() requires a name argument".to_string()),
};
let value_str = match args.get(1) {
Some(XPathValue::NodeSet(ns)) => match ns.first() {
Some(n) => crate::xml::xpath::types::node_string_value(n),
None => return Ok(XPathValue::NodeSet(NodeSet::new())),
},
Some(v) => v.as_string(),
None => return Err("key() requires a value argument".to_string()),
};
let name_c = crate::xml::string::bytes_to_xmlstr(name_str.as_bytes());
let value_c = crate::xml::string::bytes_to_xmlstr(value_str.as_bytes());
if name_c.is_null() || value_c.is_null() {
if !name_c.is_null() {
crate::abi::allocator::xmlFreeImpl(name_c as *mut c_void);
}
if !value_c.is_null() {
crate::abi::allocator::xmlFreeImpl(value_c as *mut c_void);
}
return Ok(XPathValue::NodeSet(NodeSet::new()));
}
let ns = unsafe { crate::xslt::keys::xsltEvalKeyFunction(tctxt, name_c, value_c) };
crate::abi::allocator::xmlFreeImpl(name_c as *mut c_void);
crate::abi::allocator::xmlFreeImpl(value_c as *mut c_void);
if ns.is_null() {
return Ok(XPathValue::NodeSet(NodeSet::new()));
}
let mut out = NodeSet::new();
unsafe {
let node_nr = (*ns).nodeNr;
let node_tab = (*ns).nodeTab;
if !node_tab.is_null() {
for i in 0..node_nr as isize {
let n = *node_tab.add(i as usize);
if !n.is_null() {
out.push(n);
}
}
}
crate::abi::exports_xml2::xmlXPathFreeNodeSet(ns);
}
Ok(XPathValue::NodeSet(out))
});
internal.register_function("generate-id", |ctx, args| {
let node = match args.first() {
Some(XPathValue::NodeSet(ns)) => ns.first().unwrap_or(ctx.context_node),
_ => ctx.context_node,
};
if node.is_null() {
return Ok(XPathValue::String(String::new()));
}
let id = { format!("id{:p}", node) };
Ok(XPathValue::String(id))
});
internal.register_function("format-number", |ctx, args| {
if args.len() < 2 || args.len() > 3 {
return Err("format-number() requires 2 or 3 arguments".to_string());
}
let number = args[0].as_number();
let picture = args[1].as_string();
let tctxt = ctx.func_lookup_data as *mut _xsltTransformContext;
let mut fmt: *mut _xsltDecimalFormat = if tctxt.is_null() {
ptr::null_mut()
} else {
unsafe { (*(*tctxt).style).decimalFormat }
};
if args.len() == 3 {
let qname = args[2].as_string();
let (prefix, local) = match qname.split_once(':') {
Some((p, l)) => (Some(p), l),
None => (None, qname.as_str()),
};
let mut ns_uri: *const xmlChar = ptr::null();
let mut ncname_ok = true;
if let Some(p) = prefix {
let inst = unsafe { (*tctxt).inst };
if inst.is_null() {
ncname_ok = false;
} else {
let prefix_c = crate::xml::string::bytes_to_xmlstr(p.as_bytes());
let ns = unsafe {
crate::abi::exports_xml2::xmlSearchNs((*inst).doc, inst, prefix_c)
};
if !prefix_c.is_null() {
crate::abi::allocator::xmlFreeImpl(prefix_c as *mut c_void);
}
if ns.is_null() {
let msg = format!(
"format-number : No namespace found for QName '{}:{}'\n",
p, local
);
emit_transform_error_with_ctxt(tctxt, msg.as_bytes());
unsafe {
(*(*tctxt).style).errors += 1;
}
ncname_ok = false;
} else {
ns_uri = unsafe { (*ns).href };
}
}
}
if ncname_ok {
let local_c = crate::xml::string::bytes_to_xmlstr(local.as_bytes());
let style = unsafe { (*tctxt).style };
fmt = crate::xslt::numbering::decimal_format_by_qname(style, ns_uri, local_c);
if !local_c.is_null() {
crate::abi::allocator::xmlFreeImpl(local_c as *mut c_void);
}
if fmt.is_null() {
let msg = format!("format-number() : undeclared decimal format '{}'\n", qname);
emit_transform_error_with_ctxt(tctxt, msg.as_bytes());
return Err("Stack usage error".to_string());
}
}
}
let picture_c = crate::xml::string::bytes_to_xmlstr(picture.as_bytes());
let mut result: *mut xmlChar = ptr::null_mut();
let status = unsafe {
crate::xslt::numbering::xslt_format_number_conversion(
fmt,
picture_c,
number,
&mut result,
)
};
if !picture_c.is_null() {
crate::abi::allocator::xmlFreeImpl(picture_c as *mut c_void);
}
let _ = status;
let s = if result.is_null() {
String::new()
} else {
unsafe {
std::ffi::CStr::from_ptr(result as *const std::os::raw::c_char)
.to_string_lossy()
.into_owned()
}
};
if !result.is_null() {
crate::abi::allocator::xmlFreeImpl(result as *mut c_void);
}
Ok(XPathValue::String(s))
});
internal.register_function("system-property", |_ctx, args| {
let name = match args.first() {
Some(v) => v.as_string(),
None => return Err("system-property() requires an argument".to_string()),
};
let value = match name.as_str() {
"xsl:version" => "1.0",
"xsl:vendor" => "libxslt",
"xsl:vendor-url" => "http://xmlsoft.org/XSLT/",
_ => "",
};
Ok(XPathValue::String(value.to_string()))
});
internal.register_function("element-available", |_ctx, args| {
let name = match args.first() {
Some(v) => v.as_string(),
None => return Err("element-available() requires an argument".to_string()),
};
let exslt_elements = [
"exsl:document",
"exsl:node-set",
"exsl:object-type",
"func:function",
"func:result",
"func:script",
"dyn:element",
"dyn:attribute",
"dyn:call",
"dyn:evaluate",
];
let available = name.starts_with("xsl:")
|| exslt_elements.contains(&name.as_str())
|| matches!(
name.as_str(),
"apply-templates"
| "call-template"
| "apply-imports"
| "for-each"
| "value-of"
| "copy-of"
| "copy"
| "element"
| "attribute"
| "text"
| "comment"
| "processing-instruction"
| "number"
| "choose"
| "if"
| "variable"
| "param"
| "sort"
| "message"
| "fallback"
| "output"
| "decimal-format"
| "namespace-alias"
| "attribute-set"
| "key"
| "strip-space"
| "preserve-space"
| "import"
| "include"
| "stylesheet"
| "transform"
);
Ok(XPathValue::Boolean(available))
});
internal.register_function("function-available", |_ctx, args| {
let name = match args.first() {
Some(v) => v.as_string(),
None => return Err("function-available() requires an argument".to_string()),
};
let core = [
"last",
"position",
"count",
"id",
"local-name",
"namespace-uri",
"name",
"string",
"concat",
"starts-with",
"contains",
"substring-before",
"substring-after",
"substring",
"string-length",
"normalize-space",
"translate",
"boolean",
"not",
"true",
"false",
"lang",
"number",
"sum",
"floor",
"ceiling",
"round",
];
let xslt_fn = [
"document",
"key",
"generate-id",
"system-property",
"element-available",
"function-available",
"current",
"unparsed-entity-uri",
];
let exslt_available = crate::exslt::lookup(&name).is_some();
let local = name.rsplit(':').next().unwrap_or(&name);
Ok(XPathValue::Boolean(
core.contains(&local) || xslt_fn.contains(&local) || exslt_available,
))
});
internal.register_function("current", |ctx, _args| {
let node = ctx.context_node;
if node.is_null() {
return Ok(XPathValue::NodeSet(NodeSet::new()));
}
let mut ns = NodeSet::new();
ns.push(node);
Ok(XPathValue::NodeSet(ns))
});
for (name, f) in crate::exslt::iter_functions() {
internal.register_function(&name, f);
}
crate::exslt::functions::register_stylesheet_functions(
internal,
(*ctxt)
.style
.as_ref()
.map_or(std::ptr::null_mut(), |s| s.doc),
);
internal.function_lookup = Some(Box::new(|ctx: &XPathContext, name: &str| {
let (prefix, local) = name.split_once(':')?;
let tctxt = ctx.func_lookup_data as *mut _xsltTransformContext;
if tctxt.is_null() {
return None;
}
let style = unsafe { (*tctxt).style.as_ref() }?;
let style_doc = style.doc;
let prefix_c = crate::xml::string::bytes_to_xmlstr(prefix.as_bytes());
let root = unsafe { (*style_doc).children };
let ns = unsafe { crate::xml::tree::search_ns(style_doc, root, prefix_c) };
if !prefix_c.is_null() {
crate::abi::allocator::xmlFreeImpl(prefix_c as *mut c_void);
}
if ns.is_null() {
return None;
}
let href = unsafe { (*ns).href };
if href.is_null() {
return None;
}
let href_str = unsafe {
std::ffi::CStr::from_ptr(href as *const std::os::raw::c_char)
.to_string_lossy()
.into_owned()
};
let local_c = crate::xml::string::bytes_to_xmlstr(local.as_bytes());
let href_c = crate::xml::string::bytes_to_xmlstr(href_str.as_bytes());
let mut fnptr =
unsafe { crate::xslt::extensions::xsltFindExtFunction(tctxt, local_c, href_c) };
if fnptr.is_null() {
if let Some(modfn) = unsafe {
crate::abi::exports_xslt_ext::xsltExtModuleFunctionLookup(local_c, href_c)
} {
fnptr = modfn as usize as *mut c_void;
}
}
if !local_c.is_null() {
crate::abi::allocator::xmlFreeImpl(local_c as *mut c_void);
}
if !href_c.is_null() {
crate::abi::allocator::xmlFreeImpl(href_c as *mut c_void);
}
if fnptr.is_null() {
return None;
}
let f: Option<unsafe extern "C" fn(*mut c_void, c_int)> =
unsafe { std::mem::transmute(fnptr) };
let tctxt_addr = tctxt as usize;
let local_owned = local.to_string();
let href_owned = href_str.clone();
Some(Box::new(
move |_ctx: &mut XPathContext, args: &[XPathValue]| {
let t = tctxt_addr as *mut _xsltTransformContext;
let xpath_ctxt = unsafe { (*t).xpathCtxt };
if xpath_ctxt.is_null() {
return Err("XSLT: null XPath context".to_string());
}
unsafe {
crate::abi::exports_xml2::call_xslt_ext_function(
f,
t,
xpath_ctxt,
&local_owned,
Some(&href_owned),
args,
)
}
},
))
}));
}
pub const fn exslt_element_names() -> &'static [&'static str] {
&[
"exsl:document",
"exsl:node-set",
"exsl:object-type",
"func:function",
"func:result",
"func:script",
"dyn:element",
"dyn:attribute",
"dyn:call",
"dyn:evaluate",
]
}
pub fn exslt_function_names() -> Vec<String> {
crate::exslt::iter_functions()
.into_iter()
.map(|(n, _)| n)
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use core::ptr;
#[test]
fn test_new_context_null_style() {
unsafe {
assert!(xsltNewTransformContext(ptr::null_mut(), ptr::null_mut()).is_null());
}
}
#[test]
fn test_free_null() {
unsafe {
xsltFreeTransformContext(ptr::null_mut());
xsltFreeTransformResult(ptr::null_mut());
}
}
#[test]
fn test_apply_stylesheet_null() {
unsafe {
assert!(
xsltApplyStylesheet(ptr::null_mut(), ptr::null_mut(), ptr::null_mut()).is_null()
);
}
}
#[test]
fn test_end_to_end_simplified_stylesheet() {
unsafe {
let xsl = b"<?xml version=\"1.0\"?><html xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\" xsl:version=\"1.0\"><body><p>Hello</p></body></html>\0";
let style = crate::xslt::stylesheet::xsltParseStylesheetMemory(
xsl.as_ptr() as *const c_char,
(xsl.len() - 1) as c_int,
ptr::null(),
);
assert!(!style.is_null(), "stylesheett parse failed");
let src = b"<?xml version=\"1.0\"?><root><item>world</item></root>\0";
let doc = crate::abi::exports_xml2::xmlReadMemory(
src.as_ptr() as *const c_char,
(src.len() - 1) as c_int,
ptr::null(),
ptr::null(),
0,
);
assert!(!doc.is_null());
let result = xsltApplyStylesheet(style, doc, ptr::null_mut());
assert!(!result.is_null(), "apply failed");
let mut txt: *mut xmlChar = ptr::null_mut();
let mut len: c_int = 0;
let ret = crate::xslt::serialization::xsltSaveResultToString(
&mut txt, &mut len, result, style,
);
assert_eq!(ret, 0);
assert!(!txt.is_null());
let out = String::from_utf8_lossy(core::slice::from_raw_parts(txt, len as usize));
assert!(
out.contains("Hello"),
"result should contain the literal text, got: {}",
out
);
libc::free(txt as *mut libc::c_void);
crate::xml::tree::free_doc(result);
crate::xml::tree::free_doc(doc);
crate::xslt::stylesheet::xsltFreeStylesheet(style);
}
}
#[test]
fn test_end_to_end_template_transform() {
unsafe {
let xsl = b"<?xml version=\"1.0\"?>\n\
<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\n\
<xsl:template match=\"/\">\n\
<out><xsl:value-of select=\"/root/item\"/></out>\n\
</xsl:template>\n\
</xsl:stylesheet>\0";
let style = crate::xslt::stylesheet::xsltParseStylesheetMemory(
xsl.as_ptr() as *const c_char,
(xsl.len() - 1) as c_int,
ptr::null(),
);
assert!(!style.is_null(), "stylesheet parse failed");
let src = b"<?xml version=\"1.0\"?><root><item>world</item></root>\0";
let doc = crate::abi::exports_xml2::xmlReadMemory(
src.as_ptr() as *const c_char,
(src.len() - 1) as c_int,
ptr::null(),
ptr::null(),
0,
);
assert!(!doc.is_null());
let result = xsltApplyStylesheet(style, doc, ptr::null_mut());
assert!(!result.is_null(), "apply failed");
let mut txt: *mut xmlChar = ptr::null_mut();
let mut len: c_int = 0;
let ret = crate::xslt::serialization::xsltSaveResultToString(
&mut txt, &mut len, result, style,
);
assert_eq!(ret, 0);
let out = String::from_utf8_lossy(core::slice::from_raw_parts(txt, len as usize));
assert!(
out.contains("world"),
"result should contain the selected value, got: {}",
out
);
libc::free(txt as *mut libc::c_void);
crate::xml::tree::free_doc(result);
crate::xml::tree::free_doc(doc);
crate::xslt::stylesheet::xsltFreeStylesheet(style);
}
}
unsafe fn run_transform(xsl: &[u8], src: &[u8]) -> String {
let style = crate::xslt::stylesheet::xsltParseStylesheetMemory(
xsl.as_ptr() as *const c_char,
(xsl.len() - 1) as c_int,
ptr::null(),
);
assert!(!style.is_null(), "stylesheet parse failed");
let doc = crate::abi::exports_xml2::xmlReadMemory(
src.as_ptr() as *const c_char,
(src.len() - 1) as c_int,
ptr::null(),
ptr::null(),
0,
);
assert!(!doc.is_null());
let result = xsltApplyStylesheet(style, doc, ptr::null_mut());
assert!(!result.is_null(), "apply failed");
let mut txt: *mut xmlChar = ptr::null_mut();
let mut len: c_int = 0;
let ret =
crate::xslt::serialization::xsltSaveResultToString(&mut txt, &mut len, result, style);
assert_eq!(ret, 0);
let out =
String::from_utf8_lossy(core::slice::from_raw_parts(txt, len as usize)).into_owned();
libc::free(txt as *mut libc::c_void);
crate::xml::tree::free_doc(result);
crate::xml::tree::free_doc(doc);
crate::xslt::stylesheet::xsltFreeStylesheet(style);
out
}
#[test]
fn test_xslt_for_each() {
unsafe {
let xsl = b"<?xml version=\"1.0\"?>\
<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\
<xsl:template match=\"/\">\
<list><xsl:for-each select=\"/root/item\"><i><xsl:value-of select=\".\"/></i></xsl:for-each></list>\
</xsl:template>\
</xsl:stylesheet>\0";
let src =
b"<?xml version=\"1.0\"?><root><item>a</item><item>b</item><item>c</item></root>\0";
let out = run_transform(xsl, src);
assert!(out.contains("<i>a</i>"), "got: {}", out);
assert!(out.contains("<i>b</i>"), "got: {}", out);
assert!(out.contains("<i>c</i>"), "got: {}", out);
}
}
#[test]
fn test_xslt_core_functions_in_value_of() {
unsafe {
let xsl = b"<?xml version=\"1.0\"?>\
<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\
<xsl:template match=\"/\">\
<out>\
<cnt><xsl:value-of select=\"count(library/book)\"/></cnt>\
<sub><xsl:value-of select=\"substring('hello',1,2)\"/></sub>\
<str><xsl:value-of select=\"string(library/book[1]/title)\"/></str>\
</out>\
</xsl:template>\
</xsl:stylesheet>\0";
let src = b"<?xml version=\"1.0\"?>\
<library>\
<book><title>Rust</title></book>\
<book><title>XML</title></book>\
</library>\0";
let out = run_transform(xsl, src);
assert!(out.contains("<cnt>2</cnt>"), "count() wrong: {}", out);
assert!(out.contains("<sub>he</sub>"), "substring() wrong: {}", out);
assert!(out.contains("<str>Rust</str>"), "string() wrong: {}", out);
}
}
#[test]
fn test_xslt_avt_in_literal_attribute() {
unsafe {
let xsl = b"<?xml version=\"1.0\"?>\
<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\
<xsl:template match=\"/\">\
<out>\
<xsl:for-each select=\"library/book\">\
<book id=\"{@id}\" label=\"{{literal}}\"/>\
</xsl:for-each>\
</out>\
</xsl:template>\
</xsl:stylesheet>\0";
let src = b"<?xml version=\"1.0\"?>\
<library>\
<book id=\"b1\"/>\
<book id=\"b2\"/>\
</library>\0";
let out = run_transform(xsl, src);
assert!(
out.contains("<book id=\"b1\" label=\"{literal}\""),
"AVT not evaluated: {}",
out
);
assert!(
out.contains("<book id=\"b2\" label=\"{literal}\""),
"AVT not evaluated: {}",
out
);
}
}
#[test]
fn test_xslt_avt_in_xsl_element_name() {
unsafe {
let xsl = b"<?xml version=\"1.0\"?>\
<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\
<xsl:template match=\"/\">\
<out>\
<xsl:element name=\"el-{library/book/@id}\">text</xsl:element>\
</out>\
</xsl:template>\
</xsl:stylesheet>\0";
let src = b"<?xml version=\"1.0\"?>\
<library><book id=\"b1\"/></library>\0";
let out = run_transform(xsl, src);
assert!(
out.contains("<el-b1>"),
"xsl:element AVT not evaluated: {}",
out
);
}
}
#[test]
fn test_xslt_variable_inline_content_rtf() {
unsafe {
let xsl = b"<?xml version=\"1.0\"?>\
<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\
<xsl:variable name=\"rtf\"><nums><n>3</n><n>7</n></nums></xsl:variable>\
<xsl:template match=\"/\">\
<out><v><xsl:value-of select=\"$rtf\"/></v></out>\
</xsl:template>\
</xsl:stylesheet>\0";
let src = b"<?xml version=\"1.0\"?><root/>\0";
let out = run_transform(xsl, src);
assert!(out.contains("<v>37</v>"), "RTF string-value wrong: {}", out);
}
}
#[test]
fn test_xslt_exsl_node_set_on_rtf() {
unsafe {
crate::exslt::register_all();
let xsl = b"<?xml version=\"1.0\"?>\
<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\" xmlns:exsl=\"http://exslt.org/common\" xmlns:math=\"http://exslt.org/math\" extension-element-prefixes=\"exsl math\">\
<xsl:variable name=\"rtf\"><nums><n>3</n><n>7</n><n>1</n><n>9</n></nums></xsl:variable>\
<xsl:template match=\"/\">\
<out>\
<max><xsl:value-of select=\"math:max(exsl:node-set($rtf)/nums/n)\"/></max>\
<cnt><xsl:value-of select=\"count(exsl:node-set($rtf)/nums/n)\"/></cnt>\
</out>\
</xsl:template>\
</xsl:stylesheet>\0";
let src = b"<?xml version=\"1.0\"?><root/>\0";
let out = run_transform(xsl, src);
assert!(out.contains("<max>9</max>"), "math:max wrong: {}", out);
assert!(out.contains("<cnt>4</cnt>"), "count wrong: {}", out);
}
}
#[test]
fn test_xslt_extension_prefixes_excluded_from_result() {
unsafe {
crate::exslt::register_all();
let xsl = b"<?xml version=\"1.0\"?>\
<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\" xmlns:exsl=\"http://exslt.org/common\" extension-element-prefixes=\"exsl\">\
<xsl:template match=\"/\">\
<out><v><xsl:value-of select=\"count(exsl:node-set(/doc/b)/b)\"/></v></out>\
</xsl:template>\
</xsl:stylesheet>\0";
let src = b"<?xml version=\"1.0\"?><doc><b>x</b></doc>\0";
let out = run_transform(xsl, src);
assert!(
!out.contains("xmlns:exsl"),
"extension-element-prefixes leaked: {}",
out
);
let xsl2 = b"<?xml version=\"1.0\"?>\
<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\" xmlns:exsl=\"http://exslt.org/common\" exclude-result-prefixes=\"exsl\">\
<xsl:template match=\"/\">\
<out><v><xsl:value-of select=\"count(exsl:node-set(/doc/b)/b)\"/></v></out>\
</xsl:template>\
</xsl:stylesheet>\0";
let out2 = run_transform(xsl2, src);
assert!(
!out2.contains("xmlns:exsl"),
"exclude-result-prefixes leaked: {}",
out2
);
}
}
#[test]
fn test_xslt_if_node_set_test() {
unsafe {
let xsl = b"<?xml version=\"1.0\"?>\
<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\
<xsl:template match=\"/\">\
<out>\
<xsl:for-each select=\"library/book\">\
<b><xsl:if test=\"author\">A</xsl:if><xsl:if test=\"missing\">M</xsl:if></b>\
</xsl:for-each>\
</out>\
</xsl:template>\
</xsl:stylesheet>\0";
let src = b"<?xml version=\"1.0\"?>\
<library><book><author>x</author></book><book/></library>\0";
let out = run_transform(xsl, src);
assert!(out.contains("<b>A</b>"), "node-set test false: {}", out);
assert!(out.contains("<b/>"), "missing-node test true: {}", out);
}
}
#[test]
fn test_xslt_attribute_string_value() {
unsafe {
let xsl = b"<?xml version=\"1.0\"?>\
<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\
<xsl:template match=\"/\">\
<out>\
<x><xsl:value-of select=\"string(library/book[1]/@id)\"/></x>\
<y><xsl:value-of select=\"count(library/book[@id='b2'])\"/></y>\
</out>\
</xsl:template>\
</xsl:stylesheet>\0";
let src = b"<?xml version=\"1.0\"?>\
<library><book id=\"b1\"/><book id=\"b2\"/></library>\0";
let out = run_transform(xsl, src);
assert!(
out.contains("<x>b1</x>"),
"attr string-value wrong: {}",
out
);
assert!(out.contains("<y>1</y>"), "attr predicate wrong: {}", out);
}
}
#[test]
fn test_xslt_sort_descending() {
unsafe {
let xsl = b"<?xml version=\"1.0\"?>\
<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\
<xsl:template match=\"/\">\
<out>\
<xsl:for-each select=\"library/book\">\
<xsl:sort select=\"title\" order=\"descending\"/>\
<i><xsl:value-of select=\"title\"/></i>\
</xsl:for-each>\
</out>\
</xsl:template>\
</xsl:stylesheet>\0";
let src = b"<?xml version=\"1.0\"?>\
<library><book><title>Alpha</title></book><book><title>Gamma</title></book><book><title>Beta</title></book></library>\0";
let out = run_transform(xsl, src);
let gamma = out.find("<i>Gamma</i>").unwrap();
let beta = out.find("<i>Beta</i>").unwrap();
let alpha = out.find("<i>Alpha</i>").unwrap();
assert!(gamma < beta && beta < alpha, "not descending: {}", out);
}
}
#[test]
fn test_xslt_key_function() {
unsafe {
let xsl = b"<?xml version=\"1.0\"?>\
<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\
<xsl:key name=\"byAuthor\" match=\"book\" use=\"author\"/>\
<xsl:template match=\"/\">\
<out><k><xsl:value-of select=\"key('byAuthor', 'Smith')/title\"/></k></out>\
</xsl:template>\
</xsl:stylesheet>\0";
let src = b"<?xml version=\"1.0\"?>\
<library><book><title>A</title><author>Smith</author></book><book><title>B</title><author>Jones</author></book></library>\0";
let out = run_transform(xsl, src);
assert!(out.contains("<k>A</k>"), "key() wrong: {}", out);
}
}
#[test]
fn test_xslt_call_template_with_params() {
unsafe {
let xsl = b"<?xml version=\"1.0\"?>\
<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\
<xsl:template match=\"/\">\
<out>\
<xsl:call-template name=\"greet\">\
<xsl:with-param name=\"who\" select=\"'World'\"/>\
</xsl:call-template>\
</out>\
</xsl:template>\
<xsl:template name=\"greet\">\
<xsl:param name=\"who\" select=\"'nobody'\"/>\
<g>Hello <xsl:value-of select=\"$who\"/>!</g>\
</xsl:template>\
</xsl:stylesheet>\0";
let src = b"<?xml version=\"1.0\"?><root/>\0";
let out = run_transform(xsl, src);
assert!(out.contains("Hello World"), "with-param lost: {}", out);
}
}
#[test]
fn test_xslt_html_method_meta_charset() {
unsafe {
let xsl = b"<?xml version=\"1.0\"?>\
<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\
<xsl:output method=\"html\" indent=\"yes\"/>\
<xsl:template match=\"/\">\
<html><head><title>T</title></head><body><p>x</p></body></html>\
</xsl:template>\
</xsl:stylesheet>\0";
let src = b"<?xml version=\"1.0\"?><root/>\0";
let out = run_transform(xsl, src);
assert!(
out.contains("<meta charset=\"UTF-8\">"),
"meta charset missing: {}",
out
);
assert!(!out.contains(" <head>"), "unexpected indent: {}", out);
}
}
#[test]
fn test_xslt_if() {
unsafe {
let xsl = b"<?xml version=\"1.0\"?>\
<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\
<xsl:template match=\"/\">\
<xsl:if test=\"/root/item = 'yes'\"><yes/></xsl:if>\
<xsl:if test=\"/root/item = 'no'\"><no/></xsl:if>\
</xsl:template>\
</xsl:stylesheet>\0";
let src = b"<?xml version=\"1.0\"?><root><item>yes</item></root>\0";
let out = run_transform(xsl, src);
assert!(out.contains("<yes/>"), "got: {}", out);
assert!(!out.contains("<no/>"), "got: {}", out);
}
}
#[test]
fn test_xslt_choose() {
unsafe {
let xsl = b"<?xml version=\"1.0\"?>\
<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\
<xsl:template match=\"/\">\
<xsl:choose>\
<xsl:when test=\"/root/item = 'a'\"><chosen>a</chosen></xsl:when>\
<xsl:when test=\"/root/item = 'b'\"><chosen>b</chosen></xsl:when>\
<xsl:otherwise><chosen>other</chosen></xsl:otherwise>\
</xsl:choose>\
</xsl:template>\
</xsl:stylesheet>\0";
let src = b"<?xml version=\"1.0\"?><root><item>b</item></root>\0";
let out = run_transform(xsl, src);
assert!(out.contains("<chosen>b</chosen>"), "got: {}", out);
}
}
#[test]
fn test_xslt_variable_and_call_template() {
unsafe {
let xsl = b"<?xml version=\"1.0\"?>\
<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\
<xsl:variable name=\"greeting\" select=\"'Hello'\"/>\
<xsl:template match=\"/\">\
<xsl:call-template name=\"say\"/>\
</xsl:template>\
<xsl:template name=\"say\">\
<msg><xsl:value-of select=\"$greeting\"/> <xsl:value-of select=\"/root/name\"/></msg>\
</xsl:template>\
</xsl:stylesheet>\0";
let src = b"<?xml version=\"1.0\"?><root><name>World</name></root>\0";
let out = run_transform(xsl, src);
assert!(out.contains("HelloWorld"), "got: {}", out);
}
}
#[test]
fn test_xslt_text_preserves_whitespace() {
unsafe {
let xsl = b"<?xml version=\"1.0\"?>\
<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\
<xsl:template match=\"/\">\
<msg><xsl:value-of select=\"'Hello'\"/><xsl:text> </xsl:text><xsl:value-of select=\"/root/name\"/></msg>\
</xsl:template>\
</xsl:stylesheet>\0";
let src = b"<?xml version=\"1.0\"?><root><name>World</name></root>\0";
let out = run_transform(xsl, src);
assert!(out.contains("Hello World"), "got: {}", out);
}
}
#[test]
fn test_xslt_element_and_attribute() {
unsafe {
let xsl = b"<?xml version=\"1.0\"?>\
<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\
<xsl:template match=\"/\">\
<xsl:element name=\"custom\">\
<xsl:attribute name=\"attr\">value</xsl:attribute>\
<xsl:value-of select=\"/root/item\"/>\
</xsl:element>\
</xsl:template>\
</xsl:stylesheet>\0";
let src = b"<?xml version=\"1.0\"?><root><item>data</item></root>\0";
let out = run_transform(xsl, src);
assert!(out.contains("custom"), "got: {}", out);
assert!(out.contains("attr=\"value\""), "got: {}", out);
assert!(out.contains("data"), "got: {}", out);
}
}
#[test]
fn test_xslt_apply_templates_with_select() {
unsafe {
let xsl = b"<?xml version=\"1.0\"?>\
<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\
<xsl:template match=\"/\">\
<out><xsl:apply-templates select=\"/root/item\"/></out>\
</xsl:template>\
<xsl:template match=\"item\"><item><xsl:value-of select=\".\"/></item></xsl:template>\
</xsl:stylesheet>\0";
let src = b"<?xml version=\"1.0\"?><root><item>alpha</item><item>beta</item></root>\0";
let out = run_transform(xsl, src);
assert!(out.contains("<item>alpha</item>"), "got: {}", out);
assert!(out.contains("<item>beta</item>"), "got: {}", out);
}
}
#[test]
fn test_xslt_text_and_comment_and_pi() {
unsafe {
let xsl = b"<?xml version=\"1.0\"?>\
<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\
<xsl:template match=\"/\">\
<xsl:text>plain</xsl:text>\
<xsl:comment>a comment</xsl:comment>\
<xsl:processing-instruction name=\"target\">pi-data</xsl:processing-instruction>\
</xsl:template>\
</xsl:stylesheet>\0";
let src = b"<?xml version=\"1.0\"?><root/>\0";
let out = run_transform(xsl, src);
assert!(out.contains("plain"), "got: {}", out);
assert!(out.contains("<!--a comment-->"), "got: {}", out);
assert!(out.contains("<?target pi-data?>"), "got: {}", out);
}
}
#[test]
fn test_xslt_copy_and_copy_of() {
unsafe {
let xsl = b"<?xml version=\"1.0\"?>\
<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\
<xsl:template match=\"/\">\
<xsl:copy-of select=\"/root/item\"/>\
<xsl:copy-of select=\"'literal'\"/>\
</xsl:template>\
</xsl:stylesheet>\0";
let src = b"<?xml version=\"1.0\"?><root><item>copied</item></root>\0";
let out = run_transform(xsl, src);
assert!(out.contains("<item>copied</item>"), "got: {}", out);
assert!(out.contains("literal"), "got: {}", out);
}
}
#[test]
fn test_xslt_number() {
unsafe {
let xsl = b"<?xml version=\"1.0\"?>\
<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\
<xsl:template match=\"/\">\
<n><xsl:number value=\"42\"/></n>\
<r><xsl:number value=\"9\" format=\"I\"/></r>\
</xsl:template>\
</xsl:stylesheet>\0";
let src = b"<?xml version=\"1.0\"?><root/>\0";
let out = run_transform(xsl, src);
assert!(out.contains("<n>42</n>"), "got: {}", out);
assert!(out.contains("<r>IX</r>"), "got: {}", out);
}
}
}