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