Clojure.data.xml: append XML Element - clojure

My Goal is to append Child Elements under another XML Element (the parent). Lets say under <Books>.
(def bookshelf
(xml/element :books {}))
So i would like to create <Book> Elements dynamically and append to as Long as they are created one After the other.
Does anyone have a clue how to do this using the lib clojure.data.xml?
Thx in advance.
Horace

As mentioned in the Quick Start Examples of their library you can do it as follows:
(let [tags (element :foo {:foo-attr "foo value"}
(element :bar {:bar-attr "bar value"}
(element :baz {} "The baz value")))]
(with-open [out-file (java.io.FileWriter. "/tmp/foo.xml")]
(emit tags out-file)))
Gives you
<?xml version="1.0" encoding="UTF-8"?>
<foo foo-attr="foo value">
<bar bar-attr="bar value">
<baz>
The baz value
</baz>
</bar>
</foo>

Related

Wrap HTML tags around pretty-printed Clojure forms

Clojure's pretty printer (clojure.pprint) takes unformatted code like this:
(defn fib ([n] (fib n 1 0)) ([n a b] (if (= n 0) a (fib (dec n) (+ a b) a))))
And makes it nice, like this.
(defn fib
([n] (fib n 1 0))
([n a b]
(if (= n 0)
a
(fib (dec n) (+ a b) a))))
I'd like to put some source in a web page, so I'd like it to be pretty-printed. But I'd also like to wrap each form in a set of < span > tags with a unique ID so I can manipulate the representation with javascript. That is, I want to turn
(foo bar baz)
into
<span id="001">(<span id="002">foo</span> <span id="003">bar</span> <span id="004">baz</span>)</span>
But I still want the resulting forms to be indented like the pretty printer would, so that the code that actually gets displayed looks right.
Some of the documentation for the pretty printer mentions that it can take custom dispatch functions, but I can't find anything about what they do or how to define them. Is it possible to do what I want with such a beast, and if so can someone provide me with some information on how to do it?
There are ways to pretty print XML, as you can see here:
https://nakkaya.com/2010/03/27/pretty-printing-xml-with-clojure/
That person used
(defn ppxml [xml]
(let [in (javax.xml.transform.stream.StreamSource.
(java.io.StringReader. xml))
writer (java.io.StringWriter.)
out (javax.xml.transform.stream.StreamResult. writer)
transformer (.newTransformer
(javax.xml.transform.TransformerFactory/newInstance))]
(.setOutputProperty transformer
javax.xml.transform.OutputKeys/INDENT "yes")
(.setOutputProperty transformer
"{http://xml.apache.org/xslt}indent-amount" "2")
(.setOutputProperty transformer
javax.xml.transform.OutputKeys/METHOD "xml")
(.transform transformer in out)
(-> out .getWriter .toString)))
So if you put your HTMl string (which is not exactly a subset of XML), you would get:
(ppxml "<root><child>aaa</child><child/></root>")
output:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<child>aaa</child>
<child/>
</root>
In Clojure, using Compojure, you can build HTML/XML tags in a very lispy syntax.
You can use them too:
(ppxml (html
[:html
[:head
[:title "Hello World"]]
[:body "Hello World!"]]))
With the output of:
<html>
<head>
<title>Hello World</title>
</head>
<body>Hello World!</body>
</html>
You see also suggestions here:
Compojure HTML Formatting
It's possible, but I think it's much more work than you expect. You'll work with source code pprint/dispatch and modify functions that are already here.
You'll surely need with-pprint-dispatch. This function uses given dispatch function to execute body:
(with-pprint-dispatch code-dispatch
(pprint '(foo bar baz)))
(foo bar baz)
=> nil
Look for function code-dispatch and see it's definition:
(defmulti
code-dispatch
"The pretty print dispatch function for pretty printing Clojure code."
{:added "1.2" :arglists '[[object]]}
class)
(use-method code-dispatch clojure.lang.ISeq pprint-code-list)
(use-method code-dispatch clojure.lang.Symbol pprint-code-symbol)
;; The following are all exact copies of simple-dispatch
(use-method code-dispatch clojure.lang.IPersistentVector pprint-vector)
(use-method code-dispatch clojure.lang.IPersistentMap pprint-map)
(use-method code-dispatch clojure.lang.IPersistentSet pprint-set)
(use-method code-dispatch clojure.lang.PersistentQueue pprint-pqueue)
(use-method code-dispatch clojure.lang.IDeref pprint-ideref)
(use-method code-dispatch nil pr)
(use-method code-dispatch :default pprint-simple-default)
As you can see, there is special function for each collection type. I just picked list and vector and my dispatch function looks like this:
(defmulti
my-code-dispatch
class)
(use-method my-code-dispatch clojure.lang.ISeq my-pprint-code-list)
(use-method my-code-dispatch clojure.lang.IPersistentVector my-pprint-vector)
(use-method my-code-dispatch :default my-pprint-simple-default)
Now, look for pprint-code-list, pprint-vector and pprint-simple-default. Two of them use pprint-logical-block with keywords :prefix and :suffix- that's the place where you insert additional string (the rest of function will be the same). Don't forget to define some counter for span numbering:
(in-ns 'clojure.pprint)
(def id (atom 0))
(defn- my-pprint-vector [avec]
(pprint-meta avec)
(pprint-logical-block :prefix (format "<span id=\"%03d\">[" (swap! id inc))
:suffix "]</span>"
...)
(defn- my-pprint-simple-default [obj]
(cond
(.isArray (class obj)) (pprint-array obj)
(and *print-suppress-namespaces* (symbol? obj)) (print (name obj))
:else (cl-format true "<span id=\"~3,'0d\">~s</span>"
(swap! id inc)
obj)))
(defn- my-pprint-simple-code-list [alis]
(pprint-logical-block :prefix (format "<span id=\"%03d\">(" (swap! id inc))
:suffix ")</span>"
...)
(defn- my-pprint-code-list [alis]
(if-not (pprint-reader-macro alis)
(if-let [special-form (*code-table* (first alis))]
(special-form alis)
(my-pprint-simple-code-list alis))))
With all this setup, I called:
(with-pprint-dispatch my-code-dispatch
(pprint '(foo bar baz)))
<span id="001">(<span id="002">foo</span>
<span id="003">bar</span>
<span id="004">baz</span>)</span>
=> nil
Or you can print it into string:
(with-out-str (with-pprint-dispatch my-code-dispatch
(pprint '(foo bar baz))))
=>
"<span id=\"001\">(<span id=\"002\">foo</span>\r
<span id=\"003\">bar</span>\r
<span id=\"004\">baz</span>)</span>\r
"
And I have to mention again that for printing some real code, you would have to modify all functions for all data types. So- it's possible? Yes. Worth the effort? I doubt it.

What is the correct way to dynamically render components in reagent?

Dynamically rendering components in React is fundamental to its use. It's very easy to do as can be seen here:
render() {
return (
<div className="blocks_loop">
{this.props.blocks.map(block => (
<div className="block" />
))}
</div>
)
}
In this example you will get as many divs rendered as there are blocks. I am trying to achieve the same thing with reagent, part of which I've documented in this post. There are examples out there of people doing it like this one, but they all seem to include the use of lists which I don't want to use - it just doesn't suit my purpose. I just want as many components out as items I put in.
Update
I now have this code trying to follow the answer below which is meant to render 3 divs for every key value pair in my-map. Nothing is rendered and it throws the error react-dom.development.js:507 Warning: Functions are not valid as a React child.:
(ns mapping-test.views
(:require
[re-frame.core :as re-frame]
[mapping-test.subs :as subs]))
(defn main-panel []
(def my-map {:a 1 :b 2 :c 3})
(defn a-component []
[:h1 "This is a component rendering"])
(defn my-loop [my-map]
(for [value my-map]
[a-component]))
(fn []
[my-loop my-map]))
(defn my-component [blocks]
[:div.blocks_loop
(for [b blocks]
[:div.block])])
Since you are creating hiccup, you can just use any clojure code to map or loop through your data.

How to elegantly parse xml in clojure

I have this piece of code building up sentences from XML looking like follows. I wonder what might be an alternative code, that would be more readable after being hacked to work.
(mapcat
(fn [el]
(map special-join
(map
(fn [el] (map zip-xml/text (zip-xml/xml-> el :word)))
(zip-xml/xml-> el :sentence))))
(zip-xml/xml-> root :document))
The above code is not very readable, given the repeat inline function definitions combined with the nested probing, but tearing them apart into standalone functions as in this official tutorial really doesn't make sense to me for such simple cases.
For completeness, here's the repeat XML structure that this is parsing
<document>
<sentence id="1">
<word id="1.1">Foo</w>
<word id="1.2">bar</w>
</sentence>
</document>
Zippers may be overkill in this situation. clojure.xml/parse will give you a simple data structure representing the HTML.
(require '[clojure.xml :as xml] '[clojure.string :as string])
(def doc
(->
"<document>
<sentence id=\"1\">
<word id=\"1.1\">
Foo
</word>
<word id=\"1.2\">
bar
</word>
</sentence>
</document>
" .getBytes java.io.ByteArrayInputStream. xml/parse))
Then you can use xml-seq to get all the <sentence> tags and their children, gathering the children's text content, trimming whitespace, and joining with spaces.
(->> doc
xml-seq
(filter (comp #{:sentence} :tag))
(map :content)
(map #(transduce
(comp
(mapcat :content)
(map string/trim)
(interpose " "))
str %)))
I do not like the way zippers work in Clojure, and I've not looked at clojure.zip/xml-zip or clojure.data.zip/xml-> (confusing that they are two separate libs!).
Instead, may I suggest you try out the tupelo.forest library? Here is an overview from the 2017 Clojure/Conj.
Below is a live solution using tupelo.forest. I added a second sentence to make it more interesting:
(dotest
(with-forest (new-forest)
(let [xml-str (ts/quotes->double
"<document>
<sentence id='1'>
<word id='1.1'>foo</word>
<word id='1.2'>bar</word>
</sentence>
<sentence id='2'>
<word id='2.1'>beyond</word>
<word id='2.2'>all</word>
<word id='2.3'>recognition</word>
</sentence>
</document>")
root-hid (add-tree-xml xml-str)
>> (remove-whitespace-leaves)
bush-no-blanks (hid->bush root-hid)
sentence-hids (find-hids root-hid [:document :sentence])
sentences (forv [sentence-hid sentence-hids]
(let [word-hids (hid->kids sentence-hid)
words (mapv #(grab :value (hid->leaf %)) word-hids)
sentence-text (str/join \space words)]
sentence-text))
]
(is= bush-no-blanks
[{:tag :document}
[{:id "1", :tag :sentence}
[{:id "1.1", :tag :word, :value "foo"}]
[{:id "1.2", :tag :word, :value "bar"}]]
[{:id "2", :tag :sentence}
[{:id "2.1", :tag :word, :value "beyond"}]
[{:id "2.2", :tag :word, :value "all"}]
[{:id "2.3", :tag :word, :value "recognition"}]]])
(is= sentences
["foo bar"
"beyond all recognition"]))))
The idea is to find the hid (Hex ID, like a pointer) for each sentence. In the forv loop, we find the child nodes for each sentence, extract the :value, and joint into a string. The unit tests show the tree structure as parsed from XML (after deleting blank nodes) and the final result. Note that we ignore the id fields and use only the tree structure to understand the sentences.
Documentation for tupelo.forest is still a work in progress, but you can see many live examples here.
The Tupelo project lives on GitHub.\
Update
I have been thinking about the streaming data problem, and have added a new function proc-tree-enlive-lazy to enable lazy processing of large data sets. Here is an example:
(let [xml-str (ts/quotes->double
"<document>
<sentence id='1'>
<word id='1.1'>foo</word>
<word id='1.2'>bar</word>
</sentence>
<sentence id='2'>
<word id='2.1'>beyond</word>
<word id='2.2'>all</word>
<word id='2.3'>recognition</word>
</sentence>
</document>")
(let [enlive-tree-lazy (clojure.data.xml/parse (StringReader. xml-str))
doc-sentence-handler (fn [root-hid]
(remove-whitespace-leaves)
(let [sentence-hid (only (find-hids root-hid [:document :sentence]))
word-hids (hid->kids sentence-hid)
words (mapv #(grab :value (hid->leaf %)) word-hids)
sentence-text (str/join \space words)]
sentence-text))
result-sentences (proc-tree-enlive-lazy enlive-tree-lazy
[:document :sentence] doc-sentence-handler)]
(is= result-sentences ["foo bar" "beyond all recognition"])) ))
The idea is that you process successive subtrees, in this case whenever you get a subtree path of [:document :sentence]. You pass in a handler function, which will receive the root-hid of a tupelo.forest tree. The return value of the handler is then placed onto an output lazy sequence returned to the caller.

How to repeat a list of items from a vector in hiccup?

If I have a vector name-lst as ["John" "Mary" "Watson" "James"],
and I want to diplay them as list items, how do I do that using hiccup ?
something like
[:ul
(for [name name-list]
[:li name])]
will return a list of [:li ] in between [:ul ] instead of just repeating.
There must be something better. I am relatively new to hiccup, I searched but couldn't find anything.
Once you feed the data structure to Hiccup you should get the expected result:
(require '[hiccup.core :refer [html]])
(def names
["John" "Mary" "Watson" "James"])
(html [:ul
(for [name names]
[:li name])])
;=> "<ul><li>John</li><li>Mary</li><li>Watson</li><li>James</li></ul>"

Adding script to lacij graph

From what I can understand the lacij library does not have support for the "script" tag. So I was trying to include a javascript src.
When I try the following
(def new-graph (lacij.edit/build old-graph))
(def element (tikkba.util.dom/elements (:xmldoc new-graph) tikkba.dom/svg-ns [:script {:src "myscript.js"}]))
(println element)
(tikkba.util.dom/append-child (:xmldoc new-graph) element)
I get a " The node (type: 1, name: script) cannot be inserted, since the document node already has a node of type 1. " exception and not I am not sure why. "Script" node is not in the Dom in the first place.
I'd appreciate any kind of help here.
OK - so I've got something for you to try. I've not used lacij or tikkba before, so I'm not sure if it's idiomatic. Mutating an xml document in this way in Clojure makes me feel a bit uneasy - but I guess that's just the way when doing java interop.
Anyway, here goes:
First build the graph using lacij:
(use 'lacij.edit.graph)
(def g
(-> (graph)
(add-node :a "A" :x 50 :y 20)
(add-node :b "B" :x 50 :y 100)
(add-edge :a-to-b :a :b)
(build)))
Then create and add the script element using tikkba:
(use 'tikkba.utils.dom)
(def element
(elements
(:xmldoc g)
tikkba.dom/svg-ns
[:script {:src "myscript.js"}]))
(append-child (document-element (:xmldoc g)) element)
(spit-xml "test-with-script.svg" (:xmldoc g))
Script will be appended at the end of the document.
Reasoning
I guess before tikkba was complaining about adding at the top level of the XML file. There was already a document tag (<svg>) in the xml document and XML files can only have one root element. The code above instructs tikkba to add within the svg element instead of at the top level. It seems to work.