diff --git a/vcarded.egg b/vcarded.egg new file mode 100644 index 0000000..77caefd --- /dev/null +++ b/vcarded.egg @@ -0,0 +1,8 @@ +;; -*- Scheme -*- +((synopsis "Simple vCard parser.") + (author "Jaidyn Ann") + (category net) + (license "GPLv3") + (dependencies srfi-1 srfi-13 srfi-130 uri-common) + (components + (extension vcarded))) diff --git a/vcarded.scm b/vcarded.scm index f277419..6f9d062 100644 --- a/vcarded.scm +++ b/vcarded.scm @@ -13,161 +13,166 @@ ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . +(module vcarded + (read-vcard) -(import - scheme - (chicken io) - srfi-1 - srfi-13 - srfi-130 - (prefix uri-common uri:)) + (import + scheme + (chicken base) + (chicken condition) + (chicken io) + srfi-1 + srfi-13 + srfi-19 + srfi-130 + (prefix uri-common uri:)) -;; List of all properties with semicoloned-structured strings. -(define vcard-semicoloned-properties - '(ADR CLIENTPIDMAP GENDER N)) + ;; List of all properties with semicoloned-structured strings. + (define vcard-semicoloned-properties + '(ADR CLIENTPIDMAP GENDER N)) -;; List of all properties with datetime values. -(define vcard-datetime-properties - '(ANNIVERSARY BDAY REV)) + ;; List of all properties with datetime values. + (define vcard-datetime-properties + '(ANNIVERSARY BDAY REV)) -;; List of all properties with multiple comma-separated values. -(define vcard-csv-properties - '(CATEGORIES NICKNAME)) + ;; List of all properties with multiple comma-separated values. + (define vcard-csv-properties + '(CATEGORIES NICKNAME)) -;; List of all URL-type vcard properties. -(define vcard-url-properties - '(CALADRURI CALURI FBURL GEO IMPP - KEY LOGO MEMBER PHOTO RELATED - SOUND SOURCE TEL UID URL)) + ;; List of all URL-type vcard properties. + (define vcard-url-properties + '(CALADRURI CALURI FBURL GEO IMPP + KEY LOGO MEMBER PHOTO RELATED + SOUND SOURCE TEL UID URL)) -;; Should parse any truncated & reduced-accuracy ISO 8601 datetime. -;; … right now, it only parses a few possibilities. -(define (string->any-date str) - (let* [(ymd "~Y~m~d") - (hms "~H~M~S") - (ymd-hms (string-join (list ymd hms) "T"))] - (or (ignore-error (string->date str ymd-hms) #f) - (ignore-error (string->date str ymd) #f) - (ignore-error (string->date str hms) #f)))) + ;; Should parse any truncated & reduced-accuracy ISO 8601 datetime. + ;; … right now, it only parses a few possibilities. + (define (string->any-date str) + (let* [(ymd "~Y~m~d") + (hms "~H~M~S") + (ymd-hms (string-join (list ymd hms) "T"))] + (or (ignore-error (string->date str ymd-hms) #f) + (ignore-error (string->date str ymd) #f) + (ignore-error (string->date str hms) #f)))) -;; A list of the parser/serializer functions for each vcard property. -;; ((TEL # #) -;; (ADR # #) -;; …) -;; TODO: Add a parser for the TZ [timezone] property. -(define vcard-value-parsers - (append - (map (lambda (uri-prop) - (list uri-prop - (lambda strs (or (uri:uri-reference (string-join strs ";")) - (string-join sts ";"))) - (lambda (url) (uri:uri->string url)))) - vcard-url-properties) - (map (lambda (date-prop) - (list date-prop - string->any-date - (lambda (datetime) - (date->string datetime "~Y~m~dT~H~M~S~z")))) - vcard-datetime-properties) - (map (lambda (csv-prop) - (list csv-prop - (lambda (str) (string-split-unescaped str ",")) - (lambda (csv-list) (string-join csv-list ",")))) - vcard-csv-properties) - (map (lambda (semicolon-prop) - (list semicolon-prop - (lambda (str) (string-split-unescaped str ";")) - (lambda (sc-list) (string-join sc-list ";")))) - vcard-semicoloned-properties))) + ;; A list of the parser/serializer functions for each vcard property. + ;; ((TEL # #) + ;; (ADR # #) + ;; …) + ;; TODO: Add a parser for the TZ [timezone] property. + (define vcard-value-parsers + (append + (map (lambda (uri-prop) + (list uri-prop + (lambda strs (or (uri:uri-reference (string-join strs ";")) + (string-join strs ";"))) + (lambda (url) (uri:uri->string url)))) + vcard-url-properties) + (map (lambda (date-prop) + (list date-prop + string->any-date + (lambda (datetime) + (date->string datetime "~Y~m~dT~H~M~S~z")))) + vcard-datetime-properties) + (map (lambda (csv-prop) + (list csv-prop + (lambda (str) (string-split-unescaped str ",")) + (lambda (csv-list) (string-join csv-list ",")))) + vcard-csv-properties) + (map (lambda (semicolon-prop) + (list semicolon-prop + (lambda (str) (string-split-unescaped str ";")) + (lambda (sc-list) (string-join sc-list ";")))) + vcard-semicoloned-properties))) -;; Splits a line into a cons of the property-string and value-string. -;; … basically splits the string along the first unescaped colon (:). -;; "apple\:berry:mom:dad" → ("apple\:berry" . "mom:dad") -(define (split-vcard-line line) - (let [(split (string-split-unescaped line ":"))] - (cons (car split) - (string-join (cdr split) ":")))) + ;; Splits a line into a cons of the property-string and value-string. + ;; … basically splits the string along the first unescaped colon (:). + ;; "apple\:berry:mom:dad" → ("apple\:berry" . "mom:dad") + (define (split-vcard-line line) + (let [(split (string-split-unescaped line ":"))] + (cons (car split) + (string-join (cdr split) ":")))) -;; Ignore whatever conditions the expression might return; just return the -;; default value in that case. -;; (ignore-error (+ 1 "seven") 3) → 3 -(define-syntax ignore-error - (syntax-rules () - ((ignore-error expr default) - (condition-case expr (var () default))))) + ;; Ignore whatever conditions the expression might return; just return the + ;; default value in that case. + ;; (ignore-error (+ 1 "seven") 3) → 3 + (define-syntax ignore-error + (syntax-rules () + ((ignore-error expr default) + (condition-case expr (var () default))))) -;; Splits a string along a delimiter; while not splitting along -;; backslash-escaped delimiters. With “;” as the delimiter: -;; "Bird;dad;apple\;mom" → ("Bird" "dad" "apple\;mom") -(define (string-split-unescaped string delimiter) - (let [(appendee "")] - (remove - not - (map (lambda (substr) - (let [(substr (string-concatenate (list appendee substr)))] - (if (and (not (string-null? substr)) - (eq? (last (string->list substr)) - #\\)) - (and (set! appendee - (string-concatenate (list substr delimiter))) - #f) - (and (set! appendee "") substr)))) - (string-split string delimiter))))) + ;; Splits a string along a delimiter; while not splitting along + ;; backslash-escaped delimiters. With “;” as the delimiter: + ;; "Bird;dad;apple\;mom" → ("Bird" "dad" "apple\;mom") + (define (string-split-unescaped string delimiter) + (let [(appendee "")] + (remove + not + (map (lambda (substr) + (let [(substr (string-concatenate (list appendee substr)))] + (if (and (not (string-null? substr)) + (eq? (last (string->list substr)) + #\\)) + (and (set! appendee + (string-concatenate (list substr delimiter))) + #f) + (and (set! appendee "") substr)))) + (string-split string delimiter))))) -(define (parse-vcard-prop property elements) - (append (list property) - (list elements))) + (define (parse-vcard-prop property elements) + (append (list property) + (list elements))) -(define (parse-vcard-value prop elements) - (let [(parser-and-unparser-funcs (alist-ref prop vcard-value-parsers))] - (if parser-and-unparser-funcs - (list (ignore-error (apply (car parser-and-unparser-funcs) - (list elements)) - elements)) - (list elements)))) + (define (parse-vcard-value prop elements) + (let [(parser-and-unparser-funcs (alist-ref prop vcard-value-parsers))] + (if parser-and-unparser-funcs + (list (ignore-error (apply (car parser-and-unparser-funcs) + (list elements)) + elements)) + (list elements)))) -;; Parse a line of a vcard file into an alist-friendly format: -;; (KEY ("keyprop1=d" "keyprop2=b") "VALUE" ("valprop1=a" "valprop2=b")) -(define (parse-vcard-line line) - (let* [(prop-value-strings (split-vcard-line line)) - (prop-elements (string-split-unescaped (car prop-value-strings) ";")) - (value-elements (cdr prop-value-strings)) - (property (string->symbol (string-upcase (car prop-elements))))] - (append (parse-vcard-prop property (cdr prop-elements)) - (parse-vcard-value property value-elements)))) + ;; Parse a line of a vcard file into an alist-friendly format: + ;; (KEY ("keyprop1=d" "keyprop2=b") "VALUE" ("valprop1=a" "valprop2=b")) + (define (parse-vcard-line line) + (let* [(prop-value-strings (split-vcard-line line)) + (prop-elements (string-split-unescaped (car prop-value-strings) ";")) + (value-elements (cdr prop-value-strings)) + (property (string->symbol (string-upcase (car prop-elements))))] + (append (parse-vcard-prop property (cdr prop-elements)) + (parse-vcard-value property value-elements)))) -;; Reader thunk. Read/parse an entire vcard into a “vcard alist.” -(define (read-vcard) - (let [(element (read-vcard-element))] - (if (not (eof-object? (peek-char))) - (append (list element) (read-vcard)) - (list element)))) + ;; Reader thunk. Read/parse an entire vcard into a “vcard alist.” + (define (read-vcard) + (let [(element (read-vcard-element))] + (if (not (eof-object? (peek-char))) + (append (list element) (read-vcard)) + (list element)))) -;; Read a single unfolded line into a vcard “element” list. -(define (read-vcard-element) - (parse-vcard-line (read-folded-line))) + ;; Read a single unfolded line into a vcard “element” list. + (define (read-vcard-element) + (parse-vcard-line (read-folded-line))) -;; Reader-thunk. Read a “logical” folded-line, where a line beginning with a -;; space is a continuation of the previous line — like with vcards. -(define (read-folded-line) - (let [(line (read-line))] - (if (eq? (peek-char) #\space) - (string-concatenate - (list line - (string-drop (read-folded-line) 1))) - line))) + ;; Reader-thunk. Read a “logical” folded-line, where a line beginning with a + ;; space is a continuation of the previous line — like with vcards. + (define (read-folded-line) + (let [(line (read-line))] + (if (eq? (peek-char) #\space) + (string-concatenate + (list line + (string-drop (read-folded-line) 1))) + line))))