Created
December 15, 2023 14:40
-
-
Save hhalex/aac4717c74b4de575602061f185b12e9 to your computer and use it in GitHub Desktop.
Handlebars rust template helper to execute xpath queries against xml documents
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use handlebars::{ | |
to_json, BlockContext, BlockParams, Context, Handlebars, Helper, HelperDef, HelperResult, | |
Output, PathAndJson, RenderContext, RenderError, RenderErrorReason, Renderable, | |
}; | |
use serde_json::value::Value as Json; | |
use std::collections::HashMap; | |
use std::fmt::format; | |
use sxd_document::parser; | |
use sxd_xpath::evaluate_xpath; | |
use sxd_xpath::nodeset::Node; | |
fn create_block<'rc>(param: &PathAndJson<'rc>) -> BlockContext<'rc> { | |
let mut block = BlockContext::new(); | |
if let Some(new_path) = param.context_path() { | |
*block.base_path_mut() = new_path.clone(); | |
} else { | |
// use clone for now | |
block.set_base_value(param.value().clone()); | |
} | |
block | |
} | |
#[inline] | |
fn copy_on_push_vec<T>(input: &[T], el: T) -> Vec<T> | |
where | |
T: Clone, | |
{ | |
let mut new_vec = Vec::with_capacity(input.len() + 1); | |
new_vec.extend_from_slice(input); | |
new_vec.push(el); | |
new_vec | |
} | |
#[inline] | |
fn extend(base: &Vec<String>, slice: &[String]) -> Vec<String> { | |
let mut v: Vec<String> = base.clone(); | |
for i in slice { | |
v.push(i.to_owned()); | |
} | |
v | |
} | |
fn update_block_context( | |
block: &mut BlockContext<'_>, | |
base_path: &Option<Vec<String>>, | |
relative_path: String, | |
is_first: bool, | |
value: &Json, | |
) { | |
if let Some(p) = base_path { | |
if is_first { | |
*block.base_path_mut() = copy_on_push_vec(p, relative_path); | |
} else if let Some(ptr) = block.base_path_mut().last_mut() { | |
*ptr = relative_path; | |
} | |
} else { | |
block.set_base_value(value.clone()); | |
} | |
} | |
fn set_block_param<'rc>( | |
block: &mut BlockContext<'rc>, | |
h: &Helper<'rc>, | |
base_path: &Option<Vec<String>>, | |
k: &Json, | |
v: &Json, | |
) -> Result<(), RenderError> { | |
if let Some(bp_val) = h.block_param() { | |
let mut params = BlockParams::new(); | |
if base_path.is_some() { | |
params.add_path(bp_val, Vec::with_capacity(0))?; | |
} else { | |
params.add_value(bp_val, v.clone())?; | |
} | |
block.set_block_params(params); | |
} else if let Some((bp_val, bp_key)) = h.block_param_pair() { | |
let mut params = BlockParams::new(); | |
if base_path.is_some() { | |
params.add_path(bp_val, Vec::with_capacity(0))?; | |
} else { | |
params.add_value(bp_val, v.clone())?; | |
} | |
params.add_value(bp_key, k.clone())?; | |
block.set_block_params(params); | |
} | |
Ok(()) | |
} | |
#[derive(Clone, Copy)] | |
pub struct XpathHelper; | |
impl HelperDef for XpathHelper { | |
fn call<'reg: 'rc, 'rc>( | |
&self, | |
h: &Helper<'rc>, | |
r: &'reg Handlebars<'reg>, | |
ctx: &'rc Context, | |
rc: &mut RenderContext<'reg, 'rc>, | |
out: &mut dyn Output, | |
) -> HelperResult { | |
let body = h | |
.param(0) | |
.ok_or(RenderErrorReason::ParamNotFoundForIndex("xpath", 0))?; | |
let query = h | |
.param(1) | |
.ok_or(RenderErrorReason::ParamNotFoundForIndex("xpath", 1))?; | |
let template = h.template(); | |
match template { | |
Some(t) => match *body.value() { | |
Json::String(ref body_str) | |
if !body_str.is_empty() || (body_str.is_empty() && h.inverse().is_none()) => | |
{ | |
let block_context = create_block(body); | |
rc.push_block(block_context); | |
let package = parser::parse(body_str) | |
.ok() | |
.ok_or(RenderErrorReason::Other("problem parsing xml".to_string()))?; | |
let document = package.as_document(); | |
let query_xpath = match *query.value() { | |
Json::String(ref query_str) => Ok(query_str), | |
_ => Err(RenderErrorReason::InvalidParamType( | |
"xpath query must be a string", | |
)), | |
} | |
.ok() | |
.ok_or(RenderErrorReason::Other( | |
"problem parsing xpath query".to_string(), | |
))?; | |
let value = evaluate_xpath(&document, query_xpath).ok().ok_or( | |
RenderErrorReason::Other("problem evaluating xpath query".to_string()), | |
)?; | |
match value { | |
sxd_xpath::Value::Nodeset(nodeset) => { | |
let len = nodeset.size(); | |
let array_path = body | |
.context_path() | |
.map(move |v| extend(v, &[query_xpath.clone()])); | |
for (i, v) in nodeset.iter().enumerate().take(len) { | |
if let Some(ref mut block) = rc.block_mut() { | |
let is_first = i == 0usize; | |
let is_last = i == len - 1; | |
let el_json = to_json( | |
v.element() | |
.unwrap() | |
.attributes() | |
.iter() | |
.map(|attr| { | |
(attr.name().local_part().to_string(), attr.value()) | |
}) | |
.collect::<HashMap<String, &str>>(), | |
); | |
let index = to_json(i); | |
block.set_local_var("first", to_json(is_first)); | |
block.set_local_var("last", to_json(is_last)); | |
block.set_local_var("index", index.clone()); | |
block.set_local_var("el", el_json.clone()); | |
update_block_context( | |
block, | |
&array_path, | |
i.to_string(), | |
is_first, | |
&el_json, | |
); | |
set_block_param(block, h, &array_path, &index, &el_json)?; | |
dbg!(block.clone()); | |
} | |
t.render(r, ctx, rc, out)?; | |
} | |
} | |
sxd_xpath::Value::String(v) => { | |
if let Some(ref mut block) = rc.block_mut() { | |
block.set_local_var("this", to_json(v)); | |
} | |
t.render(r, ctx, rc, out)?; | |
} | |
sxd_xpath::Value::Number(v) => { | |
if let Some(ref mut block) = rc.block_mut() { | |
block.set_local_var("this", to_json(v)); | |
} | |
t.render(r, ctx, rc, out)?; | |
} | |
sxd_xpath::Value::Boolean(v) => { | |
if let Some(ref mut block) = rc.block_mut() { | |
block.set_local_var("this", to_json(v)); | |
} | |
t.render(r, ctx, rc, out)?; | |
} | |
} | |
rc.pop_block(); | |
Ok(()) | |
} | |
_ => { | |
if let Some(else_template) = h.inverse() { | |
else_template.render(r, ctx, rc, out) | |
} else if r.strict_mode() { | |
Err(RenderError::strict_error(body.relative_path())) | |
} else { | |
Ok(()) | |
} | |
} | |
}, | |
None => Ok(()), | |
} | |
} | |
} | |
pub static XPATH_HELPER: XpathHelper = XpathHelper; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment