Last active
October 15, 2020 05:26
-
-
Save eerohele/28e761f05aa1e68aa5191090cc35effe to your computer and use it in GitHub Desktop.
Write XSLT with Clojure
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns clj-xslt | |
(:require [clojure.data.xml :as xml]) | |
(:import (java.time LocalDateTime) | |
(java.time.format DateTimeFormatter) | |
(java.io StringBufferInputStream StringWriter StringReader) | |
(javax.xml.transform.stream StreamSource) | |
(net.sf.saxon.s9api XsltCompiler Processor) | |
(net.sf.saxon Configuration))) | |
(def processor | |
(Processor. (Configuration.))) | |
(def ^:private builder | |
(doto (.newDocumentBuilder processor))) | |
(defprotocol XmlNode | |
(build [source])) | |
(extend-protocol XmlNode String | |
(build [xml-string] | |
(.build builder (StreamSource. (StringReader. xml-string))))) | |
(def ^:dynamic ^XsltCompiler *xslt-compiler* (.newXsltCompiler processor)) | |
(xml/alias-uri 'xsl "http://www.w3.org/1999/XSL/Transform") | |
;; Constructor functions for XSLT elements. | |
(defn stylesheet | |
[& [attrs & xs]] [::xsl/stylesheet attrs xs]) | |
(defn template | |
[& [attrs & xs]] [::xsl/template attrs xs]) | |
(defn copy | |
[& [attrs & xs]] [::xsl/copy attrs xs]) | |
(defn apply-templates | |
[& [attrs & xs]] [::xsl/apply-templates attrs xs]) | |
(defn value-of | |
[& [attrs & xs]] [::xsl/value-of attrs xs]) | |
(defn current-date | |
"Return the current date in yyyy-MM-dd format." | |
[] | |
(.format (LocalDateTime/now) (DateTimeFormatter/ofPattern "yyyy-MM-dd"))) | |
;; Define reusable templates. | |
(def identity-template | |
"An XSLT identity template." | |
(template {:match "@* | node()"} | |
(copy (apply-templates {:select "@* | node()"})))) | |
;; Reduce boilerplate. | |
(defn xslt3-identity | |
[& content] | |
(stylesheet {:version 3.0} identity-template content)) | |
(def first-stylesheet | |
(xslt3-identity | |
(template {:match "date"} | |
;; Dynamically define XSLT stylesheets. This example doesn't make much sense because XPath has the | |
;; `current-date()` function, but this is just illustrative. | |
(copy (current-date))))) | |
(def second-stylesheet | |
(xslt3-identity | |
(template {:match "number"} | |
(copy (value-of {:select "number(.) * 10"}))))) | |
(def third-stylesheet | |
(xslt3-identity | |
(template {:match "element"} | |
(copy (apply-templates {:select "@*"}) | |
[:child-element "with some content"])))) | |
(defn compile-xslt | |
[& stylesheets] | |
(map (fn [stylesheet] | |
;; There's probably a more efficient way than to serialize the Clojure stylesheet into string and then read it, | |
;; but this'll do for illustrative purposes. | |
(let [writer (xml/emit (xml/sexp-as-element stylesheet) (StringWriter.))] | |
(.. *xslt-compiler* | |
(compile (StreamSource. | |
(StringBufferInputStream. | |
(.toString writer))))))) | |
stylesheets)) | |
(defn transform | |
[executables source] | |
(if (empty? executables) | |
source | |
(transform (rest executables) | |
(.applyTemplates (.load30 (first executables)) source)))) | |
;; Easily create XSLT transformation pipelines. | |
(def pipeline (comp (partial transform (compile-xslt third-stylesheet)) | |
(partial transform (compile-xslt second-stylesheet)) | |
(partial transform (compile-xslt first-stylesheet)))) | |
(def document-one (build "<root><date>2001-01-01</date><number>1</number><element x=\"1\"/></root>")) | |
(def document-two (build "<root><date>2002-02-02</date><number>2</number><element x=\"2\"/></root>")) | |
;; Run XSLT transformations in parallel | |
(pmap (comp println str pipeline) [document-one document-two]) | |
;;=> | |
;; <root> | |
;; <date>2017-04-21</date> | |
;; <number>10</number> | |
;; <element x="1"> | |
;; <child-element>with some content</child-element> | |
;; </element> | |
;; </root> | |
;; <root> | |
;; <date>2017-04-21</date> | |
;; <number>20</number> | |
;; <element x="2"> | |
;; <child-element>with some content</child-element> | |
;; </element> | |
;; </root> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment