alistair33
alistair33

Reputation: 21

How to read text one line at a time from a file, and assign the text to a variable in Clojure

I want to open a large XML file in Clojure, process the text line per line then dump a selection of strings into a work file.

I am totally new to this so for now I just want to be able to (1) read a file, and (2) assign the selection to a variable. I will be working with large XML files so slurping is not an option.

Anyway, I'm using this code I got from a tutorial. Whenever I execute it in REPL, it prints out the text contained in my file, but seems to fail with creating the variable str1(unresolved symbol error).

Here's the code:

(defn readfile []
  (let [str1    ;; I want the text inside the text file to fill this variable
        (with-open [rdr (io/reader "resources/loremipsum.txt")]
          (reduce conj [] (line-seq rdr)))] str1))
(readfile)

Upvotes: 2

Views: 379

Answers (3)

l0st3d
l0st3d

Reputation: 2968

If you're trying to deal with xml in clojure, I'd recommend clojure.data.zip. Here's a quick example:

(require '[clojure.xml :as xml])
(require '[clojure.zip :as zip])
(require '[clojure.data.zip.xml :as zf])
(import '[java.io ByteArrayInputStream])
(with-open [in-stream (ByteArrayInputStream. (.getBytes "
<xml>
  <test>something</test>
  <fish>dog</fish>
  <test>something else</test>
</xml>"))]
  (let [parsed (zip/xml-zip (xml/parse in-stream))]
    (zf/xml-> parsed :xml :test zf/text)))

It will produce a lazy sequence of the selector provided. It's quite often a really nice way to work with XML, and may suit your needs.

Upvotes: 1

Arthur Ulfeldt
Arthur Ulfeldt

Reputation: 91597

the code you have listed here is correct, did something get changed in copying and pasting it in?

user=> (require '[clojure.java.io :as io])
nil

(defn readfile []
  (let [str1 (with-open [rdr (io/reader "lorum-lispsum")]
               (reduce conj [] (line-seq rdr)))] 
    str1))
#'user/readfile

user=> (readfile)
["let lambda bind lambda let lambda"]

the patern (reduce conj...) is often more easily written with the into function.

(defn readfile []
  (let [str1 (with-open [rdr (io/reader "lorum-lispsum")]
               (into [] (line-seq rdr)))] 
    str1))

and since the effect of the into here is both to make it a vector and to pull the entire file into memory simultaneously it could be replaced with a call to vec

(defn readfile []
  (let [str1 (with-open [rdr (io/reader "lorum-lispsum")]
               (vec (line-seq rdr)))] 
    str1))

since you also mention that not storing the whole file in memory is a goal, we may want to rearrange things to keep the file open while the sequence is processed as a lazy sequence

(require '[clojure.java.io :as io])

(defn process-a-line [line]
  :your-code-here)

(defn the-main-part-of-my-program [lazy-sequence-of-lines]
  (dorun (map process-a-line)))

(defn readfile []
  (let [str1 (with-open [rdr (io/reader "lorum-lispsum")]
               (line-seq rdr))] 
    (the-main-part-of-my-program str1)))

Upvotes: 2

Lee
Lee

Reputation: 144206

Bindings declared within a let are only visible within the let body. Since your readfile function returns the lines of the file you can create a var and assign the result of calling the function to it:

(def lines (readfile))

Upvotes: 0

Related Questions