git.delta.rocks / jrsonnet / refs/commits / 11dc48e90ac0

difftreelog

source

crates/jrsonnet-stdlib/src/manifest/xml.rs3.7 KiBsourcehistory
1use jrsonnet_evaluator::{2	bail,3	manifest::{ManifestFormat, ToStringFormat},4	typed::{ComplexValType, Either4, Typed, ValType},5	val::{ArrValue, IndexableVal},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::Arr(arr) = untyped else {42			if let Val::Str(s) = untyped {43				return Ok(Self::String(s.to_string()));44			};45			bail!("expected JSONML value (an array or string)");46		};47		if arr.len() < 1 {48			bail!("JSONML value should have tag");49		};50		let tag = String::from_untyped(51			arr.get(0)52				.with_description(|| "getting JSONML tag")?53				.expect("length checked"),54		)?;55		let (has_attrs, attrs) = if arr.len() >= 2 {56			let maybe_attrs = arr57				.get(1)58				.with_description(|| "getting JSONML attrs")?59				.expect("length checked");60			if let Val::Obj(attrs) = maybe_attrs {61				(true, attrs)62			} else {63				(false, ObjValue::new_empty())64			}65		} else {66			(false, ObjValue::new_empty())67		};68		Ok(Self::Tag {69			tag,70			attrs,71			children: Typed::from_untyped(Val::Arr(arr.slice(72				Some(if has_attrs { 2 } else { 1 }),73				None,74				None,75			)))?,76		})77	}78}7980impl ManifestFormat for XmlJsonmlFormat {81	fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {82		let val = JSONMLValue::from_untyped(val).with_description(|| "parsing JSONML value")?;83		manifest_jsonml(&val, buf, self)84	}85}8687fn manifest_jsonml(v: &JSONMLValue, buf: &mut String, opts: &XmlJsonmlFormat) -> Result<()> {88	match v {89		JSONMLValue::Tag {90			tag,91			attrs,92			children,93		} => {94			let has_children = !children.is_empty();95			buf.push('<');96			buf.push_str(&tag);97			attrs.run_assertions()?;98			for (key, value) in attrs.iter(99				// Not much sense to preserve order here100				#[cfg(feature = "exp-preserve-order")]101				false,102			) {103				buf.push(' ');104				buf.push_str(&key);105				buf.push('=');106				buf.push('"');107				let value = value?;108				let value = if let Val::Str(s) = value {109					s.to_string()110				} else {111					ToStringFormat.manifest(value)?112				};113				escape_string_xml_buf(&value, buf);114				buf.push('"');115			}116			if !has_children && !opts.force_closing {117				buf.push('/');118			}119			buf.push('>');120			for child in children {121				manifest_jsonml(&child, buf, opts)?;122			}123			if has_children || opts.force_closing {124				buf.push('<');125				buf.push('/');126				buf.push_str(&tag);127				buf.push('>');128			}129			Ok(())130		}131		JSONMLValue::String(s) => {132			escape_string_xml_buf(s, buf);133			Ok(())134		}135	}136}137138pub fn escape_string_xml(str: &str) -> String {139	let mut out = String::new();140	escape_string_xml_buf(str, &mut out);141	out142}143144fn escape_string_xml_buf(str: &str, out: &mut String) {145	if str.is_empty() {146		return;147	}148	let mut remaining = str;149150	let mut found = false;151	while let Some(position) = remaining152		.bytes()153		.position(|c| matches!(c, b'<' | b'>' | b'&' | b'"' | b'\''))154	{155		found = true;156157		let (plain, rem) = remaining.split_at(position);158		out.push_str(plain);159160		out.push_str(match rem.as_bytes()[0] {161			b'<' => "&lt;",162			b'>' => "&gt;",163			b'&' => "&amp;",164			b'"' => "&quot;",165			b'\'' => "&apos;",166			_ => unreachable!("position() searches for those matches"),167		});168169		remaining = &rem[1..];170	}171	if !found {172		// No match - no escapes required173		out.push_str(&str);174		return;175	}176	out.push_str(&remaining);177}