|
| 1 | +(ns clojure.data.xml.pu-map |
| 2 | + "Provides a bidirectional mapping for keeping track of prefix->uri mappings in xml namespaces. |
| 3 | +
|
| 4 | + This has the semantics of a basic key -> multiple values map + two special features, both of which are dictated by the xml standard: |
| 5 | +
|
| 6 | + - instead of a special dissoc, there is assoc to empty string or nil |
| 7 | + - there are two fixed, unique mappings: |
| 8 | + - \"xml\" <-> [\"http://www.w3.org/2000/xmlns/\"] |
| 9 | + - \"xmlns\" <-> [\"http://www.w3.org/XML/1998/namespace\"]" |
| 10 | + (:require [clojure.data.xml.name :as name] |
| 11 | + [clojure.string :as str] |
| 12 | + [clojure.core :as core]) |
| 13 | + (:refer-clojure :exclude [assoc! dissoc! transient persistent! get assoc])) |
| 14 | + |
| 15 | +(defn transient [{:keys [u->ps p->u]}] |
| 16 | + (core/assoc! (core/transient {}) |
| 17 | + :p->u (core/transient p->u) |
| 18 | + :u->ps (core/transient u->ps))) |
| 19 | + |
| 20 | +(defn persistent! [put] |
| 21 | + (core/persistent! |
| 22 | + (core/assoc! put |
| 23 | + :p->u (core/persistent! (core/get put :p->u)) |
| 24 | + :u->ps (core/persistent! (core/get put :u->ps))))) |
| 25 | + |
| 26 | +(defn- assoc-uri! [u->ps uri prefix] |
| 27 | + (core/assoc! u->ps uri |
| 28 | + (if-let [ps (core/get u->ps uri)] |
| 29 | + (conj ps prefix) |
| 30 | + [prefix]))) |
| 31 | + |
| 32 | +(defn- dissoc-uri! [u->ps uri prefix] |
| 33 | + (if-let [ps (seq (remove #{prefix} (core/get u->ps uri)))] |
| 34 | + (core/assoc! u->ps uri (vec ps)) |
| 35 | + (core/dissoc! u->ps uri))) |
| 36 | + |
| 37 | +(defn assoc! [{:as put :keys [p->u u->ps]} prefix uri] |
| 38 | + (when (or (core/get #{"xml" "xmlns"} prefix) |
| 39 | + (core/get #{name/xml-uri name/xmlns-uri} uri)) |
| 40 | + (throw (ex-info "Mapping for xml: and xmlns: prefixes are fixed by the standard" |
| 41 | + {:attempted-mapping {:prefix prefix |
| 42 | + :uri uri}}))) |
| 43 | + (let [prev-uri (core/get p->u prefix)] |
| 44 | + (core/assoc! put |
| 45 | + :p->u (if (str/blank? uri) |
| 46 | + (core/dissoc! p->u prefix) |
| 47 | + (core/assoc! p->u prefix uri)) |
| 48 | + :u->ps (if (str/blank? uri) |
| 49 | + (dissoc-uri! u->ps prev-uri prefix) |
| 50 | + (cond |
| 51 | + (= uri prev-uri) u->ps |
| 52 | + (not prev-uri) (assoc-uri! u->ps uri prefix) |
| 53 | + :else (-> u->ps |
| 54 | + (dissoc-uri! prev-uri prefix) |
| 55 | + (assoc-uri! uri prefix))))))) |
| 56 | + |
| 57 | +(defn get [{:keys [p->u]} prefix] |
| 58 | + (core/get p->u prefix)) |
| 59 | + |
| 60 | +(defn get-prefixes [{:keys [u->ps]} uri] |
| 61 | + (core/get u->ps uri)) |
| 62 | + |
| 63 | +(def get-prefix (comp first get-prefixes)) |
| 64 | + |
| 65 | +(defn assoc [put & {:as kvs}] |
| 66 | + (persistent! |
| 67 | + (reduce-kv assoc! (transient put) kvs))) |
| 68 | + |
| 69 | + |
| 70 | +;; TODO replace this with a deftype for memory savings |
| 71 | +(def EMPTY {:u->ps {name/xml-uri ["xml"] |
| 72 | + name/xmlns-uri ["xmlns"]} |
| 73 | + :p->u {"xml" name/xml-uri |
| 74 | + "xmlns" name/xmlns-uri}}) |
0 commit comments