source: trunk/src/base-tools/base-tools.lisp

Last change on this file was 465, checked in by lgiessmann, 13 years ago

JTM: added unit-tests for importing jtm-strings containing entire topic maps

File size: 18.9 KB
Line 
1;;+-----------------------------------------------------------------------------
2;;+  Isidorus
3;;+  (c) 2008-2010 Marc Kuester, Christoph Ludwig, Lukas Georgieff
4;;+
5;;+  Isidorus is freely distributable under the LLGPL license.
6;;+  You can find a detailed description in trunk/docs/LLGPL-LICENSE.txt and
7;;+  trunk/docs/LGPL-LICENSE.txt.
8;;+-----------------------------------------------------------------------------
9
10(defpackage :base-tools
11  (:use :cl)
12  (:nicknames :tools)
13  (:export :push-string
14           :concat
15           :when-do
16           :string-replace
17           :remove-null
18           :full-path
19           :trim-whitespace-left
20           :trim-whitespace-right
21           :trim-whitespace
22           :string-starts-with
23           :string-ends-with
24           :string-ends-with-one-of
25           :string-starts-with-char
26           :string-starts-with-one-of
27           :string-until
28           :string-after
29           :search-first
30           :search-first-ignore-literals
31           :concatenate-uri
32           :absolute-uri-p
33           :string-starts-with-digit
34           :string-after-number
35           :separate-leading-digits
36           :white-space
37           :white-space-p
38           :escape-string
39           :search-first-unclosed-paranthesis 
40           :search-first-unopened-paranthesis
41           :in-literal-string-p
42           :find-literal-end
43           :get-literal-quotation
44           :get-literal
45           :return-if-starts-with
46           :prefix-of-uri
47           :get-store-spec
48           :open-tm-store
49           :close-tm-store
50           :read-file-to-string))
51
52(in-package :base-tools)
53
54
55(defparameter *white-space*
56  (list #\Space #\Tab #\Newline #\cr)
57  "Contains all characters that are treated as white space.")
58
59
60(defun white-space()
61  "Returns a lit os string that represents a white space."
62  (map 'list #'(lambda(char)
63                 (string char))
64       *white-space*))
65
66
67(defmacro concat (&rest strings)
68  `(concatenate 'string ,@strings))
69
70
71(defmacro push-string (obj place)
72  "Imitates the push macro but instead of pushing object in a list,
73   there will be appended the given string to the main string object."
74  `(setf ,place (concat ,place ,obj)))
75
76
77(defmacro when-do (result-bounding condition-statement do-with-result)
78  "Executes the first statement and stores its result in the variable result.
79   If result isn't nil the second statement is called.
80   The second statement can use the variable tools:result as a parameter."
81  `(let ((,result-bounding ,condition-statement))
82     (if ,result-bounding
83         ,do-with-result
84         nil)))
85
86
87(defun white-space-p (str)
88  "Returns t if the passed str contains only white space characters."
89  (cond ((and (= (length str) 1)
90              (string-starts-with-one-of str (white-space)))
91         t)
92        ((string-starts-with-one-of str (white-space))
93         (white-space-p (subseq str 1)))
94        (t
95         nil)))
96
97
98(defun remove-null (lst)
99  "Removes all null values from the passed list."
100  (remove-if #'null lst))
101
102
103(defun full-path (pathname)
104  "Returns a string that represents the full path of the passed
105   CL:Pathname construct."
106  (declare (CL:Pathname pathname))
107  (let ((segments
108         (remove-if #'null
109                    (map 'list #'(lambda(item)
110                                   (when (stringp item)
111                                     (concat "/" item)))
112                         (pathname-directory pathname))))
113        (full-path-string ""))
114    (dolist (segment segments)
115      (push-string segment full-path-string))
116    (concat full-path-string "/" (pathname-name pathname))))
117
118
119(defun trim-whitespace-left (value)
120  "Uses string-left-trim with a predefined character-list."
121  (declare (type (or Null String) value))
122  (when value
123    (string-left-trim *white-space* value)))
124
125
126(defun trim-whitespace-right (value)
127  "Uses string-right-trim with a predefined character-list."
128  (declare (type (or Null String) value))
129  (when value
130    (string-right-trim *white-space* value)))
131
132
133(defun trim-whitespace (value)
134  "Uses string-trim with a predefined character-list."
135  (declare (type (or Null String) value))
136  (when value
137    (string-trim *white-space* value)))
138
139
140(defun string-starts-with (str prefix &key (ignore-case nil))
141  "Checks if string str starts with a given prefix."
142  (declare (String str prefix)
143           (Boolean ignore-case))
144  (let ((str-i (if ignore-case
145                   (string-downcase str :start 0 :end (min (length str)
146                                                           (length prefix)))
147                   str))
148        (prefix-i (if ignore-case
149                      (string-downcase prefix)
150                      prefix)))
151    (string= str-i prefix-i :start1 0 :end1
152             (min (length prefix-i)
153                  (length str-i)))))
154
155
156(defun string-starts-with-one-of (str prefixes &key (ignore-case nil))
157  "Returns t if str ends with one of the string contained in suffixes."
158  (declare (String str)
159           (List prefixes)
160           (Boolean ignore-case))
161  (loop for prefix in prefixes
162     when (string-starts-with str prefix :ignore-case ignore-case)
163     return t))
164
165
166(defun string-ends-with (str suffix &key (ignore-case nil))
167  "Checks if string str ends with a given suffix."
168  (declare (String str suffix)
169           (Boolean ignore-case))
170  (let ((str-i (if ignore-case
171                   (string-downcase str :start (max (- (length str)
172                                                       (length suffix))
173                                                    0)
174                                    :end (length str))
175                   str))
176        (suffix-i (if ignore-case
177                      (string-downcase suffix)
178                      suffix)))
179    (string= str-i suffix-i :start1 (max (- (length str)
180                                            (length suffix))
181                                         0))))
182
183
184(defun string-ends-with-one-of (str suffixes &key (ignore-case nil))
185  "Returns t if str ends with one of the string contained in suffixes."
186  (declare (String str)
187           (List suffixes)
188           (Boolean ignore-case))
189  (loop for suffix in suffixes
190     when (string-ends-with str suffix :ignore-case ignore-case)
191     return t))
192
193
194(defun string-replace (main-string string-to-replace new-string)
195  "Replaces every occurrence of string-to-replace by new-string
196   in main-string."
197  (declare (String main-string string-to-replace new-string))
198  (if (string= string-to-replace new-string)
199      main-string
200      (let ((search-idx (search-first (list string-to-replace) main-string)))
201        (if (not search-idx)
202            main-string
203            (let* ((leading-part (subseq main-string 0 search-idx))
204                   (trailing-part
205                    (subseq main-string
206                            (+ search-idx (length string-to-replace))))
207                   (modified-string
208                    (concat leading-part new-string trailing-part)))
209              (if (search-first (list string-to-replace) trailing-part)
210                  (string-replace modified-string string-to-replace new-string)
211                  modified-string))))))
212
213
214
215(defun string-starts-with-digit (str)
216  "Checks whether the passed string starts with a digit."
217  (declare (String str))
218  (loop for item in (list 0 1 2 3 4 5 6 7 8 9)
219     when (string-starts-with str (write-to-string item))
220     return t))
221
222
223(defun string-after-number (str)
224  "If str starts with a digit, there is returned the first
225   substring after a character that is a non-digit.
226   If str does not start with a digit str is returned."
227  (declare (String str))
228  (if (and (string-starts-with-digit str)
229           (> (length str) 0))
230      (string-after-number (subseq str 1))
231      str))
232
233
234(defun separate-leading-digits (str &optional digits)
235  "If str starts with a number the number is returned."
236  (declare (String str)
237           (type (or Null String) digits))
238  (if (string-starts-with-digit str)
239      (separate-leading-digits
240       (subseq str 1) (concat digits (subseq str 0 1)))
241      digits))
242
243
244(defun string-starts-with-char (begin str)
245  (equal (char str 0) begin))
246
247
248(defun string-until (str anchor)
249  "Returns a substring until the position of the passed anchor."
250  (declare (String str anchor))
251  (let ((pos (search anchor str)))
252    (if pos
253        (subseq str 0 pos)
254        str)))
255
256
257(defun string-after (str prefix)
258  "Returns the substring after the found prefix.
259   If there is no substring equal to prefix nil is returned."
260  (declare (String str prefix))
261  (let ((pos (search prefix str)))
262    (if pos
263        (subseq str (+ pos (length prefix)))
264        nil)))
265
266
267(defun search-first (search-strings main-string &key from-end)
268  "Returns the position of one of the search-strings. The returned position
269   is the one closest to 0. If no search-string is found, nil is returned."
270  (declare (String main-string)
271           (List search-strings))
272  (let ((positions
273         (remove-null
274          (map 'list #'(lambda(search-str)
275                         (search search-str main-string :from-end from-end))
276               search-strings))))
277    (let ((sorted-positions (if from-end
278                                (sort positions #'>)
279                                (sort positions #'<))))
280      (when sorted-positions
281        (first sorted-positions)))))
282
283
284(defun find-literal-end (query-string delimiter &optional (overall-pos 0))
285  "Returns the end of the literal corresponding to the passed delimiter
286   string. The query-string must start after the opening literal delimiter.
287   The return value is an int that represents the start index of closing
288   delimiter. delimiter must be either \", ', \"\"\", or '''.
289   If the returns value is nil, there is no closing delimiter."
290  (declare (String query-string delimiter)
291           (Integer overall-pos))
292  (let ((current-pos (search delimiter query-string)))
293    (if current-pos
294        (if (string-ends-with (subseq query-string 0 current-pos) "\\")
295            (find-literal-end (subseq query-string (+ current-pos
296                                                      (length delimiter)))
297                              delimiter (+ overall-pos current-pos 1))
298            (+ overall-pos current-pos (length delimiter)))
299        nil)))
300
301
302(defun get-literal-quotation (str)
303  "Returns ', ''', \" or \"\"\" when the string starts with a literal delimiter."
304  (cond ((string-starts-with str "'''")
305         "'''")
306        ((string-starts-with str "\"\"\"")
307         "\"\"\"")
308        ((string-starts-with str "'")
309         "'")
310        ((string-starts-with str "\"")
311         "\"")))
312
313
314(defun get-literal (query-string &key (quotation nil))
315  "Returns a list of the form (:next-string <string> :literal <string>
316   where next-query is the query after the found literal and literal
317   is the literal string."
318  (declare (String query-string)
319           (type (or Null String) quotation))
320  (let ((local-quotation quotation))
321    (cond ((or (string-starts-with query-string "\"\"\"")
322               (string-starts-with query-string "'''"))
323           (unless local-quotation
324             (setf local-quotation (subseq query-string 0 3)))
325           (let ((literal-end
326                  (find-literal-end (subseq query-string 3) (subseq query-string 0 3))))
327             (when literal-end
328               (list :next-string (subseq query-string (+ 3 literal-end))
329                     :literal (concat quotation
330                                      (subseq query-string 3 literal-end)
331                                      quotation)))))
332          ((or (string-starts-with query-string "\"")
333               (string-starts-with query-string "'"))
334           (unless local-quotation
335             (setf local-quotation (subseq query-string 0 1)))
336           (let ((literal-end
337                  (find-literal-end (subseq query-string 1)
338                                    (subseq query-string 0 1))))
339             (when literal-end
340               (let ((literal
341                      (escape-string (subseq query-string 1 literal-end) "\"")))
342                 (list :next-string (subseq query-string (+ 1 literal-end))
343                       :literal (concat local-quotation literal
344                                        local-quotation)))))))))
345
346
347(defun search-first-ignore-literals (search-strings main-string &key from-end)
348  (declare (String main-string)
349           (List search-strings)
350           (Boolean from-end))
351  (let ((first-pos
352         (search-first search-strings main-string :from-end from-end)))
353    (when first-pos
354      (if (not (in-literal-string-p main-string first-pos))
355          first-pos
356          (let* ((literal-start
357                  (search-first (list "\"" "'") (subseq main-string 0 first-pos)
358                                :from-end from-end))
359                 (next-str
360                  (if from-end               
361                      (subseq main-string 0 literal-start)
362                      (let* ((sub-str (subseq main-string literal-start))
363                             (literal-result (get-literal sub-str)))
364                        (getf literal-result :next-string)))))
365            (let ((next-pos
366                   (search-first-ignore-literals search-strings next-str
367                                                 :from-end from-end)))
368              (when next-pos
369                (+ (- (length main-string) (length next-str)) next-pos))))))))
370
371
372(defun concatenate-uri (absolute-ns value)
373  "Returns a string conctenated of the absolut namespace an the given value
374   separated by either '#' or '/'."
375  (declare (string absolute-ns value))
376  (unless (and (> (length absolute-ns) 0)
377               (> (length value) 0))
378    (error "From concatenate-uri(): absolute-ns and value must be of length > 0"))
379  (unless (absolute-uri-p absolute-ns)
380    (error "From concatenate-uri(): absolute-ns has to be an absolute URI: ~a" absolute-ns))
381  (let ((last-char
382         (elt absolute-ns (- (length absolute-ns) 1)))
383        (first-char
384         (elt value 0)))
385    (let ((separator
386           (cond
387             ((or (eql first-char #\#)
388                  (eql first-char #\/))
389              "")
390             ((or (eql last-char #\#)
391                  (eql last-char #\/))
392              "")
393             (t
394              "/"))))
395      (let ((prep-ns
396             (if (and (eql last-char first-char)
397                      (or (eql last-char #\#)
398                          (eql last-char #\/)))
399                 (subseq absolute-ns 0 (- (length absolute-ns) 1))
400                 (if (and (eql last-char #\#)
401                          (find #\/ value))
402                     (progn
403                       (when (not (eql first-char #\/))
404                         (setf separator "/"))
405                       (subseq absolute-ns 0 (- (length absolute-ns) 1)))
406                     absolute-ns))))
407        (concat prep-ns separator value)))))
408
409
410(defun absolute-uri-p (uri)
411  "Returns t if the passed uri is an absolute one. This
412   is indicated by a ':' with no leadgin '/'."
413  (when uri
414    (let ((position-of-colon
415           (position #\: uri)))
416      (declare (string uri))
417      (and position-of-colon (> position-of-colon 0)
418           (not (find #\/ (subseq uri 0 position-of-colon)))))))
419
420
421(defun escape-string (str char-to-escape)
422  "Escapes every occurrence of char-to-escape in str, if it is
423   not escaped."
424  (declare (String str char-to-escape))
425  (let ((result ""))
426    (dotimes (idx (length str))
427      (let ((current-char (subseq str idx (1+ idx)))
428            (previous-char (if (= idx 0) "" (subseq str (1- idx) idx))))
429        (cond ((and (string= current-char char-to-escape)
430                    (string/= previous-char "\\"))
431               (push-string "\\" result)
432               (push-string current-char result))
433              (t
434               (push-string current-char result)))))
435    result))
436
437
438(defun in-literal-string-p(filter-string pos)
439  "Returns t if the passed pos is within a literal string value."
440  (declare (String filter-string)
441           (Integer pos))
442  (let ((result nil))
443    (dotimes (idx (length filter-string) result)
444      (let* ((current-str (subseq filter-string idx))
445             (delimiter (get-literal-quotation current-str)))
446        (when delimiter
447          (let* ((end-pos
448                  (let ((result
449                         (find-literal-end (subseq current-str (length delimiter))
450                                    delimiter)))
451                  (when result
452                      (+ (length delimiter) result))))
453                 (quoted-str (when end-pos
454                               (subseq current-str (length delimiter) end-pos)))
455                 (start-pos idx))
456            (when quoted-str
457              (incf idx (+ (* 2 (length delimiter)) (length quoted-str)))
458              (if (and (>= pos start-pos)
459                       (< pos (+ start-pos end-pos)))
460                  (progn
461                    (setf result t)
462                    (setf idx (length filter-string)))
463                  (incf idx (+ (* 2 (length delimiter)) (length quoted-str)))))))))))
464
465
466(defun search-first-unclosed-paranthesis (str &key (ignore-literals t))
467  "Returns the idx of the first ( that is not closed, the search is
468   started from the end of the string.
469   If ignore-literals is set to t all paranthesis that are within
470   \", \"\"\", ' and ''' are ignored."
471  (declare (String str)
472           (Boolean ignore-literals))
473  (let ((open-brackets 0)
474        (result-idx nil))
475    (do ((idx (1- (length str)))) ((< idx 0))
476      (let ((current-char (subseq str idx (1+ idx))))
477        (cond ((string= current-char ")")
478               (when (or (not ignore-literals)
479                         (and ignore-literals
480                              (not (in-literal-string-p str idx))))
481                 (decf open-brackets)))
482              ((string= current-char "(")
483               (when (or (not ignore-literals)
484                         (and ignore-literals
485                              (not (in-literal-string-p str idx))))
486                 (incf open-brackets)
487                 (when (> open-brackets 0)
488                   (setf result-idx idx)
489                   (setf idx 0)))))
490        (decf idx)))
491    result-idx))
492
493
494(defun search-first-unopened-paranthesis (str &key (ignore-literals t))
495  "Returns the idx of the first paranthesis that is not opened in str.
496   If ignore-literals is set to t all mparanthesis that are within
497   \", \"\"\", ' and ''' are ignored."
498  (declare (String str)
499           (Boolean ignore-literals))
500  (let ((closed-brackets 0)
501        (result-idx nil))
502    (dotimes (idx (length str))
503      (let ((current-char (subseq str idx (1+ idx))))
504        (cond ((string= current-char "(")
505               (when (or (not ignore-literals)
506                         (and ignore-literals
507                              (not (in-literal-string-p str idx))))
508                 (decf closed-brackets)
509                 (setf result-idx nil)))
510              ((string= current-char ")")
511               (when (or (not ignore-literals)
512                         (and ignore-literals
513                              (not (in-literal-string-p str idx))))
514                 (incf closed-brackets)
515                 (when (> closed-brackets 0)
516                   (setf result-idx idx)
517                   (setf idx (length str))))))))
518    result-idx))
519
520
521(defun return-if-starts-with (str to-be-matched &key from-end ignore-case
522                              ignore-leading-whitespace)
523  "Returns the string that is contained in to-be-matched and that is the
524   start of the string str."
525  (declare (String str)
526           (List to-be-matched)
527           (Boolean from-end ignore-case ignore-leading-whitespace))
528  (let ((cleaned-str (if ignore-leading-whitespace
529                         (trim-whitespace-left str)
530                         str)))
531    (loop for try in to-be-matched
532       when (if from-end
533                (string-ends-with cleaned-str try :ignore-case ignore-case)
534                (string-starts-with cleaned-str try :ignore-case ignore-case))
535       return try)))
536
537
538(defun prefix-of-uri (uri)
539  "Returns the prefix of the passed URI until the last / or # is reached.
540   Note / or # that are placed on the last idx of the string are ignored, e.g.
541   http://eg.org/ or http://eg.org//."
542  (let* ((after-scheme (string-after uri "://"))
543         (prepared-after-scheme (when after-scheme
544                                  (cut-uri-end after-scheme))))
545    (when prepared-after-scheme
546      (let ((pos-#
547             (let ((val (search-first (list "#") prepared-after-scheme :from-end t)))
548               (when val
549                 (+ val (- (length uri) (length after-scheme))))))
550            (pos-/
551             (let ((val (search-first (list "/") prepared-after-scheme :from-end t)))
552               (when val
553                 (+ val (- (length uri) (length after-scheme)))))))
554        (cond ((and pos-# pos-/)
555               (if (> pos-# pos-/)
556                   (subseq uri 0 (1+ pos-#))
557                   (subseq uri 0 (1+ pos-/))))
558              (pos-#
559               (subseq uri 0 (1+ pos-#)))
560              (pos-/
561               (subseq uri 0 (1+ pos-/))))))))
562
563
564(defun cut-uri-end (uri)
565  "Cuts the passed uri if it ends with '/+' or '#+'."
566  (if (string-ends-with-one-of uri (list "#" "/"))
567      (cut-uri-end (subseq uri 0 (1- (length uri))))
568      uri))
569
570
571(defun get-store-spec (pathname)
572  "return the store spec for elephant and ensure that the path name is absolute"
573  (list :BDB (truename pathname)))
574
575
576(defun open-tm-store (pathname)
577  "Wraps the function elephant:open-store with the key-parameter
578   :register, so one store canbe used by several instances of
579   isidorus in parallel."
580  (if elephant:*store-controller*
581      elephant:*store-controller*
582      (elephant:open-store (get-store-spec pathname) :register t)))
583
584
585(defun close-tm-store ()
586  "Wraps the function elephant:close-store."
587  (elephant:close-store))
588
589
590(defun read-file-to-string (file-path)
591  "A helper function that reads a file and returns the content as a string."
592  (with-open-file (stream file-path)
593    (let ((file-string ""))
594      (do ((l (read-line stream) (read-line stream nil 'eof)))
595          ((eq l 'eof))
596        (base-tools:push-string (base-tools::concat l (string #\newline)) file-string))
597      (subseq file-string 0 (max 0 (1- (length file-string)))))))
Note: See TracBrowser for help on using the repository browser.