Skip to content

Commit

Permalink
add xml() function: insert custom xml nodes from variables
Browse files Browse the repository at this point in the history
  • Loading branch information
erdos committed May 21, 2019
1 parent c02d3a8 commit 6631629
Show file tree
Hide file tree
Showing 8 changed files with 176 additions and 56 deletions.
6 changes: 5 additions & 1 deletion docs/Functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ This is a short description of the functions implemented in Stencil:
- `switch`
- `titlecase`
- `uppercase`
- [xml](#xml)

## Basic Functions

Expand Down Expand Up @@ -106,8 +107,11 @@ The rendering throws an exception on invalid HTML input or unexpected HTML tags.
Write the following to embed the content of `x` as HTML in the document:
- <code>{<i>%=html(x) %</i>}</code>.

### Format
### XML

You can embed custom xml fragments in the document with the `xml()` function. The parameter is a string containing the XML nodes to insert.

### Format

Calls [String.format](https://docs.oracle.com/javase/7/docs/api/java/util/Formatter.html) function.

Expand Down
38 changes: 38 additions & 0 deletions examples/Custom XML/Main.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import io.github.erdos.stencil.API;
import io.github.erdos.stencil.EvaluatedDocument;
import io.github.erdos.stencil.PreparedTemplate;
import io.github.erdos.stencil.TemplateData;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.Map;

public class Main {

public static void main(String... args) throws IOException {

// prepare template file
PreparedTemplate template = API.prepare(findFile("template.docx"));

// read XML content from file
String customXML = new String(Files.readAllBytes(findFile("table.xml").toPath()));

// assemble template data
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("customXML", customXML);
TemplateData data = TemplateData.fromMap(dataMap);

// render template
EvaluatedDocument result = API.render(template, data);

// write generated document to a file
File output = new File("example-output.docx");
result.writeToFile(output);
}

private static File findFile(String name) {
return new File(Main.class.getResource(name).getFile());
}
}
5 changes: 5 additions & 0 deletions examples/Custom XML/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Inserting custom OOXML content

This example shows how to insert custom Office Open XML content in the generated document to create a table.

See the [Main.java](Main.java) file for usage.
43 changes: 43 additions & 0 deletions examples/Custom XML/table.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<w:tbl xmlns:w="http:https://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:tblPr>
<w:tblStyle w:val="TableGrid"/>
<w:tblW w:w="5000" w:type="pct"/>
</w:tblPr>
<w:tblGrid>
<w:gridCol w:w="2880"/>
<w:gridCol w:w="2880"/>
<w:gridCol w:w="2880"/>
</w:tblGrid>
<w:tr>
<w:tc>
<w:tcPr>
<w:tcW w:w="2880" w:type="dxa"/>
</w:tcPr>
<w:p>
<w:r>
<w:t>First cell</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:tcPr>
<w:tcW w:w="2880" w:type="dxa"/>
</w:tcPr>
<w:p>
<w:r>
<w:t>Second cell</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:tcPr>
<w:tcW w:w="2880" w:type="dxa"/>
</w:tcPr>
<w:p>
<w:r>
<w:t>Third cell</w:t>
</w:r>
</w:p>
</w:tc>
</w:tr>
</w:tbl>
Binary file added examples/Custom XML/template.docx
Binary file not shown.
40 changes: 16 additions & 24 deletions src/stencil/model.clj
Original file line number Diff line number Diff line change
Expand Up @@ -381,37 +381,29 @@
:path new-path})))


