git.delta.rocks / jrsonnet / refs/heads / master

difftreelog

source

crates/jrsonnet-stdlib/src/manifest/xml.rs3.8 KiBsourcehistory
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				// Not much sense to preserve order here97				#[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'<' => "&lt;",159			b'>' => "&gt;",160			b'&' => "&amp;",161			b'"' => "&quot;",162			b'\'' => "&apos;",163			_ => unreachable!("position() searches for those matches"),164		});165166		remaining = &rem[1..];167	}168	if !found {169		// No match - no escapes required170		out.push_str(str);171		return;172	}173	out.push_str(remaining);174}