From 6e0b879767d8f7965e8fbb30d8c6dc772dd9601f Mon Sep 17 00:00:00 2001 From: erdos Date: Fri, 21 Apr 2023 18:58:55 +0200 Subject: [PATCH] feat: split up model building, extract contexts --- src/stencil/model.clj | 117 ++++++++++++-------------- src/stencil/model/numbering.clj | 7 +- src/stencil/model/style.clj | 7 ++ src/stencil/postprocess/fragments.clj | 13 +++ src/stencil/spec.clj | 4 +- 5 files changed, 83 insertions(+), 65 deletions(-) diff --git a/src/stencil/model.clj b/src/stencil/model.clj index 84663fce..c097578c 100644 --- a/src/stencil/model.clj +++ b/src/stencil/model.clj @@ -10,13 +10,13 @@ [stencil.model.numbering :as numbering] [stencil.types :refer [->FragmentInvoke ->ReplaceImage]] [stencil.postprocess.images :refer [img-data->extrafile]] - [stencil.util :refer [unlazy-tree assoc-if-val eval-exception]] + [stencil.util :refer [unlazy-tree eval-exception]] [stencil.model.relations :as relations] [stencil.model.common :refer [unix-path ->xml-writer resource-copier]] [stencil.functions :refer [call-fn]] - [stencil.model.style :as style - :refer [expect-fragment-context! *current-styles*]] - [stencil.cleanup :as cleanup])) + [stencil.model.style :as style :refer [expect-fragment-context!]] + [stencil.cleanup :as cleanup] + [stencil.postprocess.fragments :as fragments])) (set! *warn-on-reflection* true) @@ -36,9 +36,6 @@ (def extra-relations #{rel-type-footer rel-type-header rel-type-slide}) -;; all insertable fragments. map of id to frag def. -(def ^:private ^:dynamic *all-fragments* nil) - ;; set of already inserted fragment ids. (def ^:private ^:dynamic *inserted-fragments* nil) @@ -104,7 +101,7 @@ @*inserted-fragments*])] (swap! *inserted-fragments* into fragments) {:xml result - :fragment-names fragments + :fragment-names fragments ;; set of fragment names this model used :writer (->xml-writer result)}))) @@ -122,46 +119,44 @@ (defn- eval-template-model [template-model data functions fragments] (assert (:main template-model) "Should be a result of load-template-model call!") (assert (some? fragments)) - (binding [*current-styles* (atom (:parsed (:style (:main template-model)))) - numbering/*numbering* (::numbering (:main template-model)) - *inserted-fragments* (atom #{}) - *extra-files* (atom #{}) - *all-fragments* (into {} fragments)] - (let [evaluate (fn [m] - (let [result (eval-model-part m data functions) - fragment-names (set (:fragment-names result))] - (cond-> m + (style/with-template-styles template-model + (numbering/with-template-numberings template-model + (fragments/with-fragments-context fragments + (binding [*inserted-fragments* (atom #{}) + *extra-files* (atom #{})] + (let [evaluate (fn [m] + (let [result (eval-model-part m data functions) + ;; names of fragments + fragment-names (set (:fragment-names result))] + (cond-> m ;; create a rels file for the current xml - (and (seq @*extra-files*) (nil? (::path (:relations m)))) - (assoc-in [:relations ::path] - (unix-path (file (.getParentFile (file (::path m))) - "_rels" - (str (.getName (file (::path m))) ".rels")))) + (and (seq @*extra-files*) (nil? (::path (:relations m)))) + (assoc-in [:relations ::path] + (unix-path (file (.getParentFile (file (::path m))) + "_rels" + (str (.getName (file (::path m))) ".rels")))) ;; add relations if any - (seq @*extra-files*) - (update-in [:relations :parsed] (fnil into {}) - (for [relation @*extra-files* - :when (or (not (contains? relation :fragment-name)) - (contains? fragment-names (:fragment-name relation)))] - [(:new-id relation) relation])) + (seq @*extra-files*) + (update-in [:relations :parsed] (fnil into {}) + (for [relation @*extra-files* + :when (or (not (contains? relation :fragment-name)) + (contains? fragment-names (:fragment-name relation)))] + [(:new-id relation) relation])) ;; relation file will be rendered instead of copied - (seq @*extra-files*) - (update-in [:relations] dissoc :source-file) - - :finally (assoc :result result))))] - (-> template-model - (update :main evaluate) - (update-in [:main :headers+footers] (partial mapv evaluate)) + (seq @*extra-files*) + (update-in [:relations] dissoc :source-file) - (cond-> (-> template-model :main :style) - (assoc-in [:main :style :result] (style/file-writer template-model))))))) + :finally (assoc :result result))))] + (-> template-model + (update :main evaluate) + (update-in [:main :headers+footers] (partial mapv evaluate))))))))) (defn- model-seq [model] - (let [model-keys [:relations :headers+footers :main :style :content-types :fragments ::numbering :result]] + (let [model-keys [:relations :headers+footers :main :style :content-types :fragments :stencil.model.numbering/numbering :result]] (tree-seq map? (fn [node] (flatten (keep node model-keys))) model))) @@ -190,7 +185,7 @@ (for [m (model-seq evaled-template-model) :when (:relations m) :let [src-parent (delay (file (or (:source-folder m) - (.getParentFile (file (:source-file m)))))) + (.getParentFile (file (:source-file m)))))) path-parent (some-> m ::path file .getParentFile)] relation (vals (:parsed (:relations m))) :when (not= "External" (::mode relation)) @@ -222,29 +217,27 @@ (defmethod eval/eval-step :cmd/include [function local-data-map {frag-name :name}] (assert (map? local-data-map)) - (assert (string? frag-name)) - (expect-fragment-context! - (if-let [fragment-model (get *all-fragments* frag-name)] - (let [;; merge style definitions from fragment - style-ids-rename (-> fragment-model :main :style :parsed (doto assert) (style/insert-styles!)) - - relation-ids-rename (relations/ids-rename fragment-model frag-name) - relation-rename-map (into {} (map (juxt :old-id :new-id)) relation-ids-rename) - - ;; evaluate - evaled (eval-template-model fragment-model local-data-map function {}) - - ;; write back - get-xml (fn [x] (or (:xml x) @(:xml-delay x))) - evaled-parts (->> evaled :main :result - (get-xml) - (extract-body-parts) - (map (partial relations/xml-rename-relation-ids relation-rename-map)) - (map (partial style/xml-rename-style-ids style-ids-rename)))] - (swap! *inserted-fragments* conj frag-name) - (run! add-extra-file! relation-ids-rename) - [{:text (->FragmentInvoke {:frag-evaled-parts evaled-parts})}]) - (throw (eval-exception (str "No fragment for name: " frag-name) nil))))) + (let [fragment-model (fragments/fragment-by-name frag-name) + + ;; merge style definitions from fragment + style-ids-rename (-> fragment-model :main :style :parsed (doto assert) (style/insert-styles!)) + + relation-ids-rename (relations/ids-rename fragment-model frag-name) + relation-rename-map (into {} (map (juxt :old-id :new-id)) relation-ids-rename) + + ;; evaluate + evaled (eval-template-model fragment-model local-data-map function {}) + + ;; write back + get-xml (fn [x] (or (:xml x) @(:xml-delay x))) + evaled-parts (->> evaled :main :result + (get-xml) + (extract-body-parts) + (map (partial relations/xml-rename-relation-ids relation-rename-map)) + (map (partial style/xml-rename-style-ids style-ids-rename)))] + (swap! *inserted-fragments* conj frag-name) + (run! add-extra-file! relation-ids-rename) + [{:text (->FragmentInvoke {:frag-evaled-parts evaled-parts})}])) ;; replaces the nearest image with the content diff --git a/src/stencil/model/numbering.clj b/src/stencil/model/numbering.clj index 31c68307..6ae5982d 100644 --- a/src/stencil/model/numbering.clj +++ b/src/stencil/model/numbering.clj @@ -13,6 +13,11 @@ (def ^:dynamic *numbering* nil) +(defmacro with-template-numberings [template-model body] + `(binding [numbering/*numbering* (::numbering (:main ~template-model))] + ~body)) + + (defn- find-node [tree predicate] (when (map? tree) (if (predicate tree) @@ -79,7 +84,7 @@ (defn assoc-numbering [model dir] (->> (main-numbering dir (:stencil.model/path model) (:relations model)) - (assoc-if-val model :stencil.model/numbering))) + (assoc-if-val model ::numbering))) (defn style-def-for [id lvl] (assert (string? id)) diff --git a/src/stencil/model/style.clj b/src/stencil/model/style.clj index 7eaa6c4f..75454f56 100644 --- a/src/stencil/model/style.clj +++ b/src/stencil/model/style.clj @@ -9,6 +9,7 @@ (set! *warn-on-reflection* true) +(declare file-writer) (def rel-type "Relationship type of style definitions in _rels/.rels file." "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles") @@ -20,6 +21,12 @@ ;; throws error when not invoked from inside fragment context (defmacro expect-fragment-context! [& bodies] `(do (assert *current-styles*) ~@bodies)) +(defmacro with-template-styles [template-model body] + `(binding [*current-styles* (atom (:parsed (:style (:main ~template-model))))] + (let [modified-model# ~body] + (if (-> modified-model# :main :style) + (assoc-in modified-model# [:main :style :result] (file-writer modified-model#)) + modified-model#)))) (defn- parse "Returns a map where key is style id and value is style definition." diff --git a/src/stencil/postprocess/fragments.clj b/src/stencil/postprocess/fragments.clj index a5a80379..d7bbf83a 100644 --- a/src/stencil/postprocess/fragments.clj +++ b/src/stencil/postprocess/fragments.clj @@ -9,6 +9,19 @@ [stencil.util :refer :all])) +;; all insertable fragments. map of id to frag def. +(def ^:dynamic *all-fragments* nil) + +(defmacro with-fragments-context [fragments-map body] + `(binding [*all-fragments* (into {} ~fragments-map)] ~body)) + +(defn fragment-by-name + "Get fragment model by name in current context or throw exception." + [frag-name] + (assert (string? frag-name)) + (or (get *all-fragments* frag-name) + (throw (eval-exception (str "No fragment for name: " frag-name) nil)))) + (defn- remove+up "Removes current node and moves pointer to parent node." [loc] diff --git a/src/stencil/spec.clj b/src/stencil/spec.clj index f54ffe22..62683554 100644 --- a/src/stencil/spec.clj +++ b/src/stencil/spec.clj @@ -46,7 +46,7 @@ (s/def ::main (s/keys :req [:stencil.model/path] :opt-un [:stencil.model/headers+footers ::result] ;; not present in fragments - :opt [::numbering] + :opt [:stencil.model.numbering/numbering] :req-un [::source-file ::executable ::style @@ -63,7 +63,7 @@ (s/def ::parsed any?) -(s/def :stencil.model/numbering (s/nilable (s/keys :req [:stencil.model/path] +(s/def :stencil.model.numbering/numbering (s/nilable (s/keys :req [:stencil.model/path] :req-un [::source-file ::parsed]))) (s/fdef stencil.model/load-template-model