git.delta.rocks / jrsonnet / refs/commits / 112adb2810f2

difftreelog

source

crates/jrsonnet-stdlib/src/manifest/xml.rs3.9 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.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				// Not much sense to preserve order here103				#[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'<' => "&lt;",165			b'>' => "&gt;",166			b'&' => "&amp;",167			b'"' => "&quot;",168			b'\'' => "&apos;",169			_ => unreachable!("position() searches for those matches"),170		});171172		remaining = &rem[1..];173	}174	if !found {175		// No match - no escapes required176		out.push_str(str);177		return;178	}179	out.push_str(remaining);180}