git.delta.rocks / jrsonnet / refs/commits / 5df60b8b674f

difftreelog

source

crates/jrsonnet-stdlib/src/manifest/xml.rs3.9 KiBsourcehistory
1use jrsonnet_evaluator::{2	bail, in_description_frame,3	manifest::{ManifestFormat, ToStringFormat},4	typed::{ComplexValType, Either2, 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);3536	fn into_untyped(_typed: Self) -> Result<Val> {37		unreachable!("not used, reserved for parseXML?")38	}3940	fn from_untyped(untyped: Val) -> Result<Self> {41		let val = <Either![ArrValue, String]>::from_untyped(untyped)42			.description("parsing JSONML value (an array or string)")?;43		let arr = match val {44			Either2::A(a) => a,45			Either2::B(s) => return Ok(Self::String(s)),46		};47		if arr.is_empty() {48			bail!("JSONML value should have tag (array length should be >=1)");49		}50		let tag = String::from_untyped(51			arr.get(0)52				.description("getting JSONML tag")?53				.expect("length checked"),54		)55		.description("parsing JSONML tag")?;5657		let (has_attrs, attrs) = if arr.len() >= 2 {58			let maybe_attrs = arr59				.get(1)60				.with_description(|| "getting JSONML attrs")?61				.expect("length checked");62			if let Val::Obj(attrs) = maybe_attrs {63				(true, attrs)64			} else {65				(false, ObjValue::empty())66			}67		} else {68			(false, ObjValue::empty())69		};70		Ok(Self::Tag {71			tag,72			attrs,73			children: in_description_frame(74				|| "parsing children".to_owned(),75				|| {76					Typed::from_untyped(Val::Arr(arr.slice(77						Some(if has_attrs { 2 } else { 1 }),78						None,79						None,80					)))81				},82			)?,83		})84	}85}8687impl ManifestFormat for XmlJsonmlFormat {88	fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {89		let val = JSONMLValue::from_untyped(val).with_description(|| "parsing JSONML value")?;90		manifest_jsonml(&val, buf, self)91	}92}9394fn manifest_jsonml(v: &JSONMLValue, buf: &mut String, opts: &XmlJsonmlFormat) -> Result<()> {95	match v {96		JSONMLValue::Tag {97			tag,98			attrs,99			children,100		} => {101			let has_children = !children.is_empty();102			buf.push('<');103			buf.push_str(tag);104			attrs.run_assertions()?;105			for (key, value) in attrs.iter(106				// Not much sense to preserve order here107				#[cfg(feature = "exp-preserve-order")]108				false,109			) {110				buf.push(' ');111				buf.push_str(&key);112				buf.push('=');113				buf.push('"');114				let value = value?;115				let value = if let Val::Str(s) = value {116					s.to_string()117				} else {118					ToStringFormat.manifest(value)?119				};120				escape_string_xml_buf(&value, buf);121				buf.push('"');122			}123			if !has_children && !opts.force_closing {124				buf.push('/');125			}126			buf.push('>');127			for child in children {128				manifest_jsonml(child, buf, opts)?;129			}130			if has_children || opts.force_closing {131				buf.push('<');132				buf.push('/');133				buf.push_str(tag);134				buf.push('>');135			}136			Ok(())137		}138		JSONMLValue::String(s) => {139			escape_string_xml_buf(s, buf);140			Ok(())141		}142	}143}144145pub fn escape_string_xml(str: &str) -> String {146	let mut out = String::new();147	escape_string_xml_buf(str, &mut out);148	out149}150151fn escape_string_xml_buf(str: &str, out: &mut String) {152	if str.is_empty() {153		return;154	}155	let mut remaining = str;156157	let mut found = false;158	while let Some(position) = remaining159		.bytes()160		.position(|c| matches!(c, b'<' | b'>' | b'&' | b'"' | b'\''))161	{162		found = true;163164		let (plain, rem) = remaining.split_at(position);165		out.push_str(plain);166167		out.push_str(match rem.as_bytes()[0] {168			b'<' => "&lt;",169			b'>' => "&gt;",170			b'&' => "&amp;",171			b'"' => "&quot;",172			b'\'' => "&apos;",173			_ => unreachable!("position() searches for those matches"),174		});175176		remaining = &rem[1..];177	}178	if !found {179		// No match - no escapes required180		out.push_str(str);181		return;182	}183	out.push_str(remaining);184}