How can I parse string into Hiccup? - clojure

How can I parse a string of Hiccup into a Hiccup node?
For example,
"[:b 'hello world']" into [:b "hello world"]

Use reader to convert string to data structures:
user=> (clojure.edn/read-string "[:b 'hello world']")
[:b 'hello world']
You should use " to denote string:
user=> (clojure.edn/read-string "[:b \"hello world\"]")
[:b "hello world"]

Related

Trying to split string in Clojure running into lazy seq problem

I am working on a problem to read in a file with lines like:
A abcdefg
B bcdefgh
But I keep getting errors about Lazy Sequence not compatible with Java Charseq ..
I tried:
(def notlazy (doall lyne2))
Then thought I verified:
(realized? notlazy)
true
But still:
(str/split notlazy #" ")
ClassCastException class clojure.lang.LazySeq cannot be cast to class
java.lang.CharSequence (clojure.lang.LazySeq is in unnamed module of
loader 'app'; java.lang.CharSequence is in module java.base of loader
'bootstrap') clojure.string/split (string.clj:219)
Help please!
The first argument to str/split must be a CharSequence to be split. Presumably you want to split each input line in the sequence for which you can use map without needing to eagerly evaluate the input sequence:
(map (fn [line] (str/split line #" ")) lyne2)
Expanding on the previous result a bit, we have this example. You can reproduce the following using this template project.
(ns tst.demo.core
(:use demo.core tupelo.core tupelo.test)
(:require
[clojure.java.io :as io]
[tupelo.string :as str]
))
(def data-file
"A abcdefg
B bcdefgh
")
(dotest
; Version #1
(let [lines (line-seq (io/reader (str/string->stream data-file)))
lines2 (remove str/blank? lines)
lines3 (map str/trim lines2)
line-words (mapv #(str/split % #"\s+") lines3) ; "\s+" => "one or more whitespace chars"
]
(spyxx lines)
(spyxx lines2)
(spyxx lines3)
(spyxx line-words))
with result:
--------------------------------------
Clojure 1.10.2-alpha1 Java 15
--------------------------------------
Testing tst.demo.core
lines => <#clojure.lang.Cons ("A abcdefg" " B bcdefgh" " ")>
lines2 => <#clojure.lang.LazySeq ("A abcdefg" " B bcdefgh")>
lines3 => <#clojure.lang.LazySeq ("A abcdefg" "B bcdefgh")>
line-words => <#clojure.lang.PersistentVector [["A" "abcdefg"] ["B" "bcdefgh"]]>
This shows the type of each result along with its value. We use string->stream so we don't need to set up a dummy file to read from.
The following shows how it would typically be written in real code (not as a demo exercise like Version #1). We use the "thread-last" operator, and write a unit test to verify the result:
; Version #2
(let [result (->> data-file
(str/string->stream)
(io/reader)
(line-seq)
(remove str/blank?)
(map str/trim)
(mapv #(str/split % #"\s+"))) ; "\s+" => "one or more whitespace chars"
]
(is= result [["A" "abcdefg"] ["B" "bcdefgh"]])))

Read a file containing data and print values 10 characters before and after into a new file if string "john" is found

I need to print 10 characters before and after string "john",whenever "john" is encountered. Even though half of letters are printed in the next line,it should still print 10 characters before and after.
I have tried using .contains function and using the index of string but the problem arises when i input a file like this:
Hello my name is jo
hn and i work for bla bla.
What I tried:
(ns clojure-assignment.problem6
(:use [clojure.string :only [index-of]]))
(defn String_manipulation []
(def str1 (slurp "string.txt"))
(println str1)
(def check (.contains str1 "john"))
(if (= check true)
(def index (index-of str1 "john")))
(println (subs str1 (- index 11) index))
(println (subs str1 (+ index 4) (+ index 5 10))))
(String_manipulation)
I expect the output to print values 10 characters before and after the given string and it should also work if there is a line end.
You can use this:
(def s "Hello my name is jo
hn and i work for bla bla.
")
(re-seq #".{10}john.{10}" (str/replace s #"\n" ""))
;; => ("y name is john and i wor")
UPDATE 1 - overlapping pattern
If pattern is expected to be overlapping, use look ahead regex and extract from the matched group:
(def s "hello my name is john-john and john is my father's name")
(re-seq #"(?=(.{10}john.{10}))" (str/replace s #"\n" ""))
(["" "y name is john-john and "]
["" "e is john-john and john "]
["" "-john and john is my fat"])
UPDATE 2 - print prefix and suffix match group
(use 'clojure.pprint)
(def s "Hello my name is jo
hn and i work for bla bla.
")
(->> (str/replace s #"\n" "")
(re-seq #"(?=(.{10})john(.{10}))")
(cl-format *out* "~{~{~a~}~%~}"))
;; => y name is and i wor

Clojure say-hi with varargs

Input: "Michael" "Julia" "Joe" "Sam"
Output: Hi, Michael, Julia, Joe, and Sam. (pay attention to the commas and the word "and")
Input: nil
Output: Hi, world.
Here is my first attempt:
(defn say-hi [& name]
(print "Hi," name))
user> (say-hi "Michael")
Hi, (Michael)
nil
user> (say-hi "Michael" "Julia")
Hi, (Michael Julia)
nil
Question:
How to implement default: (no input, say "Hi World!")
How to get rid of the parents around names in output?
How to implement the commas separation and add the conjunction word "and"?
First off, Clojure supports multi-arity functions, so you could do something like this to achieve default behaviour:
(defn say-hi
([] (say-hi "World"))
([& names] ...))
Then, what you want is to take a seq and join all the strings it contains together, using ", " in between. The clojure.string namespaces contains lots of string manipulation functions, one of them being clojure.string/join:
(require '[clojure.string :as string])
(string/join ", " ["Michael", "Julia"])
;; => "Michael, Julia"
But the last element of the seq should be concatenated using " and " as a separator, so you'll end up with something like this:
(require '[clojure.string :as string])
(defn say-hi
([] (say-hi "World"))
([& names]
(if (next names)
(format "Hi, %s, and %s!"
(string/join ", " (butlast names))
(last names))
(format "Hi, %s!" (first names)))))
Note that you have to differentiate between the single- and multi-name cases and (next names) basically checks whether the seq contains more than one element. (You could achieve the same by adding another arity to the function.)
(say-hi)
;; => "Hi, World!"
(say-hi "Michael")
;; => "Hi, Michael!"
(say-hi "Michael" "Julia" "Joe" "Sam")
;; => "Hi, Michael, Julia, Joe, and Sam!"
You can use clojure.string/join:
(use '[clojure.string :only [join]])
(defn sentencify [& elems]
(->>
[(join ", " (butlast elems)) (last elems)]
(remove empty?)
(join " and ")))
(defn say-hi [& name]
(print "Hi," (if name
(sentencify name)
"World!")))
A concise solution:
(defn say-hi [& names]
(let [names (case (count names)
0 ["world"]
1 names
(concat (butlast names) (list (str "and " (last names)))))]
(->> names, (cons "Hi"), (interpose ", "), (apply str))))
(say-hi)
;"Hi, world"
(say-hi "Michael")
;"Hi, Michael"
(say-hi "Michael" "Julia" "Joe" "Sam")
;"Hi, Michael, Julia, Joe, and Sam"
For long lists of names, you would want to eschew count, last, and butlast, maybe by pouring names into a vector first.
To print (as the question does) rather than return the formatted string, append print to the final form:
(->> names, (cons "Hi"), (interpose ", "), (apply str), print)

How can I read strings with backslashes?

Apparently I can't read-string some strings, like
user> (read-string "\" \\ABC \"")
RuntimeException Unsupported escape character: \A clojure.lang.Util.runtimeException (Util.java:219)
user>
Is there a way around that?
Thanks!
I assume that you want to end up with a string that when you print its "\ABC", so:
user=> (println "\\ABC")
\ABC
nil
As you see, the reader needs two "\". As read-string expects the string to be a valid Clojure expression and from your example you are trying to read a string that contains a string, you need to escape both the " (as you are doing) and the two \ :
user=> (def s (read-string "\" \\\\AB\""))
#'user/s
user=> (class s)
java.lang.String
user=> (println s)
\AB
nil
user=> s
" \\AB"

How to convert a clojure keyword into a string?

In my application I need to convert clojure keyword eg. :var_name into a string "var_name". Any ideas how that could be done?
user=> (doc name)
-------------------------
clojure.core/name
([x])
Returns the name String of a string, symbol or keyword.
nil
user=> (name :var_name)
"var_name"
Actually, it's just as easy to get the namespace portion of a keyword:
(name :foo/bar) => "bar"
(namespace :foo/bar) => "foo"
Note that namespaces with multiple segments are separated with a '.', not a '/'
(namespace :foo/bar/baz) => throws exception: Invalid token: :foo/bar/baz
(namespace :foo.bar/baz) => "foo.bar"
And this also works with namespace qualified keywords:
;; assuming in the namespace foo.bar
(namespace ::baz) => "foo.bar"
(name ::baz) => "baz"
Note that kotarak's answer won't return the namespace part of keyword, just the name part - so :
(name :foo/bar)
>"bar"
Using his other comment gives what you asked for :
(subs (str :foo/bar) 1)
>"foo/bar"
It's not a tedious task to convert any data type into a string, Here is an example by using str.
(defn ConvertVectorToString []
(let [vector [1 2 3 4]]
(def toString (str vector)))
(println toString)
(println (type toString)
(let [KeyWordExample (keyword 10)]
(def ConvertKeywordToString (str KeyWordExample)))
(println ConvertKeywordToString)
(println (type ConvertKeywordToString))
(ConvertVectorToString) ;;Calling ConvertVectorToString Function
Output will be:
1234
java.lang.string
10
java.lang.string
This will also give you a string from a keyword:
(str (name :baz)) -> "baz"
(str (name ::baz)) -> "baz"