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.get32(0)48 .description("getting JSONML tag")?49 .expect("length checked"),50 )51 .description("parsing JSONML tag")?;5253 let (has_attrs, attrs) = if arr.len32() >= 2 {54 let maybe_attrs = arr55 .get32(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 || FromUntyped::from_untyped(Val::Arr(arr.slice(if has_attrs { 2 } else { 1 }..))),72 )?,73 })74 }75}7677impl ManifestFormat for XmlJsonmlFormat {78 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {79 let val = JSONMLValue::from_untyped(val).with_description(|| "parsing JSONML value")?;80 manifest_jsonml(&val, buf, self)81 }82}8384fn manifest_jsonml(v: &JSONMLValue, buf: &mut String, opts: &XmlJsonmlFormat) -> Result<()> {85 match v {86 JSONMLValue::Tag {87 tag,88 attrs,89 children,90 } => {91 let has_children = !children.is_empty();92 buf.push('<');93 buf.push_str(tag);94 attrs.run_assertions()?;95 for (key, value) in attrs.iter(96 97 #[cfg(feature = "exp-preserve-order")]98 false,99 ) {100 buf.push(' ');101 buf.push_str(&key);102 buf.push('=');103 buf.push('"');104 let value = value?;105 let value = if let Val::Str(s) = value {106 s.to_string()107 } else {108 ToStringFormat.manifest(value)?109 };110 escape_string_xml_buf(&value, buf);111 buf.push('"');112 }113 if !has_children && !opts.force_closing {114 buf.push('/');115 }116 buf.push('>');117 for child in children {118 manifest_jsonml(child, buf, opts)?;119 }120 if has_children || opts.force_closing {121 buf.push('<');122 buf.push('/');123 buf.push_str(tag);124 buf.push('>');125 }126 Ok(())127 }128 JSONMLValue::String(s) => {129 escape_string_xml_buf(s, buf);130 Ok(())131 }132 }133}134135pub fn escape_string_xml(str: &str) -> String {136 let mut out = String::new();137 escape_string_xml_buf(str, &mut out);138 out139}140141fn escape_string_xml_buf(str: &str, out: &mut String) {142 if str.is_empty() {143 return;144 }145 let mut remaining = str;146147 let mut found = false;148 while let Some(position) = remaining149 .bytes()150 .position(|c| matches!(c, b'<' | b'>' | b'&' | b'"' | b'\''))151 {152 found = true;153154 let (plain, rem) = remaining.split_at(position);155 out.push_str(plain);156157 out.push_str(match rem.as_bytes()[0] {158 b'<' => "<",159 b'>' => ">",160 b'&' => "&",161 b'"' => """,162 b'\'' => "'",163 _ => unreachable!("position() searches for those matches"),164 });165166 remaining = &rem[1..];167 }168 if !found {169 170 out.push_str(str);171 return;172 }173 out.push_str(remaining);174}