1use jrsonnet_evaluator::{2 Either, ObjValue, Result, ResultExt, Val, bail, in_description_frame,3 manifest::{ManifestFormat, ToStringFormat},4 typed::{ComplexValType, Either2, FromUntyped, Typed, ValType},5 val::ArrValue,6};78pub struct XmlJsonmlFormat {9 force_closing: bool,10}11impl XmlJsonmlFormat {12 pub fn std_to_xml() -> Self {13 Self {14 force_closing: true,15 }16 }17 pub fn cli() -> Self {18 Self {19 force_closing: false,20 }21 }22}2324enum JSONMLValue {25 Tag {26 tag: String,27 attrs: ObjValue,28 children: Vec<JSONMLValue>,29 },30 String(String),31}32impl Typed for JSONMLValue {33 const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Arr);34}35impl FromUntyped for JSONMLValue {36 fn from_untyped(untyped: Val) -> Result<Self> {37 let val = <Either![ArrValue, String]>::from_untyped(untyped)38 .description("parsing JSONML value (an array or string)")?;39 let arr = match val {40 Either2::A(a) => a,41 Either2::B(s) => return Ok(Self::String(s)),42 };43 if arr.is_empty() {44 bail!("JSONML value should have tag (array length should be >=1)");45 }46 let tag = String::from_untyped(47 arr.get(0)48 .description("getting JSONML tag")?49 .expect("length checked"),50 )51 .description("parsing JSONML tag")?;5253 let (has_attrs, attrs) = if arr.len() >= 2 {54 let maybe_attrs = arr55 .get(1)56 .with_description(|| "getting JSONML attrs")?57 .expect("length checked");58 if let Val::Obj(attrs) = maybe_attrs {59 (true, attrs)60 } else {61 (false, ObjValue::empty())62 }63 } else {64 (false, ObjValue::empty())65 };66 Ok(Self::Tag {67 tag,68 attrs,69 children: in_description_frame(70 || "parsing children".to_owned(),71 || {72 FromUntyped::from_untyped(Val::Arr(arr.slice(73 Some(if has_attrs { 2 } else { 1 }),74 None,75 None,76 )))77 },78 )?,79 })80 }81}8283impl ManifestFormat for XmlJsonmlFormat {84 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {85 let val = JSONMLValue::from_untyped(val).with_description(|| "parsing JSONML value")?;86 manifest_jsonml(&val, buf, self)87 }88}8990fn manifest_jsonml(v: &JSONMLValue, buf: &mut String, opts: &XmlJsonmlFormat) -> Result<()> {91 match v {92 JSONMLValue::Tag {93 tag,94 attrs,95 children,96 } => {97 let has_children = !children.is_empty();98 buf.push('<');99 buf.push_str(tag);100 attrs.run_assertions()?;101 for (key, value) in attrs.iter(102 103 #[cfg(feature = "exp-preserve-order")]104 false,105 ) {106 buf.push(' ');107 buf.push_str(&key);108 buf.push('=');109 buf.push('"');110 let value = value?;111 let value = if let Val::Str(s) = value {112 s.to_string()113 } else {114 ToStringFormat.manifest(value)?115 };116 escape_string_xml_buf(&value, buf);117 buf.push('"');118 }119 if !has_children && !opts.force_closing {120 buf.push('/');121 }122 buf.push('>');123 for child in children {124 manifest_jsonml(child, buf, opts)?;125 }126 if has_children || opts.force_closing {127 buf.push('<');128 buf.push('/');129 buf.push_str(tag);130 buf.push('>');131 }132 Ok(())133 }134 JSONMLValue::String(s) => {135 escape_string_xml_buf(s, buf);136 Ok(())137 }138 }139}140141pub fn escape_string_xml(str: &str) -> String {142 let mut out = String::new();143 escape_string_xml_buf(str, &mut out);144 out145}146147fn escape_string_xml_buf(str: &str, out: &mut String) {148 if str.is_empty() {149 return;150 }151 let mut remaining = str;152153 let mut found = false;154 while let Some(position) = remaining155 .bytes()156 .position(|c| matches!(c, b'<' | b'>' | b'&' | b'"' | b'\''))157 {158 found = true;159160 let (plain, rem) = remaining.split_at(position);161 out.push_str(plain);162163 out.push_str(match rem.as_bytes()[0] {164 b'<' => "<",165 b'>' => ">",166 b'&' => "&",167 b'"' => """,168 b'\'' => "'",169 _ => unreachable!("position() searches for those matches"),170 });171172 remaining = &rem[1..];173 }174 if !found {175 176 out.push_str(str);177 return;178 }179 out.push_str(remaining);180}