xml.rs (14641B)
1 /* 2 * This file is part of LibEuFin. 3 * Copyright (C) 2026 Taler Systems S.A. 4 5 * LibEuFin is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU Affero General Public License as 7 * published by the Free Software Foundation; either version 3, or 8 * (at your option) any later version. 9 10 * LibEuFin is distributed in the hope that it will be useful, but 11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General 13 * Public License for more details. 14 15 * You should have received a copy of the GNU Affero General Public 16 * License along with LibEuFin; see the file COPYING. If not, see 17 * <http://www.gnu.org/licenses/> 18 */ 19 20 use std::{ 21 fmt::{Display, Write}, 22 str::{FromStr, Utf8Error}, 23 }; 24 25 use roxmltree::{Document, Node}; 26 use taler_common::encoding::base64; 27 28 #[macro_export] 29 macro_rules! xml { 30 // Trailing comma 31 ($w:ident => $(,)?) => {{}}; 32 // Logic escape 33 ($w:ident => @ $logic:expr$(, $($rest:tt)*)?) => {{ 34 ($logic)($w); 35 $($crate::xml!($w => $($rest)*);)* 36 }}; 37 // Text element 38 ($w:ident => $name:tt $($k:literal=$v:tt)* : $content:expr $(, $($rest:tt)*)?) => {{ 39 $w.text(&$name, &[$((&$k, &$v)),*], &$content); 40 $($crate::xml!($w => $($rest)*);)* 41 }}; 42 // Nested block 43 ($w:ident => $name:tt $($k:literal=$v:tt)* { $($body:tt)* }$(, $($rest:tt)*)?) => {{ 44 let name = &$name; 45 $w.open(&name, &[$((&$k, &$v)),*]); 46 $crate::xml!($w => $($body)*); 47 $w.close(&name); 48 $($crate::xml!($w => $($rest)*);)* 49 }}; 50 // Empty element 51 ($w:ident => $name:tt $($k:literal=$v:tt)* $(, $($rest:tt)*)?) => {{ 52 $w.empty(&$name, &[$((&$k, &$v)),*]); 53 $($crate::xml!($w => $($rest)*);)* 54 }}; 55 // Root builder 56 ($name:tt $($k:literal=$v:tt)* { $($body:tt)* }) => {{ 57 let mut writer = $crate::xml::XmlWriter::init(); 58 let w = &mut writer; 59 let name = &$name; 60 w.open(&name, &[$((&$k, &$v)),*]); 61 $crate::xml!(w => $($body)*); 62 w.close(&name); 63 writer.finish() 64 }}; 65 } 66 67 pub struct XmlWriter { 68 xml: String, 69 } 70 71 impl XmlWriter { 72 pub fn init() -> Self { 73 let mut xml = String::with_capacity(1024); 74 xml.push_str(r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>"#); 75 Self { xml } 76 } 77 78 pub fn open<N: Display>(&mut self, name: N, attrs: &[(&dyn Display, &dyn Display)]) { 79 self.xml.push('<'); 80 self.write_tag_attrs(name, attrs); 81 self.xml.push('>'); 82 } 83 84 pub fn close<N: Display>(&mut self, name: N) { 85 self.xml.push_str("</"); 86 self.xml.write_fmt(format_args!("{name}")).unwrap(); 87 self.xml.push('>'); 88 } 89 90 pub fn empty<N: Display>(&mut self, name: N, attrs: &[(&dyn Display, &dyn Display)]) { 91 self.xml.push('<'); 92 self.write_tag_attrs(name, attrs); 93 self.xml.push_str("/>"); 94 } 95 96 pub fn text<N: Display, C: Display>( 97 &mut self, 98 name: N, 99 attrs: &[(&dyn Display, &dyn Display)], 100 content: C, 101 ) { 102 self.open(&name, attrs); 103 self.write_escaped(content); 104 self.close(&name); 105 } 106 107 fn write_tag_attrs<N: Display>(&mut self, name: N, attrs: &[(&dyn Display, &dyn Display)]) { 108 self.xml.write_fmt(format_args!("{name}")).unwrap(); 109 110 for (key, value) in attrs { 111 self.xml.push(' '); 112 self.xml.write_fmt(format_args!("{}", *key)).unwrap(); 113 self.xml.push_str("=\""); 114 self.write_escaped(*value); 115 self.xml.push('"'); 116 } 117 } 118 119 fn write_escaped<D: Display>(&mut self, content: D) { 120 std::fmt::write(self, format_args!("{content}")).unwrap(); 121 } 122 123 pub fn finish(self) -> String { 124 self.xml 125 } 126 } 127 128 /// Write XML text content following XML escape rules 129 impl std::fmt::Write for XmlWriter { 130 fn write_str(&mut self, s: &str) -> std::fmt::Result { 131 // Single pass over bytes. For each special character, bulk-copy 132 // everything before it, then push the entity. No double-scan, 133 // no char-at-a-time pushing for clean runs. 134 let mut start = 0; 135 for (i, &b) in s.as_bytes().iter().enumerate() { 136 let entity = match b { 137 b'<' => "<", 138 b'>' => ">", 139 b'&' => "&", 140 b'\'' => "'", 141 b'"' => """, 142 _ => continue, 143 }; 144 self.xml.push_str(&s[start..i]); // bulk copy of clean prefix 145 self.xml.push_str(entity); 146 start = i + 1; 147 } 148 self.xml.push_str(&s[start..]); // bulk copy of clean suffix 149 Ok(()) 150 } 151 } 152 153 #[derive(Debug)] 154 pub enum Error { 155 Str(Utf8Error), 156 Xml(roxmltree::Error), 157 Root(Box<str>, Box<str>), 158 Parent(Box<str>), 159 MissingEl(Box<str>), 160 MissingAttr(Box<str>, Box<str>), 161 Duplicate(Box<str>, usize), 162 Parse(Box<str>, Box<str>), 163 } 164 165 impl Display for Error { 166 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 167 match self { 168 Self::Str(e) => e.fmt(f), 169 Self::Xml(e) => e.fmt(f), 170 Self::Root(expected, got) => write!(f, "expected root '{expected}' got '{got}'"), 171 Self::Parent(path) => write!(f, "not parent for element '{path}'"), 172 Self::MissingEl(path) => write!(f, "missing element '{path}'"), 173 Self::MissingAttr(path, name) => write!(f, "missing attribute '{name}' on <{path}>"), 174 Self::Duplicate(path, nb) => write!(f, "expected one '{path}', got {nb}"), 175 Self::Parse(path, err) => write!(f, "malformed '{path}': {err}"), 176 } 177 } 178 } 179 180 impl std::error::Error for Error {} 181 182 pub type Result<T> = std::result::Result<T, Error>; 183 184 #[derive(Debug, Clone, Copy)] 185 pub struct Xml<'xml> { 186 pub node: Node<'xml, 'xml>, 187 } 188 189 impl<'xml> Xml<'xml> { 190 pub fn parse<F, R>(raw: &[u8], tag: &str, f: F) -> Result<R> 191 where 192 R: 'static, 193 F: for<'local> FnOnce(Xml<'local>) -> Result<R>, 194 { 195 let str = std::str::from_utf8(raw).map_err(Error::Str)?; 196 let xml = Document::parse(str).map_err(Error::Xml)?; 197 Self::doc(xml, tag, f) 198 } 199 200 pub fn doc<F, R>(xml: Document, tag: &str, f: F) -> Result<R> 201 where 202 R: 'static, 203 F: for<'local> FnOnce(Xml<'local>) -> Result<R>, 204 { 205 let root = xml.root_element(); 206 if !root.has_tag_name(tag) { 207 return Err(Error::Root(tag.into(), root.tag_name().name().into())); 208 } 209 let node = Xml { node: root }; 210 let res = f(node); 211 drop(xml); 212 res 213 } 214 215 fn path(self, tag: Option<&str>) -> Box<str> { 216 let mut ancestors = Vec::new(); 217 let mut cur = Some(self.node); 218 while let Some(n) = cur { 219 if n.is_element() { 220 ancestors.push(n); 221 } 222 cur = n.parent(); 223 } 224 let mut buf = String::new(); 225 for n in ancestors.into_iter().rev() { 226 // Add prefix if it exists 227 if let Some(prefix) = n.tag_name().namespace().and_then(|ns| n.lookup_prefix(ns)) { 228 buf.push_str(prefix); 229 buf.push(':'); 230 } 231 232 buf.push_str(n.tag_name().name()); 233 buf.push('.'); 234 } 235 match tag { 236 Some(t) => buf.push_str(t), 237 None => { 238 buf.pop(); 239 } 240 } 241 buf.into() 242 } 243 244 pub fn parse_err(self, err: impl Display) -> Error { 245 Error::Parse(self.path(None), err.to_string().into_boxed_str()) 246 } 247 248 pub fn parent(self) -> Result<Xml<'xml>> { 249 Ok(Self { 250 node: self 251 .node 252 .parent() 253 .ok_or_else(|| Error::Parent(self.path(None)))?, 254 }) 255 } 256 257 fn children(self, tag: &str, signed: bool) -> impl Iterator<Item = Node<'xml, 'xml>> { 258 self.node.children().filter(move |n| { 259 n.has_tag_name(tag) && (!signed || n.attribute("authenticate") == Some("true")) 260 }) 261 } 262 263 fn opt_inner(self, tag: &str, signed: bool) -> Result<Option<Xml<'xml>>> { 264 let mut iter = self.children(tag, signed); 265 match (iter.next(), iter.next()) { 266 (None, _) => Ok(None), 267 (Some(_), Some(_)) => Err(Error::Duplicate(self.path(Some(tag)), iter.count() + 2)), 268 (Some(node), None) => Ok(Some(Xml { node })), 269 } 270 } 271 272 fn one_inner(self, tag: &str, signed: bool) -> Result<Xml<'xml>> { 273 self.opt_inner(tag, signed) 274 .transpose() 275 .unwrap_or_else(|| Err(Error::MissingEl(self.path(Some(tag))))) 276 } 277 278 pub fn many(self, tag: &str) -> impl Iterator<Item = Xml<'xml>> { 279 self.children(tag, false).map(|node| Xml { node }) 280 } 281 282 pub fn text(self) -> &'xml str { 283 self.node.text().unwrap_or_default() 284 } 285 286 pub fn attr(self, name: &str) -> Result<&'xml str> { 287 self.node 288 .attribute(name) 289 .ok_or_else(|| Error::MissingAttr(self.path(None), name.into())) 290 } 291 292 pub fn opt_attr(self, name: &str) -> Option<&'xml str> { 293 self.node.attribute(name) 294 } 295 } 296 297 pub trait XmlAccess<'xml>: Sized { 298 type Out<T>; 299 type Opt<T>; 300 301 fn lift<T>(self, f: impl FnOnce(Xml<'xml>) -> Result<T>) -> Result<Self::Out<T>>; 302 fn opt_lift<T>(self, f: impl FnOnce(Xml<'xml>) -> Result<Option<T>>) -> Result<Self::Opt<T>>; 303 304 fn one(self, tag: &'xml str) -> Result<Self::Out<Xml<'xml>>> { 305 self.lift(|n| n.one_inner(tag, false)) 306 } 307 308 fn one_signed(self, tag: &'xml str) -> Result<Self::Out<Xml<'xml>>> { 309 self.lift(|n| n.one_inner(tag, true)) 310 } 311 312 fn opt(self, tag: &'xml str) -> Result<Self::Opt<Xml<'xml>>> { 313 self.opt_lift(|n| n.opt_inner(tag, false)) 314 } 315 316 fn opt_signed(self, tag: &'xml str) -> Result<Self::Opt<Xml<'xml>>> { 317 self.opt_lift(|n| n.opt_inner(tag, true)) 318 } 319 320 fn parse_attr<T: FromStr>(self, name: &str) -> Result<Self::Out<T>> 321 where 322 T::Err: Display, 323 { 324 self.lift(|n| n.attr(name)?.parse().map_err(|e| n.parse_err(e))) 325 } 326 327 fn parse_opt_attr<T: FromStr>(self, name: &str) -> Result<Self::Opt<T>> 328 where 329 T::Err: Display, 330 { 331 self.opt_lift(|n| { 332 n.opt_attr(name) 333 .map(|it| it.parse().map_err(|e| n.parse_err(e))) 334 .transpose() 335 }) 336 } 337 338 fn decode<T, E: Display>( 339 self, 340 lambda: impl FnOnce(&str) -> std::result::Result<T, E>, 341 ) -> Result<Self::Out<T>> { 342 // TODO error not a node text ? 343 self.lift(|n| lambda(n.text()).map_err(|e| n.parse_err(e))) 344 } 345 346 fn parse<T: FromStr>(self) -> Result<Self::Out<T>> 347 where 348 T::Err: Display, 349 { 350 self.decode(T::from_str) 351 } 352 353 fn b64(self) -> Result<Self::Out<Vec<u8>>> { 354 self.decode(|it| base64::decode(it)) 355 } 356 } 357 358 impl<'xml> XmlAccess<'xml> for Xml<'xml> { 359 type Out<T> = T; 360 type Opt<T> = Option<T>; 361 362 fn lift<T>(self, f: impl FnOnce(Xml<'xml>) -> Result<T>) -> Result<Self::Out<T>> { 363 f(self) 364 } 365 366 fn opt_lift<T>(self, f: impl FnOnce(Xml<'xml>) -> Result<Option<T>>) -> Result<Self::Opt<T>> { 367 self.lift(f) 368 } 369 } 370 371 impl<'xml> XmlAccess<'xml> for Option<Xml<'xml>> { 372 type Out<T> = Option<T>; 373 type Opt<T> = Option<T>; 374 375 fn lift<T>(self, f: impl FnOnce(Xml<'xml>) -> Result<T>) -> Result<Self::Out<T>> { 376 self.map(|it| it.lift(f)).transpose() 377 } 378 379 fn opt_lift<T>(self, f: impl FnOnce(Xml<'xml>) -> Result<Option<T>>) -> Result<Self::Opt<T>> { 380 match self { 381 Some(xml) => xml.opt_lift(f), 382 None => Ok(None), 383 } 384 } 385 } 386 387 impl<'xml> XmlAccess<'xml> for Result<Xml<'xml>> { 388 type Out<T> = T; 389 type Opt<T> = Option<T>; 390 391 fn lift<T>(self, f: impl FnOnce(Xml<'xml>) -> Result<T>) -> Result<Self::Out<T>> { 392 self?.lift(f) 393 } 394 395 fn opt_lift<T>(self, f: impl FnOnce(Xml<'xml>) -> Result<Option<T>>) -> Result<Self::Opt<T>> { 396 self.lift(f) 397 } 398 } 399 400 impl<'xml> XmlAccess<'xml> for Result<Option<Xml<'xml>>> { 401 type Out<T> = Option<T>; 402 type Opt<T> = Option<T>; 403 404 fn lift<T>(self, f: impl FnOnce(Xml<'xml>) -> Result<T>) -> Result<Self::Out<T>> { 405 self?.map(|it| it.lift(f)).transpose() 406 } 407 408 fn opt_lift<T>(self, f: impl FnOnce(Xml<'xml>) -> Result<Option<T>>) -> Result<Self::Opt<T>> { 409 match self? { 410 Some(xml) => xml.opt_lift(f), 411 None => Ok(None), 412 } 413 } 414 } 415 416 #[cfg(test)] 417 mod test { 418 use crate::xml::XmlWriter; 419 420 #[test] 421 pub fn basic() { 422 assert_eq!( 423 xml!("ebicsRequest" "version"="H004" { 424 "a" { 425 "b" { 426 "c" "attribute-of"="c" { 427 "d" { 428 "e" { 429 "f" "nested"="true" { 430 "g" { 431 "h" 432 } 433 } 434 } 435 } 436 } 437 } 438 }, 439 "one_more" 440 }), 441 r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?><ebicsRequest version="H004"><a><b><c attribute-of="c"><d><e><f nested="true"><g><h/></g></f></e></d></c></b></a><one_more/></ebicsRequest>"# 442 ) 443 } 444 445 #[test] 446 pub fn modularity() { 447 fn module(w: &mut XmlWriter) { 448 xml!(w => "module"); 449 } 450 assert_eq!( 451 xml!("root" { @ module }), 452 r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?><root><module/></root>"# 453 ) 454 } 455 456 #[test] 457 pub fn iterable() { 458 assert_eq!( 459 xml!("iterable" { 460 "endOfDocument" { 461 @ |w: &mut XmlWriter| for i in 1..=10 { 462 xml!(w => (format_args!("e{i}")) { 463 (format_args!("e{i}{i}")): (format_args!("{i}{i}{i}")) 464 }) 465 } 466 } 467 }), 468 r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?><iterable><endOfDocument><e1><e11>111</e11></e1><e2><e22>222</e22></e2><e3><e33>333</e33></e3><e4><e44>444</e44></e4><e5><e55>555</e55></e5><e6><e66>666</e66></e6><e7><e77>777</e77></e7><e8><e88>888</e88></e8><e9><e99>999</e99></e9><e10><e1010>101010</e1010></e10></endOfDocument></iterable>"# 469 ) 470 } 471 }