(defn insert-fragment! [frag-name local-data-map]
(assert (string? frag-name))
(defmethod eval/eval-step :cmd/include [f 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)]
(do (assert (map? local-data-map))
(let [;; merge style definitions from fragment
style-ids-rename (-> fragment-model :main :style :parsed (doto assert) (insert-styles!))
fragment-model (update-in fragment-model [:main :executable :executable]
executable-rename-style-ids style-ids-rename)

relation-ids-rename (relation-ids-rename fragment-model frag-name)
(let [;; merge style definitions from fragment
style-ids-rename (-> fragment-model :main :style :parsed (doto assert) (insert-styles!))
fragment-model (update-in fragment-model [:main :executable :executable]
executable-rename-style-ids style-ids-rename)

fragment-model (update-in fragment-model [:main :executable :executable]
executable-rename-relation-ids (into {} (map (juxt :old-id :new-id) relation-ids-rename)))
relation-ids-rename (relation-ids-rename fragment-model frag-name)

;; evaluate
evaled (eval-template-model fragment-model local-data-map {} {})
fragment-model (update-in fragment-model [:main :executable :executable]
executable-rename-relation-ids (into {} (map (juxt :old-id :new-id) relation-ids-rename)))

;; write back
evaled-parts (-> evaled :main :result :xml (doto assert) extract-body-parts)]
(swap! *inserted-fragments* conj frag-name)
(swap! *extra-files* into relation-ids-rename)
{:frag-evaled-parts evaled-parts}))
;; evaluate
evaled (eval-template-model fragment-model local-data-map {} {})

;; write back
evaled-parts (-> evaled :main :result :xml (doto assert) extract-body-parts)]
(swap! *inserted-fragments* conj frag-name)
(swap! *extra-files* into relation-ids-rename)
(->FragmentInvoke {:frag-evaled-parts evaled-parts}))
(throw (ex-info "Did not find fragment for name!"
{:fragment-name frag-name
:all-fragment-names (set (keys *all-fragments*))})))))


(defmethod eval/eval-step :cmd/include [f local-data item]
(let [chunk-name (:name item)
invoked (insert-fragment! chunk-name local-data)]
(->FragmentInvoke invoked)))
62 changes: 61 additions & 1 deletion src/stencil/postprocess/fragments.clj
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
"Inserts contents of fragments."
(:import [stencil.types FragmentInvoke])
(:require [clojure.zip :as zip]
[clojure.data.xml :as xml]
[stencil.types :refer :all]
[stencil.ooxml :as ooxml]
[stencil.functions :refer [call-fn]]
[stencil.util :refer :all]))


Expand Down Expand Up @@ -62,6 +64,45 @@
(defn- node-r?! [x] (assert (= ooxml/r (:tag (zip/node x)))) x)


(defn- split-texts [chunk-loc & insertable-runs]
(assert false "Not implemented!"))


