libxml_rs/xslt/sorting/
mod.rs1use crate::abi::allocator::xmlFreeImpl;
27use crate::abi::exports_xml2::{
28 xmlStrcmp, xmlXPathCastStringToNumber, xmlXPathCastToString, xmlXPathCmpNodes,
29 xmlXPathEvalExpression, xmlXPathFreeObject,
30};
31use crate::abi::structs::*;
32use crate::abi::types::xmlElementType::XML_ATTRIBUTE_NODE;
33use crate::abi::types::*;
34use crate::xml::tree::node_get_content;
35use std::os::raw::{c_char, c_int};
36use std::ptr;
37
38pub const XSLT_SORT_TEXT: c_int = 0;
40pub const XSLT_SORT_NUMBER: c_int = 1;
41
42pub const XSLT_SORT_ASCENDING: c_int = 0;
44pub const XSLT_SORT_DESCENDING: c_int = 1;
45
46pub const XSLT_SORT_CASE_UPPER_FIRST: c_int = 0;
48pub const XSLT_SORT_CASE_LOWER_FIRST: c_int = 1;
49
50pub unsafe fn xsltCompileSort(style: *mut _xsltStylesheet, inst: *mut _xmlNode) -> *mut _xsltSort {
57 if style.is_null() || inst.is_null() {
58 return ptr::null_mut();
59 }
60 let s = libc::calloc(1, core::mem::size_of::<_xsltSort>()) as *mut _xsltSort;
61 if s.is_null() {
62 return ptr::null_mut();
63 }
64 (*s).inst = inst;
65 (*s).style = style;
66 (*s).next = ptr::null_mut();
67 (*s).isText = 1; (*s).hasConst = 0;
69
70 let mut prop = (*inst).properties;
72 while !prop.is_null() {
73 let name = (*prop).name;
74 if !name.is_null() {
75 let value = node_get_content((*prop).children);
76 if !value.is_null() {
77 let name_str = crate::abi::versioning::c_str_to_bytes(name as *const c_char);
78 match name_str {
79 Some(b"select") => {
80 if (*s).select.is_null() {
81 (*s).select = value;
82 } else {
83 libc::free(value as *mut libc::c_void);
84 }
85 }
86 Some(b"lang") => {
87 (*s).lang = value;
88 }
89 Some(b"data-type") => {
90 (*s).dataType = value;
91 let v = crate::abi::versioning::c_str_to_bytes(value as *const c_char);
92 if v == Some(b"number") {
93 (*s).isText = 0;
94 }
95 }
96 Some(b"order") => {
97 (*s).order = value;
98 }
99 Some(b"case-order") => {
100 (*s).caseOrder = value;
101 }
102 _ => {
103 libc::free(value as *mut libc::c_void);
104 }
105 }
106 }
107 }
108 prop = (*prop).next;
109 }
110 s
111}
112
113pub unsafe fn xsltFreeSort(sort: *mut _xsltSort) {
119 if sort.is_null() {
120 return;
121 }
122 if !(*sort).select.is_null() {
125 libc::free((*sort).select as *mut libc::c_void);
126 }
127 if !(*sort).lang.is_null() {
128 libc::free((*sort).lang as *mut libc::c_void);
129 }
130 if !(*sort).dataType.is_null() {
131 libc::free((*sort).dataType as *mut libc::c_void);
132 }
133 if !(*sort).order.is_null() {
134 libc::free((*sort).order as *mut libc::c_void);
135 }
136 if !(*sort).caseOrder.is_null() {
137 libc::free((*sort).caseOrder as *mut libc::c_void);
138 }
139 (*sort).next = ptr::null_mut();
140 xmlFreeImpl(sort as *mut libc::c_void);
141}
142
143pub unsafe fn xsltFreeSortList(sorts: *mut _xsltSort) {
149 let mut cur = sorts;
150 while !cur.is_null() {
151 let next = (*cur).next;
152 xsltFreeSort(cur);
153 cur = next;
154 }
155}
156
157unsafe fn sort_string_value(node: *mut _xmlNode) -> *mut xmlChar {
164 if node.is_null() {
165 return ptr::null_mut();
166 }
167 let typ = (*node).type_;
168 if typ == XML_ATTRIBUTE_NODE as i32 {
169 let content = (*node).children;
171 if !content.is_null() {
172 return node_get_content(content);
173 }
174 return ptr::null_mut();
175 }
176 node_get_content(node)
177}
178
179pub(crate) unsafe fn xsltEvalSortKey(
189 ctxt: *mut _xsltTransformContext,
190 node: *mut _xmlNode,
191 sort: *mut _xsltSort,
192) -> *mut xmlChar {
193 if ctxt.is_null() || node.is_null() || sort.is_null() {
194 return ptr::null_mut();
195 }
196 if (*sort).select.is_null() {
198 return sort_string_value(node);
199 }
200 let xpath_ctxt = (*ctxt).xpathCtxt;
202 if xpath_ctxt.is_null() {
203 return sort_string_value(node);
204 }
205 let saved_node = (*xpath_ctxt).node;
208 let saved_doc = (*xpath_ctxt).doc;
209 (*xpath_ctxt).node = node;
210 (*xpath_ctxt).doc = (*(*ctxt).document).doc;
211 let internal = (*xpath_ctxt).extra as *mut crate::xml::xpath::context::XPathContext;
212 if !internal.is_null() {
213 (*internal).context_node = node;
214 (*internal).document = (*(*ctxt).document).doc;
215 }
216 let select = (*sort).select;
217 let xpath_obj = xmlXPathEvalExpression(select, xpath_ctxt);
218 (*xpath_ctxt).node = saved_node;
219 (*xpath_ctxt).doc = saved_doc;
220 if !internal.is_null() {
221 (*internal).context_node = saved_node;
222 (*internal).document = saved_doc;
223 }
224 if xpath_obj.is_null() {
225 return ptr::null_mut();
226 }
227 let result = xmlXPathCastToString(xpath_obj);
228 xmlXPathFreeObject(xpath_obj);
229 result
230}
231
232pub unsafe fn xsltCompareSingle(
246 ctxt: *mut _xsltTransformContext,
247 a: *mut _xmlNode,
248 b: *mut _xmlNode,
249 sort: *mut _xsltSort,
250) -> c_int {
251 if a.is_null() || b.is_null() || sort.is_null() {
252 return 0;
253 }
254 let mut result: c_int = 0;
255 let a_key = xsltEvalSortKey(ctxt, a, sort);
256 let b_key = xsltEvalSortKey(ctxt, b, sort);
257
258 let a_str: *const xmlChar = if a_key.is_null() { ptr::null() } else { a_key };
259 let b_str: *const xmlChar = if b_key.is_null() { ptr::null() } else { b_key };
260
261 if (*sort).isText != 0 {
262 result = match (a_str.is_null(), b_str.is_null()) {
264 (true, true) => 0,
265 (true, false) => -1,
266 (false, true) => 1,
267 (false, false) => {
268 let cmp = xmlStrcmp(a_str, b_str);
271 cmp
272 }
273 };
274 } else {
275 let a_num = if a_str.is_null() {
277 f64::NAN
278 } else {
279 xmlXPathCastStringToNumber(a_str)
280 };
281 let b_num = if b_str.is_null() {
282 f64::NAN
283 } else {
284 xmlXPathCastStringToNumber(b_str)
285 };
286 if a_num.is_nan() && b_num.is_nan() {
287 result = 0;
288 } else if a_num.is_nan() {
289 result = 1; } else if b_num.is_nan() {
291 result = -1;
292 } else if a_num < b_num {
293 result = -1;
294 } else if a_num > b_num {
295 result = 1;
296 } else {
297 result = 0;
298 }
299 }
300
301 let order = (*sort).order;
303 if !order.is_null() {
304 let o = crate::abi::versioning::c_str_to_bytes(order as *const c_char);
305 if o == Some(b"descending") {
306 result = -result;
307 }
308 }
309
310 if !a_key.is_null() {
311 libc::free(a_key as *mut libc::c_void);
312 }
313 if !b_key.is_null() {
314 libc::free(b_key as *mut libc::c_void);
315 }
316 result
317}
318
319pub unsafe fn xsltCompareNodes(
326 ctxt: *mut _xsltTransformContext,
327 a: *mut _xmlNode,
328 b: *mut _xmlNode,
329 sorts: *mut _xsltSort,
330) -> c_int {
331 if a.is_null() || b.is_null() || sorts.is_null() {
332 return 0;
333 }
334 let mut cur = sorts;
335 while !cur.is_null() {
336 let cmp = xsltCompareSingle(ctxt, a, b, cur);
337 if cmp != 0 {
338 return cmp;
339 }
340 cur = (*cur).next;
341 }
342 if a == b {
345 return 0;
346 }
347 xmlXPathCmpNodes(a, b)
348}
349
350pub unsafe fn xsltSortNodeSet(
361 ctxt: *mut _xsltTransformContext,
362 nodes: *mut _xmlNodeSet,
363 sorts: *mut _xsltSort,
364) {
365 if ctxt.is_null() || nodes.is_null() || sorts.is_null() {
366 return;
367 }
368 let nr = (*nodes).nodeNr;
369 if nr <= 1 {
370 return;
371 }
372 let tab = (*nodes).nodeTab;
373 if tab.is_null() {
374 return;
375 }
376
377 if nr < 32 {
381 let mut i = 1usize;
383 while i < nr as usize {
384 let key = *tab.offset(i as isize);
385 let mut j = i as isize - 1;
386 while j >= 0 {
387 let cur = *tab.offset(j);
388 if xsltCompareNodes(ctxt, cur, key, sorts) <= 0 {
389 break;
390 }
391 *tab.offset((j + 1) as isize) = cur;
392 j -= 1;
393 }
394 *tab.offset((j + 1) as isize) = key;
395 i += 1;
396 }
397 } else {
398 let mut indices: Vec<usize> = (0..nr as usize).collect();
400 quicksort_indices(ctxt, tab, &mut indices, sorts);
401 for (new_pos, old_idx) in indices.iter().enumerate() {
402 let old_ptr = *tab.offset(*old_idx as isize);
403 *tab.offset(new_pos as isize) = old_ptr;
404 }
405 }
406}
407
408unsafe fn quicksort_indices(
410 ctxt: *mut _xsltTransformContext,
411 tab: *mut *mut _xmlNode,
412 indices: &mut [usize],
413 sorts: *mut _xsltSort,
414) {
415 if indices.len() <= 1 {
416 return;
417 }
418 let pivot = indices[indices.len() / 2];
419 let mut less: Vec<usize> = Vec::new();
420 let mut greater: Vec<usize> = Vec::new();
421 for (i, idx) in indices.iter().enumerate() {
422 if *idx == pivot {
423 continue;
424 }
425 let cmp = xsltCompareNodes(
426 ctxt,
427 *tab.offset(*idx as isize),
428 *tab.offset(pivot as isize),
429 sorts,
430 );
431 if cmp <= 0 {
432 less.push(*idx);
433 } else {
434 greater.push(*idx);
435 }
436 let _ = i;
437 }
438 let pivot_pos = less.len();
439 quicksort_indices(ctxt, tab, &mut less, sorts);
440 quicksort_indices(ctxt, tab, &mut greater, sorts);
441 for (i, v) in less.into_iter().enumerate() {
442 indices[i] = v;
443 }
444 indices[pivot_pos] = pivot;
445 for (i, v) in greater.into_iter().enumerate() {
446 indices[pivot_pos + 1 + i] = v;
447 }
448}
449
450#[cfg(test)]
453mod tests {
454 use super::*;
455 use core::ptr;
456
457 #[test]
458 fn test_constants() {
459 assert_eq!(XSLT_SORT_TEXT, 0);
460 assert_eq!(XSLT_SORT_NUMBER, 1);
461 assert_eq!(XSLT_SORT_ASCENDING, 0);
462 assert_eq!(XSLT_SORT_DESCENDING, 1);
463 assert_eq!(XSLT_SORT_CASE_UPPER_FIRST, 0);
464 assert_eq!(XSLT_SORT_CASE_LOWER_FIRST, 1);
465 }
466
467 #[test]
468 fn test_compile_sort_null() {
469 unsafe {
470 assert!(xsltCompileSort(ptr::null_mut(), ptr::null_mut()).is_null());
471 }
472 }
473
474 #[test]
475 fn test_free_sort_null() {
476 unsafe {
477 xsltFreeSort(ptr::null_mut());
478 xsltFreeSortList(ptr::null_mut());
479 }
480 }
481}