git.delta.rocks / jrsonnet / refs/commits / 94c438bdfffb

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, FromUntyped, 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);35}36impl FromUntyped for JSONMLValue {37	fn from_untyped(untyped: Val) -> Result<Self> {38		let val = <Either![ArrValue, String]>::from_untyped(untyped)39			.description("parsing JSONML value (an array or string)")?;40		let arr = match val {41			Either2::A(a) => a,42			Either2::B(s) => return Ok(Self::String(s)),43		};44		if arr.is_empty() {45			bail!("JSONML value should have tag (array length should be >=1)");46		}47		let tag = String::from_untyped(48			arr.get(0)49				.description("getting JSONML tag")?50				.expect("length checked"),51		)52		.description("parsing JSONML tag")?;5354		let (has_attrs, attrs) = if arr.len() >= 2 {55			let maybe_attrs = arr56				.get(1)57				.with_description(|| "getting JSONML attrs")?58				.expect("length checked");59			if let Val::Obj(attrs) = maybe_attrs {60				(true, attrs)61			} else {62				(false, ObjValue::empty())63			}64		} else {65			(false, ObjValue::empty())66		};67		Ok(Self::Tag {68			tag,69			attrs,70			children: in_description_frame(71				|| "parsing children".to_owned(),72				|| {73					FromUntyped::from_untyped(Val::Arr(arr.slice(74						Some(if has_attrs { 2 } else { 1 }),75						None,76						None,77					)))78				},79			)?,80		})81	}82}8384impl ManifestFormat for XmlJsonmlFormat {85	fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {86		let val = JSONMLValue::from_untyped(val).with_description(|| "parsing JSONML value")?;87		manifest_jsonml(&val, buf, self)88	}89}9091fn manifest_jsonml(v: &JSONMLValue, buf: &mut String, opts: &XmlJsonmlFormat) -> Result<()> {92	match v {93		JSONMLValue::Tag {94			tag,95			attrs,96			children,97		} => {98			let has_children = !children.is_empty();99			buf.push('<');100			buf.push_str(tag);101			attrs.run_assertions()?;102			for (key, value) in attrs.iter(103				// Not much sense to preserve order here104				#[cfg(feature = "exp-preserve-order")]105				false,106			) {107				buf.push(' ');108				buf.push_str(&key);109				buf.push('=');110				buf.push('"');111				let value = value?;112				let value = if let Val::Str(s) = value {113					s.to_string()114				} else {115					ToStringFormat.manifest(value)?116				};117				escape_string_xml_buf(&value, buf);118				buf.push('"');119			}120			if !has_children && !opts.force_closing {121				buf.push('/');122			}123			buf.push('>');124			for child in children {125				manifest_jsonml(child, buf, opts)?;126			}127			if has_children || opts.force_closing {128				buf.push('<');129				buf.push('/');130				buf.push_str(tag);131				buf.push('>');132			}133			Ok(())134		}135		JSONMLValue::String(s) => {136			escape_string_xml_buf(s, buf);137			Ok(())138		}139	}140}141142pub fn escape_string_xml(str: &str) -> String {143	let mut out = String::new();144	escape_string_xml_buf(str, &mut out);145	out146}147148fn escape_string_xml_buf(str: &str, out: &mut String) {149	if str.is_empty() {150		return;151	}152	let mut remaining = str;153154	let mut found = false;155	while let Some(position) = remaining156		.bytes()157		.position(|c| matches!(c, b'<' | b'>' | b'&' | b'"' | b'\''))158	{159		found = true;160161		let (plain, rem) = remaining.split_at(position);162		out.push_str(plain);163164		out.push_str(match rem.as_bytes()[0] {165			b'<' => "&lt;",166			b'>' => "&gt;",167			b'&' => "&amp;",168			b'"' => "&quot;",169			b'\'' => "&apos;",170			_ => unreachable!("position() searches for those matches"),171		});172173		remaining = &rem[1..];174	}175	if !found {176		// No match - no escapes required177		out.push_str(str);178		return;179	}180	out.push_str(remaining);181}