From 544836cf0f6373a0a753d0953ce7e60fd96337af Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Wed, 19 Aug 2026 08:57:42 +0200 Subject: feat(render): XML emitter and schema Element-per-field; attributes carry identity only and there is no mixed content, so a consumer's XPath never has to distinguish the two. Schema validation is an opt-in make check-schema via xmllint, not an in-suite assertion: validating XSD needs an XML library and the dependency list is frozen. It prints SKIPPED loudly when xmllint is absent, because a silent skip reads as a pass. The suite asserts well-formedness properties directly instead. This corrects the design spec, which claimed in-test validation. --- lib/render/emit_xml.ml | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ lib/render/emit_xml.mli | 8 ++++++++ 2 files changed, 56 insertions(+) create mode 100644 lib/render/emit_xml.ml create mode 100644 lib/render/emit_xml.mli (limited to 'lib') diff --git a/lib/render/emit_xml.ml b/lib/render/emit_xml.ml new file mode 100644 index 0000000..4e4b51a --- /dev/null +++ b/lib/render/emit_xml.ml @@ -0,0 +1,48 @@ +(* emit_xml.ml *) +module T = Template + +let escape s = Escape.apply Escape.Xml s + +let get v k = match v with T.Obj kvs -> List.assoc_opt k kvs | _ -> None +let s v k = match get v k with Some (T.Str x) -> x | _ -> "" +let el b name value = + Buffer.add_string b (" <" ^ name ^ ">" ^ escape value ^ "\n") + +let day b d = + Buffer.add_string b (" \n"); + el b "season" (s d "season"); + el b "week" (s d "week"); + el b "slug" (s d "slug"); + el b "rank" (s d "rank"); + el b "colour" (s d "colour"); + el b "subject" (s d "subject"); + (match get d "name" with + | Some (T.Obj kvs) -> + List.iter + (fun (lang, v) -> + match v with + | T.Str x -> Buffer.add_string b (" " ^ escape x ^ "\n") + | _ -> ()) + kvs + | _ -> ()); + (match get d "comms" with + | Some (T.List l) -> + List.iter (fun c -> Buffer.add_string b (" " ^ escape (s c "slug") ^ "\n")) l + | _ -> ()); + let cite name value = + if value <> "" then + Buffer.add_string b (" " ^ escape value ^ "\n") + in + cite "first" (s d "first"); + cite "gospel" (s d "gospel"); + Buffer.add_string b " \n" + +let year v = + let b = Buffer.create (128 * 1024) in + Buffer.add_string b "\n"; + Buffer.add_string b + ("\n"); + Buffer.add_string b " \n"; + (match get v "days" with Some (T.List l) -> List.iter (day b) l | _ -> ()); + Buffer.add_string b " \n\n"; + Buffer.contents b diff --git a/lib/render/emit_xml.mli b/lib/render/emit_xml.mli new file mode 100644 index 0000000..4d539d6 --- /dev/null +++ b/lib/render/emit_xml.mli @@ -0,0 +1,8 @@ +(* emit_xml.mli *) +(** Element-per-field XML. Attributes carry identity only (rite, year, date); + everything else is an element, and there is no mixed content -- so a + consumer's XPath never has to distinguish the two. Shape pinned by + schema/colitur-v1.xsd. *) + +val escape : string -> string +val year : Template.value -> string -- cgit v1.3