git.delta.rocks / jrsonnet / refs/commits / f0883bbdc9f6

difftreelog

source

crates/jrsonnet-stdlib/src/manifest/xml.rs3.6 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				buf.push(' ');100				buf.push_str(&key);101				buf.push('=');102				buf.push('"');103				let value = value?;104				let value = if let Val::Str(s) = value {105					s.to_string()106				} else {107					ToStringFormat.manifest(value)?108				};109				escape_string_xml_buf(&value, buf);110				buf.push('"');111			}112			if !has_children && !opts.force_closing {113				buf.push('/');114			}115			buf.push('>');116			for child in children {117				manifest_jsonml(&child, buf, opts)?;118			}119			if has_children || opts.force_closing {120				buf.push('<');121				buf.push('/');122				buf.push_str(&tag);123				buf.push('>');124			}125			Ok(())126		}127		JSONMLValue::String(s) => {128			escape_string_xml_buf(s, buf);129			Ok(())130		}131	}132}133134pub fn escape_string_xml(str: &str) -> String {135	let mut out = String::new();136	escape_string_xml_buf(str, &mut out);137	out138}139140fn escape_string_xml_buf(str: &str, out: &mut String) {141	if str.is_empty() {142		return;143	}144	let mut remaining = str;145146	let mut found = false;147	while let Some(position) = remaining148		.bytes()149		.position(|c| matches!(c, b'<' | b'>' | b'&' | b'"' | b'\''))150	{151		found = true;152153		let (plain, rem) = remaining.split_at(position);154		out.push_str(plain);155156		out.push_str(match rem.as_bytes()[0] {157			b'<' => "&lt;",158			b'>' => "&gt;",159			b'&' => "&amp;",160			b'"' => "&quot;",161			b'\'' => "&apos;",162			_ => unreachable!("position() searches for those matches"),163		});164165		remaining = &rem[1..];166	}167	if !found {168		// No match - no escapes required169		out.push_str(&str);170		return;171	}172	out.push_str(&remaining);173}