(defn- split-runs [chunk-loc & insertable-runs]
(assert (seq insertable-runs))
(node-t?! (zip/up chunk-loc))
(let [lefts (zip/lefts chunk-loc)
rights (zip/rights chunk-loc)

t (zip/node (zip/up chunk-loc))
r (zip/node (zip/up (zip/up chunk-loc)))

;; t elems
lefts1 (remove (comp #{ooxml/rPr} :tag) (zip/lefts (zip/up chunk-loc)))
rights1 (zip/rights (zip/up chunk-loc))

;; style of run that is split
style (some #(when (= ooxml/rPr (:tag %)) %) (:content r))

->t (fn [xs] {:tag ooxml/t :content (vec xs)})
->run (fn [cts] (assoc r :content (vec (cons style cts))))]
(assert (= ooxml/t (:tag t)))
(assert (= ooxml/r (:tag r)))
(-> chunk-loc
(zip/up) ;; t
(zip/up) ;; r

(cond-> (seq lefts1) (zip/insert-left (->run lefts1)))
(cond-> (seq lefts) (zip/insert-left (->run [(->t lefts)])))

(cond-> (seq rights1) (zip/insert-right (->run rights1)))
(cond-> (seq rights) (zip/insert-right (->run [(->t rights)])))

(as-> * (reduce zip/insert-right * (reverse insertable-runs)))

(zip/remove))))


(defn- split-paragraphs [chunk-loc & insertable-paragraphs]
(let [p-left (-> chunk-loc
(remove-all-rights)
Expand Down Expand Up @@ -100,15 +141,34 @@
(zip/remove))))


(defn unpack-items [node-to-replace & insertable-nodes]
(assert (zipper? node-to-replace))
(assert (sequential? insertable-nodes))
(cond
(= ooxml/r (:tag (first insertable-nodes)))
(apply split-runs node-to-replace insertable-nodes)

(= ooxml/t (:tag (first insertable-nodes)))
(apply split-texts node-to-replace insertable-nodes)

:default
(apply split-paragraphs node-to-replace insertable-nodes)))


(defn- unpack-fragment [chunk-loc]
(assert (instance? FragmentInvoke (zip/node chunk-loc)))
(let [chunk (-> chunk-loc zip/node :result (doto (assert "result is missing")))
tree-parts (-> chunk :frag-evaled-parts (doto (assert "Evaled parts is missing")))]
(assert (sequential? tree-parts) "Tree parts must be a sequence!")
(apply split-paragraphs chunk-loc tree-parts)))


(defn unpack-fragments
"Walks the tree (Depth First) and evaluates FragmentInvoke objects."
[xml-tree]
(dfs-walk-xml-node xml-tree (partial instance? FragmentInvoke) unpack-fragment))

;; custom XML content
(defmethod call-fn "xml" [_ content]
(assert (string? content))
(let [content (:content (xml/parse-str (str "<a>" content "</a>")))]
(->FragmentInvoke {:frag-evaled-parts content})))
38 changes: 8 additions & 30 deletions src/stencil/postprocess/html.clj
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
(:require [clojure.zip :as zip]
[clojure.data.xml :as xml]
[stencil.functions :refer [call-fn]]
[stencil.postprocess.fragments :as fragments]
[stencil.types :refer [ControlMarker]]
[stencil.util :refer :all]
[stencil.ooxml :as ooxml]))
Expand Down Expand Up @@ -66,38 +67,15 @@
{:tag ooxml/br :content []}
{:tag ooxml/t :content [(str text)]})))}))))

(defn- current-run-style [chunk-loc]
(let [r (zip/node (zip/up (zip/up chunk-loc)))]
(some #(when (= ooxml/rPr (:tag %)) %) (:content r))))

(defn- fix-html-chunk [chunk-loc]
(assert (instance? HtmlChunk (zip/node chunk-loc)))
(let [lefts (zip/lefts chunk-loc)
rights (zip/rights chunk-loc)

t (zip/node (zip/up chunk-loc))
r (zip/node (zip/up (zip/up chunk-loc)))

;; t elems
lefts1 (remove (comp #{ooxml/rPr} :tag) (zip/lefts (zip/up chunk-loc)))
rights1 (zip/rights (zip/up chunk-loc))

style (some #(when (= ooxml/rPr (:tag %)) %) (:content r))
ooxml-runs (html->ooxml-runs (:content (zip/node chunk-loc)) (:content style))

->t (fn [xs] {:tag ooxml/t :content (vec xs)})
->run (fn [cts] (assoc r :content (vec (cons style cts))))]
(assert (= ooxml/t (:tag t)))
(assert (= ooxml/r (:tag r)))
(-> chunk-loc
(zip/up) ;; t
(zip/up) ;; r

(cond-> (seq lefts1) (zip/insert-left (->run lefts1)))
(cond-> (seq lefts) (zip/insert-left (->run [(->t lefts)])))

(cond-> (seq rights1) (zip/insert-right (->run rights1)))
(cond-> (seq rights) (zip/insert-right (->run [(->t rights)])))

(as-> * (reduce zip/insert-right * (reverse ooxml-runs)))

(zip/remove))))
(let [style (current-run-style chunk-loc)
ooxml-runs (html->ooxml-runs (:content (zip/node chunk-loc)) (:content style))]
(apply fragments/unpack-items chunk-loc ooxml-runs)))

(defn fix-html-chunks [xml-tree]
(dfs-walk-xml-node xml-tree #(instance? HtmlChunk %) fix-html-chunk))

0 comments on commit 6631629

Please sign in to comment.