97 |
|
|
98 |
;;; Code: |
;;; Code: |
99 |
|
|
100 |
(defconst icalendar-version 0.12 |
(defconst icalendar-version "0.13" |
101 |
"Version number of icalendar.el.") |
"Version number of icalendar.el.") |
102 |
|
|
103 |
;; ====================================================================== |
;; ====================================================================== |
113 |
"Format string for importing events from iCalendar into Emacs diary. |
"Format string for importing events from iCalendar into Emacs diary. |
114 |
This string defines how iCalendar events are inserted into diary |
This string defines how iCalendar events are inserted into diary |
115 |
file. Meaning of the specifiers: |
file. Meaning of the specifiers: |
116 |
|
%c Class, see `icalendar-import-format-class' |
117 |
%d Description, see `icalendar-import-format-description' |
%d Description, see `icalendar-import-format-description' |
118 |
%l Location, see `icalendar-import-format-location' |
%l Location, see `icalendar-import-format-location' |
119 |
%o Organizer, see `icalendar-import-format-organizer' |
%o Organizer, see `icalendar-import-format-organizer' |
120 |
%s Subject, see `icalendar-import-format-subject'" |
%s Summary, see `icalendar-import-format-summary' |
121 |
|
%t Status, see `icalendar-import-format-status' |
122 |
|
%u URL, see `icalendar-import-format-url'" |
123 |
:type 'string |
:type 'string |
124 |
:group 'icalendar) |
:group 'icalendar) |
125 |
|
|
126 |
(defcustom icalendar-import-format-subject |
(defcustom icalendar-import-format-summary |
127 |
"%s" |
"%s" |
128 |
"Format string defining how the subject element is formatted. |
"Format string defining how the summary element is formatted. |
129 |
This applies only if the subject is not empty! `%s' is replaced |
This applies only if the summary is not empty! `%s' is replaced |
130 |
by the subject." |
by the summary." |
131 |
:type 'string |
:type 'string |
132 |
:group 'icalendar) |
:group 'icalendar) |
133 |
|
|
155 |
:type 'string |
:type 'string |
156 |
:group 'icalendar) |
:group 'icalendar) |
157 |
|
|
158 |
|
(defcustom icalendar-import-format-url |
159 |
|
"\n URL: %s" |
160 |
|
"Format string defining how the URL element is formatted. |
161 |
|
This applies only if the URL is not empty! `%s' is replaced by |
162 |
|
the URL." |
163 |
|
:type 'string |
164 |
|
:group 'icalendar) |
165 |
|
|
166 |
|
(defcustom icalendar-import-format-status |
167 |
|
"\n Status: %s" |
168 |
|
"Format string defining how the status element is formatted. |
169 |
|
This applies only if the status is not empty! `%s' is replaced by |
170 |
|
the status." |
171 |
|
:type 'string |
172 |
|
:group 'icalendar) |
173 |
|
|
174 |
|
(defcustom icalendar-import-format-class |
175 |
|
"\n Class: %s" |
176 |
|
"Format string defining how the class element is formatted. |
177 |
|
This applies only if the class is not empty! `%s' is replaced by |
178 |
|
the class." |
179 |
|
:type 'string |
180 |
|
:group 'icalendar) |
181 |
|
|
182 |
(defvar icalendar-debug nil |
(defvar icalendar-debug nil |
183 |
"Enable icalendar debug messages.") |
"Enable icalendar debug messages.") |
184 |
|
|
222 |
(replace-match "" nil nil))) |
(replace-match "" nil nil))) |
223 |
unfolded-buffer)) |
unfolded-buffer)) |
224 |
|
|
225 |
(defsubst icalendar--rris (re rp st) |
(defsubst icalendar--rris (&rest args) |
226 |
"Replace regexp RE with RP in string ST and return the new string. |
"Replace regular expression in string. |
227 |
This is here for compatibility with XEmacs." |
Pass ARGS to `replace-regexp-in-string' (Emacs) or to |
228 |
|
`replace-in-string' (XEmacs)." |
229 |
;; XEmacs: |
;; XEmacs: |
230 |
(if (fboundp 'replace-in-string) |
(if (fboundp 'replace-in-string) |
231 |
(save-match-data ;; apparently XEmacs needs save-match-data |
(save-match-data ;; apparently XEmacs needs save-match-data |
232 |
(replace-in-string st re rp)) |
(apply 'replace-in-string args)) |
233 |
;; Emacs: |
;; Emacs: |
234 |
(replace-regexp-in-string re rp st))) |
(apply 'replace-regexp-in-string args))) |
235 |
|
|
236 |
(defun icalendar--read-element (invalue inparams) |
(defun icalendar--read-element (invalue inparams) |
237 |
"Recursively read the next iCalendar element in the current buffer. |
"Recursively read the next iCalendar element in the current buffer. |
637 |
(setq month day) |
(setq month day) |
638 |
(setq day x)))) |
(setq day x)))) |
639 |
( ;; date contains month names -- european-style |
( ;; date contains month names -- european-style |
640 |
(and european-calendar-style |
(string-match (concat "\\s-*" |
641 |
(string-match (concat "\\s-*" |
"0?\\([123]?[0-9]\\)[ \t/]\\s-*" |
642 |
"0?\\([123]?[0-9]\\)[ \t/]\\s-*" |
"\\([A-Za-z][^ ]+\\)[ \t/]\\s-*" |
643 |
"\\([A-Za-z][^ ]+\\)[ \t/]\\s-*" |
"\\([0-9]\\{4\\}\\)") |
644 |
"\\([0-9]\\{4\\}\\)") |
datestring) |
|
datestring)) |
|
645 |
(setq day (read (substring datestring (match-beginning 1) |
(setq day (read (substring datestring (match-beginning 1) |
646 |
(match-end 1)))) |
(match-end 1)))) |
647 |
(setq month (icalendar--get-month-number |
(setq month (icalendar--get-month-number |
650 |
(setq year (read (substring datestring (match-beginning 3) |
(setq year (read (substring datestring (match-beginning 3) |
651 |
(match-end 3))))) |
(match-end 3))))) |
652 |
( ;; date contains month names -- non-european-style |
( ;; date contains month names -- non-european-style |
653 |
(and (not european-calendar-style) |
(string-match (concat "\\s-*" |
654 |
(string-match (concat "\\s-*" |
"\\([A-Za-z][^ ]+\\)[ \t/]\\s-*" |
655 |
"\\([A-Za-z][^ ]+\\)[ \t/]\\s-*" |
"0?\\([123]?[0-9]\\),?[ \t/]\\s-*" |
656 |
"0?\\([123]?[0-9]\\),?[ \t/]\\s-*" |
"\\([0-9]\\{4\\}\\)") |
657 |
"\\([0-9]\\{4\\}\\)") |
datestring) |
|
datestring)) |
|
658 |
(setq day (read (substring datestring (match-beginning 2) |
(setq day (read (substring datestring (match-beginning 2) |
659 |
(match-end 2)))) |
(match-end 2)))) |
660 |
(setq month (icalendar--get-month-number |
(setq month (icalendar--get-month-number |
730 |
(entry-main "") |
(entry-main "") |
731 |
(entry-rest "") |
(entry-rest "") |
732 |
(header "") |
(header "") |
733 |
|
(contents-n-summary) |
734 |
(contents) |
(contents) |
735 |
(found-error nil) |
(found-error nil) |
736 |
(nonmarker (concat "^" (regexp-quote diary-nonmarking-symbol) |
(nonmarker (concat "^" (regexp-quote diary-nonmarking-symbol) |
737 |
"?"))) |
"?")) |
738 |
|
(other-elements nil)) |
739 |
;; prepare buffer with error messages |
;; prepare buffer with error messages |
740 |
(save-current-buffer |
(save-current-buffer |
741 |
(set-buffer (get-buffer-create "*icalendar-errors*")) |
(set-buffer (get-buffer-create "*icalendar-errors*")) |
756 |
(car (cddr (current-time))))) |
(car (cddr (current-time))))) |
757 |
(condition-case error-val |
(condition-case error-val |
758 |
(progn |
(progn |
759 |
(setq contents |
(setq contents-n-summary |
760 |
(or |
(icalendar--convert-to-ical nonmarker entry-main)) |
761 |
;; anniversaries -- %%(diary-anniversary ...) |
(setq other-elements (icalendar--parse-summary-and-rest |
762 |
(icalendar--convert-anniversary-to-ical nonmarker |
(concat entry-main entry-rest))) |
763 |
entry-main) |
(setq contents (concat (car contents-n-summary) |
764 |
;; cyclic events -- %%(diary-cyclic ...) |
"\nSUMMARY:" (cadr contents-n-summary))) |
765 |
(icalendar--convert-cyclic-to-ical nonmarker entry-main) |
(let ((cla (cdr (assoc 'cla other-elements))) |
766 |
;; diary-date -- %%(diary-date ...) |
(des (cdr (assoc 'des other-elements))) |
767 |
(icalendar--convert-date-to-ical nonmarker entry-main) |
(loc (cdr (assoc 'loc other-elements))) |
768 |
;; float events -- %%(diary-float ...) |
(org (cdr (assoc 'org other-elements))) |
769 |
(icalendar--convert-float-to-ical nonmarker entry-main) |
(sta (cdr (assoc 'sta other-elements))) |
770 |
;; block events -- %%(diary-block ...) |
(sum (cdr (assoc 'sum other-elements))) |
771 |
(icalendar--convert-block-to-ical nonmarker entry-main) |
(url (cdr (assoc 'url other-elements)))) |
772 |
;; other sexp diary entries |
(if cla |
773 |
(icalendar--convert-sexp-to-ical nonmarker entry-main) |
(setq contents (concat contents "\nCLASS:" cla))) |
774 |
;; weekly by day -- Monday 8:30 Team meeting |
(if des |
775 |
(icalendar--convert-weekly-to-ical nonmarker entry-main) |
(setq contents (concat contents "\nDESCRIPTION:" des))) |
776 |
;; yearly by day -- 1 May Tag der Arbeit |
(if loc |
777 |
(icalendar--convert-yearly-to-ical nonmarker entry-main) |
(setq contents (concat contents "\nLOCATION:" loc))) |
778 |
;; "ordinary" events, start and end time given |
(if org |
779 |
;; 1 Feb 2003 blah |
(setq contents (concat contents "\nORGANIZER:" org))) |
780 |
(icalendar--convert-ordinary-to-ical nonmarker entry-main) |
(if sta |
781 |
;; everything else |
(setq contents (concat contents "\nSTATUS:" sta))) |
782 |
;; Oops! what's that? |
;;(if sum |
783 |
(error "Could not parse entry"))) |
;; (setq contents (concat contents "\nSUMMARY:" sum))) |
784 |
(unless (string= entry-rest "") |
(if url |
785 |
(setq contents |
(setq contents (concat contents "\nURL:" url)))) |
|
(concat contents "\nDESCRIPTION:" |
|
|
(icalendar--convert-string-for-export |
|
|
entry-rest)))) |
|
786 |
(setq result (concat result header contents "\nEND:VEVENT"))) |
(setq result (concat result header contents "\nEND:VEVENT"))) |
787 |
;; handle errors |
;; handle errors |
788 |
(error |
(error |
805 |
(insert result) |
(insert result) |
806 |
(insert "\nEND:VCALENDAR\n") |
(insert "\nEND:VCALENDAR\n") |
807 |
;; save the diary file |
;; save the diary file |
808 |
(save-buffer)))) |
(save-buffer) |
809 |
|
(unless found-error |
810 |
|
(bury-buffer))))) |
811 |
found-error)) |
found-error)) |
812 |
|
|
813 |
;; subroutines |
(defun icalendar--convert-to-ical (nonmarker entry-main) |
814 |
|
"Convert a diary entry to icalendar format. |
815 |
|
NONMARKER is a regular expression matching the start of non-marking |
816 |
|
entries. ENTRY-MAIN is the first line of the diary entry." |
817 |
|
(or |
818 |
|
;; anniversaries -- %%(diary-anniversary ...) |
819 |
|
(icalendar--convert-anniversary-to-ical nonmarker entry-main) |
820 |
|
;; cyclic events -- %%(diary-cyclic ...) |
821 |
|
(icalendar--convert-cyclic-to-ical nonmarker entry-main) |
822 |
|
;; diary-date -- %%(diary-date ...) |
823 |
|
(icalendar--convert-date-to-ical nonmarker entry-main) |
824 |
|
;; float events -- %%(diary-float ...) |
825 |
|
(icalendar--convert-float-to-ical nonmarker entry-main) |
826 |
|
;; block events -- %%(diary-block ...) |
827 |
|
(icalendar--convert-block-to-ical nonmarker entry-main) |
828 |
|
;; other sexp diary entries |
829 |
|
(icalendar--convert-sexp-to-ical nonmarker entry-main) |
830 |
|
;; weekly by day -- Monday 8:30 Team meeting |
831 |
|
(icalendar--convert-weekly-to-ical nonmarker entry-main) |
832 |
|
;; yearly by day -- 1 May Tag der Arbeit |
833 |
|
(icalendar--convert-yearly-to-ical nonmarker entry-main) |
834 |
|
;; "ordinary" events, start and end time given |
835 |
|
;; 1 Feb 2003 blah |
836 |
|
(icalendar--convert-ordinary-to-ical nonmarker entry-main) |
837 |
|
;; everything else |
838 |
|
;; Oops! what's that? |
839 |
|
(error "Could not parse entry"))) |
840 |
|
|
841 |
|
(defun icalendar--parse-summary-and-rest (summary-and-rest) |
842 |
|
"Parse SUMMARY-AND-REST from a diary to fill iCalendar properties." |
843 |
|
(save-match-data |
844 |
|
(let* ((s icalendar-import-format) |
845 |
|
(p-cla (or (string-match "%c" icalendar-import-format) -1)) |
846 |
|
(p-des (or (string-match "%d" icalendar-import-format) -1)) |
847 |
|
(p-loc (or (string-match "%l" icalendar-import-format) -1)) |
848 |
|
(p-org (or (string-match "%o" icalendar-import-format) -1)) |
849 |
|
(p-sum (or (string-match "%s" icalendar-import-format) -1)) |
850 |
|
(p-sta (or (string-match "%t" icalendar-import-format) -1)) |
851 |
|
(p-url (or (string-match "%u" icalendar-import-format) -1)) |
852 |
|
(p-list (sort (list p-cla p-des p-loc p-org p-sta p-sum p-url) '<)) |
853 |
|
pos-cla pos-des pos-loc pos-org pos-sta pos-sum pos-url) |
854 |
|
(dotimes (i (length p-list)) |
855 |
|
(cond ((and (>= p-cla 0) (= (nth i p-list) p-cla)) |
856 |
|
(setq pos-cla (+ 2 (* 2 i)))) |
857 |
|
((and (>= p-des 0) (= (nth i p-list) p-des)) |
858 |
|
(setq pos-des (+ 2 (* 2 i)))) |
859 |
|
((and (>= p-loc 0) (= (nth i p-list) p-loc)) |
860 |
|
(setq pos-loc (+ 2 (* 2 i)))) |
861 |
|
((and (>= p-org 0) (= (nth i p-list) p-org)) |
862 |
|
(setq pos-org (+ 2 (* 2 i)))) |
863 |
|
((and (>= p-sta 0) (= (nth i p-list) p-sta)) |
864 |
|
(setq pos-sta (+ 2 (* 2 i)))) |
865 |
|
((and (>= p-sum 0) (= (nth i p-list) p-sum)) |
866 |
|
(setq pos-sum (+ 2 (* 2 i)))) |
867 |
|
((and (>= p-url 0) (= (nth i p-list) p-url)) |
868 |
|
(setq pos-url (+ 2 (* 2 i)))))) |
869 |
|
(mapc (lambda (ij) |
870 |
|
(setq s (icalendar--rris (car ij) (cadr ij) s t t))) |
871 |
|
(list |
872 |
|
;; summary must be first! because of %s |
873 |
|
(list "%s" |
874 |
|
(concat "\\(" icalendar-import-format-summary "\\)?")) |
875 |
|
(list "%c" |
876 |
|
(concat "\\(" icalendar-import-format-class "\\)?")) |
877 |
|
(list "%d" |
878 |
|
(concat "\\(" icalendar-import-format-description "\\)?")) |
879 |
|
(list "%l" |
880 |
|
(concat "\\(" icalendar-import-format-location "\\)?")) |
881 |
|
(list "%o" |
882 |
|
(concat "\\(" icalendar-import-format-organizer "\\)?")) |
883 |
|
(list "%t" |
884 |
|
(concat "\\(" icalendar-import-format-status "\\)?")) |
885 |
|
(list "%u" |
886 |
|
(concat "\\(" icalendar-import-format-url "\\)?")))) |
887 |
|
(setq s (concat (icalendar--rris "%s" "\\(.*\\)" s nil t) " ")) |
888 |
|
(if (string-match s summary-and-rest) |
889 |
|
(let (cla des loc org sta sum url) |
890 |
|
(if (and pos-sum (match-beginning pos-sum)) |
891 |
|
(setq sum (substring summary-and-rest |
892 |
|
(match-beginning pos-sum) |
893 |
|
(match-end pos-sum)))) |
894 |
|
(if (and pos-cla (match-beginning pos-cla)) |
895 |
|
(setq cla (substring summary-and-rest |
896 |
|
(match-beginning pos-cla) |
897 |
|
(match-end pos-cla)))) |
898 |
|
(if (and pos-des (match-beginning pos-des)) |
899 |
|
(setq des (substring summary-and-rest |
900 |
|
(match-beginning pos-des) |
901 |
|
(match-end pos-des)))) |
902 |
|
(if (and pos-loc (match-beginning pos-loc)) |
903 |
|
(setq loc (substring summary-and-rest |
904 |
|
(match-beginning pos-loc) |
905 |
|
(match-end pos-loc)))) |
906 |
|
(if (and pos-org (match-beginning pos-org)) |
907 |
|
(setq org (substring summary-and-rest |
908 |
|
(match-beginning pos-org) |
909 |
|
(match-end pos-org)))) |
910 |
|
(if (and pos-sta (match-beginning pos-sta)) |
911 |
|
(setq sta (substring summary-and-rest |
912 |
|
(match-beginning pos-sta) |
913 |
|
(match-end pos-sta)))) |
914 |
|
(if (and pos-url (match-beginning pos-url)) |
915 |
|
(setq url (substring summary-and-rest |
916 |
|
(match-beginning pos-url) |
917 |
|
(match-end pos-url)))) |
918 |
|
(list (if cla (cons 'cla cla) nil) |
919 |
|
(if des (cons 'des des) nil) |
920 |
|
(if loc (cons 'loc loc) nil) |
921 |
|
(if org (cons 'org org) nil) |
922 |
|
(if sta (cons 'sta sta) nil) |
923 |
|
;;(if sum (cons 'sum sum) nil) |
924 |
|
(if url (cons 'url url) nil))))))) |
925 |
|
|
926 |
|
;; subroutines for icalendar-export-region |
927 |
(defun icalendar--convert-ordinary-to-ical (nonmarker entry-main) |
(defun icalendar--convert-ordinary-to-ical (nonmarker entry-main) |
928 |
"Convert \"ordinary\" diary entry to icalendar format. |
"Convert \"ordinary\" diary entry to icalendar format. |
|
|
|
929 |
NONMARKER is a regular expression matching the start of non-marking |
NONMARKER is a regular expression matching the start of non-marking |
930 |
entries. ENTRY-MAIN is the first line of the diary entry." |
entries. ENTRY-MAIN is the first line of the diary entry." |
931 |
(if (string-match (concat nonmarker |
(if (string-match (concat nonmarker |
934 |
"\\(" |
"\\(" |
935 |
"-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?" |
"-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?" |
936 |
"\\)?" |
"\\)?" |
937 |
"\\s-*\\(.*\\)") |
"\\s-*\\(.*?\\) ?$") |
938 |
entry-main) |
entry-main) |
939 |
(let* ((datetime (substring entry-main (match-beginning 1) |
(let* ((datetime (substring entry-main (match-beginning 1) |
940 |
(match-end 1))) |
(match-end 1))) |
978 |
starttimestring)))) |
starttimestring)))) |
979 |
(setq endtimestring (format "T%06d" |
(setq endtimestring (format "T%06d" |
980 |
(+ 10000 time)))))) |
(+ 10000 time)))))) |
981 |
(concat "\nDTSTART;" |
(list (concat "\nDTSTART;" |
982 |
(if starttimestring "VALUE=DATE-TIME:" |
(if starttimestring "VALUE=DATE-TIME:" |
983 |
"VALUE=DATE:") |
"VALUE=DATE:") |
984 |
startisostring |
startisostring |
985 |
(or starttimestring "") |
(or starttimestring "") |
986 |
"\nDTEND;" |
"\nDTEND;" |
987 |
(if endtimestring "VALUE=DATE-TIME:" |
(if endtimestring "VALUE=DATE-TIME:" |
988 |
"VALUE=DATE:") |
"VALUE=DATE:") |
989 |
(if starttimestring |
(if starttimestring |
990 |
startisostring |
startisostring |
991 |
endisostring) |
endisostring) |
992 |
(or endtimestring "") |
(or endtimestring "")) |
993 |
"\nSUMMARY:" |
summary)) |
|
summary)) |
|
994 |
;; no match |
;; no match |
995 |
nil)) |
nil)) |
996 |
|
|
997 |
(defun icalendar--convert-weekly-to-ical (nonmarker entry-main) |
(defun icalendar--convert-weekly-to-ical (nonmarker entry-main) |
998 |
"Convert weekly diary entry to icalendar format. |
"Convert weekly diary entry to icalendar format. |
|
|
|
999 |
NONMARKER is a regular expression matching the start of non-marking |
NONMARKER is a regular expression matching the start of non-marking |
1000 |
entries. ENTRY-MAIN is the first line of the diary entry." |
entries. ENTRY-MAIN is the first line of the diary entry." |
1001 |
(if (and (string-match (concat nonmarker |
(if (and (string-match (concat nonmarker |
1006 |
"\\([1-9][0-9]?:[0-9][0-9]\\)" |
"\\([1-9][0-9]?:[0-9][0-9]\\)" |
1007 |
"\\([ap]m\\)?\\)?" |
"\\([ap]m\\)?\\)?" |
1008 |
"\\)?" |
"\\)?" |
1009 |
"\\s-*\\(.*\\)$") |
"\\s-*\\(.*?\\) ?$") |
1010 |
entry-main) |
entry-main) |
1011 |
(icalendar--get-weekday-abbrev |
(icalendar--get-weekday-abbrev |
1012 |
(substring entry-main (match-beginning 1) |
(substring entry-main (match-beginning 1) |
1048 |
starttimestring)))) |
starttimestring)))) |
1049 |
(setq endtimestring (format "T%06d" |
(setq endtimestring (format "T%06d" |
1050 |
(+ 10000 time)))))) |
(+ 10000 time)))))) |
1051 |
(concat "\nDTSTART;" |
(list (concat "\nDTSTART;" |
1052 |
(if starttimestring |
(if starttimestring |
1053 |
"VALUE=DATE-TIME:" |
"VALUE=DATE-TIME:" |
1054 |
"VALUE=DATE:") |
"VALUE=DATE:") |
1055 |
;; find the correct week day, |
;; find the correct week day, |
1056 |
;; 1st january 2000 was a saturday |
;; 1st january 2000 was a saturday |
1057 |
(format |
(format |
1058 |
"200001%02d" |
"200001%02d" |
1059 |
(+ (icalendar--get-weekday-number day) 2)) |
(+ (icalendar--get-weekday-number day) 2)) |
1060 |
(or starttimestring "") |
(or starttimestring "") |
1061 |
"\nDTEND;" |
"\nDTEND;" |
1062 |
(if endtimestring |
(if endtimestring |
1063 |
"VALUE=DATE-TIME:" |
"VALUE=DATE-TIME:" |
1064 |
"VALUE=DATE:") |
"VALUE=DATE:") |
1065 |
(format |
(format |
1066 |
"200001%02d" |
"200001%02d" |
1067 |
;; end is non-inclusive! |
;; end is non-inclusive! |
1068 |
(+ (icalendar--get-weekday-number day) |
(+ (icalendar--get-weekday-number day) |
1069 |
(if endtimestring 2 3))) |
(if endtimestring 2 3))) |
1070 |
(or endtimestring "") |
(or endtimestring "") |
1071 |
"\nSUMMARY:" summary |
"\nRRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=" |
1072 |
"\nRRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=" |
day) |
1073 |
day)) |
summary)) |
1074 |
;; no match |
;; no match |
1075 |
nil)) |
nil)) |
1076 |
|
|
1077 |
(defun icalendar--convert-yearly-to-ical (nonmarker entry-main) |
(defun icalendar--convert-yearly-to-ical (nonmarker entry-main) |
1078 |
"Convert yearly diary entry to icalendar format. |
"Convert yearly diary entry to icalendar format. |
|
|
|
1079 |
NONMARKER is a regular expression matching the start of non-marking |
NONMARKER is a regular expression matching the start of non-marking |
1080 |
entries. ENTRY-MAIN is the first line of the diary entry." |
entries. ENTRY-MAIN is the first line of the diary entry." |
1081 |
(if (string-match (concat nonmarker |
(if (string-match (concat nonmarker |
1087 |
"\\(" |
"\\(" |
1088 |
"-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?" |
"-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?" |
1089 |
"\\)?" |
"\\)?" |
1090 |
"\\s-*\\([^0-9]+.*\\)$" ; must not match years |
"\\s-*\\([^0-9]+.*?\\) ?$" ; must not match years |
1091 |
) |
) |
1092 |
entry-main) |
entry-main) |
1093 |
(let* ((daypos (if european-calendar-style 1 2)) |
(let* ((daypos (if european-calendar-style 1 2)) |
1133 |
starttimestring)))) |
starttimestring)))) |
1134 |
(setq endtimestring (format "T%06d" |
(setq endtimestring (format "T%06d" |
1135 |
(+ 10000 time)))))) |
(+ 10000 time)))))) |
1136 |
(concat "\nDTSTART;" |
(list (concat "\nDTSTART;" |
1137 |
(if starttimestring "VALUE=DATE-TIME:" |
(if starttimestring "VALUE=DATE-TIME:" |
1138 |
"VALUE=DATE:") |
"VALUE=DATE:") |
1139 |
(format "1900%02d%02d" month day) |
(format "1900%02d%02d" month day) |
1140 |
(or starttimestring "") |
(or starttimestring "") |
1141 |
"\nDTEND;" |
"\nDTEND;" |
1142 |
(if endtimestring "VALUE=DATE-TIME:" |
(if endtimestring "VALUE=DATE-TIME:" |
1143 |
"VALUE=DATE:") |
"VALUE=DATE:") |
1144 |
;; end is not included! shift by one day |
;; end is not included! shift by one day |
1145 |
(icalendar--date-to-isodate |
(icalendar--date-to-isodate |
1146 |
(list month day 1900) |
(list month day 1900) |
1147 |
(if endtimestring 0 1)) |
(if endtimestring 0 1)) |
1148 |
(or endtimestring "") |
(or endtimestring "") |
1149 |
"\nSUMMARY:" |
"\nRRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=" |
1150 |
summary |
(format "%2d" month) |
1151 |
"\nRRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=" |
";BYMONTHDAY=" |
1152 |
(format "%2d" month) |
(format "%2d" day)) |
1153 |
";BYMONTHDAY=" |
summary)) |
|
(format "%2d" day))) |
|
1154 |
;; no match |
;; no match |
1155 |
nil)) |
nil)) |
1156 |
|
|
1161 |
|
|
1162 |
NONMARKER is a regular expression matching the start of non-marking |
NONMARKER is a regular expression matching the start of non-marking |
1163 |
entries. ENTRY-MAIN is the first line of the diary entry." |
entries. ENTRY-MAIN is the first line of the diary entry." |
1164 |
(if (string-match (concat nonmarker |
(cond ((string-match (concat nonmarker |
1165 |
"%%(\\([^)]+\\))\\s-*\\(.*\\)") |
"%%(and \\(([^)]+)\\))\\(\\s-*.*?\\) ?$") |
1166 |
entry-main) |
entry-main) |
1167 |
(progn |
;; simple sexp entry as generated by icalendar.el: strip off the |
1168 |
(icalendar--dmsg "diary-sexp %s" entry-main) |
;; unnecessary (and) |
1169 |
(error "Sexp-entries are not supported yet")) |
(icalendar--dmsg "diary-sexp from icalendar.el %s" entry-main) |
1170 |
;; no match |
(icalendar--convert-to-ical |
1171 |
nil)) |
nonmarker |
1172 |
|
(concat "%%" |
1173 |
|
(substring entry-main (match-beginning 1) (match-end 1)) |
1174 |
|
(substring entry-main (match-beginning 2) (match-end 2))))) |
1175 |
|
((string-match (concat nonmarker |
1176 |
|
"%%([^)]+)\\s-*.*") |
1177 |
|
entry-main) |
1178 |
|
(icalendar--dmsg "diary-sexp %s" entry-main) |
1179 |
|
(error "Sexp-entries are not supported yet")) |
1180 |
|
(t |
1181 |
|
;; no match |
1182 |
|
nil))) |
1183 |
|
|
1184 |
(defun icalendar--convert-block-to-ical (nonmarker entry-main) |
(defun icalendar--convert-block-to-ical (nonmarker entry-main) |
1185 |
"Convert block diary entry to icalendar format. |
"Convert block diary entry to icalendar format. |
|
|
|
1186 |
NONMARKER is a regular expression matching the start of non-marking |
NONMARKER is a regular expression matching the start of non-marking |
1187 |
entries. ENTRY-MAIN is the first line of the diary entry." |
entries. ENTRY-MAIN is the first line of the diary entry." |
1188 |
(if (string-match (concat nonmarker |
(if (string-match (concat nonmarker |
1192 |
"\\(" |
"\\(" |
1193 |
"-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?" |
"-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?" |
1194 |
"\\)?" |
"\\)?" |
1195 |
"\\s-*\\(.*\\)") |
"\\s-*\\(.*?\\) ?$") |
1196 |
entry-main) |
entry-main) |
1197 |
(let* ((startstring (substring entry-main |
(let* ((startstring (substring entry-main |
1198 |
(match-beginning 1) |
(match-beginning 1) |
1241 |
(+ 10000 time)))))) |
(+ 10000 time)))))) |
1242 |
(if starttimestring |
(if starttimestring |
1243 |
;; with time -> write rrule |
;; with time -> write rrule |
1244 |
(concat "\nDTSTART;VALUE=DATE-TIME:" |
(list (concat "\nDTSTART;VALUE=DATE-TIME:" |
1245 |
startisostring |
startisostring |
1246 |
starttimestring |
starttimestring |
1247 |
"\nDTEND;VALUE=DATE-TIME:" |
"\nDTEND;VALUE=DATE-TIME:" |
1248 |
startisostring |
startisostring |
1249 |
endtimestring |
endtimestring |
1250 |
"\nSUMMARY:" |
"\nRRULE:FREQ=DAILY;INTERVAL=1;UNTIL=" |
1251 |
summary |
endisostring) |
1252 |
"\nRRULE:FREQ=DAILY;INTERVAL=1;UNTIL=" |
summary) |
|
endisostring) |
|
1253 |
;; no time -> write long event |
;; no time -> write long event |
1254 |
(concat "\nDTSTART;VALUE=DATE:" startisostring |
(list (concat "\nDTSTART;VALUE=DATE:" startisostring |
1255 |
"\nDTEND;VALUE=DATE:" endisostring+1 |
"\nDTEND;VALUE=DATE:" endisostring+1) |
1256 |
"\nSUMMARY:" summary))) |
summary))) |
1257 |
;; no match |
;; no match |
1258 |
nil)) |
nil)) |
1259 |
|
|
1265 |
NONMARKER is a regular expression matching the start of non-marking |
NONMARKER is a regular expression matching the start of non-marking |
1266 |
entries. ENTRY-MAIN is the first line of the diary entry." |
entries. ENTRY-MAIN is the first line of the diary entry." |
1267 |
(if (string-match (concat nonmarker |
(if (string-match (concat nonmarker |
1268 |
"%%(diary-float \\([^)]+\\))\\s-*\\(.*\\)") |
"%%(diary-float \\([^)]+\\))\\s-*\\(.*?\\) ?$") |
1269 |
entry-main) |
entry-main) |
1270 |
(progn |
(progn |
1271 |
(icalendar--dmsg "diary-float %s" entry-main) |
(icalendar--dmsg "diary-float %s" entry-main) |
1281 |
NONMARKER is a regular expression matching the start of non-marking |
NONMARKER is a regular expression matching the start of non-marking |
1282 |
entries. ENTRY-MAIN is the first line of the diary entry." |
entries. ENTRY-MAIN is the first line of the diary entry." |
1283 |
(if (string-match (concat nonmarker |
(if (string-match (concat nonmarker |
1284 |
"%%(diary-date \\([^)]+\\))\\s-*\\(.*\\)") |
"%%(diary-date \\([^)]+\\))\\s-*\\(.*?\\) ?$") |
1285 |
entry-main) |
entry-main) |
1286 |
(progn |
(progn |
1287 |
(icalendar--dmsg "diary-date %s" entry-main) |
(icalendar--dmsg "diary-date %s" entry-main) |
1291 |
|
|
1292 |
(defun icalendar--convert-cyclic-to-ical (nonmarker entry-main) |
(defun icalendar--convert-cyclic-to-ical (nonmarker entry-main) |
1293 |
"Convert `diary-cyclic' diary entry to icalendar format. |
"Convert `diary-cyclic' diary entry to icalendar format. |
|
|
|
1294 |
NONMARKER is a regular expression matching the start of non-marking |
NONMARKER is a regular expression matching the start of non-marking |
1295 |
entries. ENTRY-MAIN is the first line of the diary entry." |
entries. ENTRY-MAIN is the first line of the diary entry." |
1296 |
(if (string-match (concat nonmarker |
(if (string-match (concat nonmarker |
1300 |
"\\(" |
"\\(" |
1301 |
"-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?" |
"-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?" |
1302 |
"\\)?" |
"\\)?" |
1303 |
"\\s-*\\(.*\\)") |
"\\s-*\\(.*?\\) ?$") |
1304 |
entry-main) |
entry-main) |
1305 |
(let* ((frequency (substring entry-main (match-beginning 1) |
(let* ((frequency (substring entry-main (match-beginning 1) |
1306 |
(match-end 1))) |
(match-end 1))) |
1345 |
starttimestring)))) |
starttimestring)))) |
1346 |
(setq endtimestring (format "T%06d" |
(setq endtimestring (format "T%06d" |
1347 |
(+ 10000 time)))))) |
(+ 10000 time)))))) |
1348 |
(concat "\nDTSTART;" |
(list (concat "\nDTSTART;" |
1349 |
(if starttimestring "VALUE=DATE-TIME:" |
(if starttimestring "VALUE=DATE-TIME:" |
1350 |
"VALUE=DATE:") |
"VALUE=DATE:") |
1351 |
startisostring |
startisostring |
1352 |
(or starttimestring "") |
(or starttimestring "") |
1353 |
"\nDTEND;" |
"\nDTEND;" |
1354 |
(if endtimestring "VALUE=DATE-TIME:" |
(if endtimestring "VALUE=DATE-TIME:" |
1355 |
"VALUE=DATE:") |
"VALUE=DATE:") |
1356 |
(if endtimestring endisostring endisostring+1) |
(if endtimestring endisostring endisostring+1) |
1357 |
(or endtimestring "") |
(or endtimestring "") |
1358 |
"\nSUMMARY:" summary |
"\nRRULE:FREQ=DAILY;INTERVAL=" frequency |
1359 |
"\nRRULE:FREQ=DAILY;INTERVAL=" frequency |
;; strange: korganizer does not expect |
1360 |
;; strange: korganizer does not expect |
;; BYSOMETHING here... |
1361 |
;; BYSOMETHING here... |
) |
1362 |
)) |
summary)) |
1363 |
;; no match |
;; no match |
1364 |
nil)) |
nil)) |
1365 |
|
|
1366 |
(defun icalendar--convert-anniversary-to-ical (nonmarker entry-main) |
(defun icalendar--convert-anniversary-to-ical (nonmarker entry-main) |
1367 |
"Convert `diary-anniversary' diary entry to icalendar format. |
"Convert `diary-anniversary' diary entry to icalendar format. |
|
|
|
1368 |
NONMARKER is a regular expression matching the start of non-marking |
NONMARKER is a regular expression matching the start of non-marking |
1369 |
entries. ENTRY-MAIN is the first line of the diary entry." |
entries. ENTRY-MAIN is the first line of the diary entry." |
1370 |
(if (string-match (concat nonmarker |
(if (string-match (concat nonmarker |
1373 |
"\\(" |
"\\(" |
1374 |
"-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?" |
"-0?\\([1-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?" |
1375 |
"\\)?" |
"\\)?" |
1376 |
"\\s-*\\(.*\\)") |
"\\s-*\\(.*?\\) ?$") |
1377 |
entry-main) |
entry-main) |
1378 |
(let* ((datetime (substring entry-main (match-beginning 1) |
(let* ((datetime (substring entry-main (match-beginning 1) |
1379 |
(match-end 1))) |
(match-end 1))) |
1414 |
starttimestring)))) |
starttimestring)))) |
1415 |
(setq endtimestring (format "T%06d" |
(setq endtimestring (format "T%06d" |
1416 |
(+ 10000 time)))))) |
(+ 10000 time)))))) |
1417 |
(concat "\nDTSTART;" |
(list (concat "\nDTSTART;" |
1418 |
(if starttimestring "VALUE=DATE-TIME:" |
(if starttimestring "VALUE=DATE-TIME:" |
1419 |
"VALUE=DATE:") |
"VALUE=DATE:") |
1420 |
startisostring |
startisostring |
1421 |
(or starttimestring "") |
(or starttimestring "") |
1422 |
"\nDTEND;" |
"\nDTEND;" |
1423 |
(if endtimestring "VALUE=DATE-TIME:" |
(if endtimestring "VALUE=DATE-TIME:" |
1424 |
"VALUE=DATE:") |
"VALUE=DATE:") |
1425 |
endisostring |
endisostring |
1426 |
(or endtimestring "") |
(or endtimestring "") |
1427 |
"\nSUMMARY:" summary |
"\nRRULE:FREQ=YEARLY;INTERVAL=1" |
1428 |
"\nRRULE:FREQ=YEARLY;INTERVAL=1" |
;; the following is redundant, |
1429 |
;; the following is redundant, |
;; but korganizer seems to expect this... ;( |
1430 |
;; but korganizer seems to expect this... ;( |
;; and evolution doesn't understand it... :( |
1431 |
;; and evolution doesn't understand it... :( |
;; so... who is wrong?! |
1432 |
;; so... who is wrong?! |
";BYMONTH=" |
1433 |
";BYMONTH=" |
(substring startisostring 4 6) |
1434 |
(substring startisostring 4 6) |
";BYMONTHDAY=" |
1435 |
";BYMONTHDAY=" |
(substring startisostring 6 8)) |
1436 |
(substring startisostring 6 8))) |
summary)) |
1437 |
;; no match |
;; no match |
1438 |
nil)) |
nil)) |
1439 |
|
|
1444 |
;;;###autoload |
;;;###autoload |
1445 |
(defun icalendar-import-file (ical-filename diary-filename |
(defun icalendar-import-file (ical-filename diary-filename |
1446 |
&optional non-marking) |
&optional non-marking) |
1447 |
"Import a iCalendar file and append to a diary file. |
"Import an iCalendar file and append to a diary file. |
1448 |
Argument ICAL-FILENAME output iCalendar file. |
Argument ICAL-FILENAME output iCalendar file. |
1449 |
Argument DIARY-FILENAME input `diary-file'. |
Argument DIARY-FILENAME input `diary-file'. |
1450 |
Optional argument NON-MARKING determines whether events are created as |
Optional argument NON-MARKING determines whether events are created as |
1518 |
"Create a string representation of an iCalendar EVENT." |
"Create a string representation of an iCalendar EVENT." |
1519 |
(let ((string icalendar-import-format) |
(let ((string icalendar-import-format) |
1520 |
(conversion-list |
(conversion-list |
1521 |
'(("%d" DESCRIPTION icalendar-import-format-description) |
'(("%c" CLASS icalendar-import-format-class) |
1522 |
("%s" SUMMARY icalendar-import-format-subject) |
("%d" DESCRIPTION icalendar-import-format-description) |
1523 |
("%l" LOCATION icalendar-import-format-location) |
("%l" LOCATION icalendar-import-format-location) |
1524 |
("%o" ORGANIZER icalendar-import-format-organizer)))) |
("%o" ORGANIZER icalendar-import-format-organizer) |
1525 |
|
("%s" SUMMARY icalendar-import-format-summary) |
1526 |
|
("%t" STATUS icalendar-import-format-status) |
1527 |
|
("%u" URL icalendar-import-format-url)))) |
1528 |
;; convert the specifiers in the format string |
;; convert the specifiers in the format string |
1529 |
(mapcar (lambda (i) |
(mapcar (lambda (i) |
1530 |
(let* ((spec (car i)) |
(let* ((spec (car i)) |
1537 |
(icalendar--rris "%s" |
(icalendar--rris "%s" |
1538 |
(icalendar--convert-string-for-import |
(icalendar--convert-string-for-import |
1539 |
contents) |
contents) |
1540 |
(symbol-value format)))) |
(symbol-value format) |
1541 |
|
t t))) |
1542 |
(setq string (icalendar--rris spec |
(setq string (icalendar--rris spec |
1543 |
formatted-contents |
formatted-contents |
1544 |
string)))) |
string |
1545 |
|
t t)))) |
1546 |
conversion-list) |
conversion-list) |
1547 |
string)) |
string)) |
1548 |
|
|
1549 |
(defun icalendar--convert-ical-to-diary (ical-list diary-file |
(defun icalendar--convert-ical-to-diary (ical-list diary-file |
1550 |
&optional do-not-ask |
&optional do-not-ask |
1551 |
non-marking) |
non-marking) |
1552 |
"Convert an iCalendar file to an Emacs diary file. |
"Convert Calendar data to an Emacs diary file. |
1553 |
Import VEVENTS from the iCalendar object ICAL-LIST and saves them to a |
Import VEVENTS from the iCalendar object ICAL-LIST and saves them to a |
1554 |
DIARY-FILE. If DO-NOT-ASK is nil the user is asked for each event |
DIARY-FILE. If DO-NOT-ASK is nil the user is asked for each event |
1555 |
whether to actually import it. NON-MARKING determines whether diary |
whether to actually import it. NON-MARKING determines whether diary |
1579 |
end-d |
end-d |
1580 |
end-1-d |
end-1-d |
1581 |
end-t |
end-t |
1582 |
(subject (icalendar--convert-string-for-import |
(summary (icalendar--convert-string-for-import |
1583 |
(or (icalendar--get-event-property e 'SUMMARY) |
(or (icalendar--get-event-property e 'SUMMARY) |
1584 |
"No Subject"))) |
"No summary"))) |
1585 |
(rrule (icalendar--get-event-property e 'RRULE)) |
(rrule (icalendar--get-event-property e 'RRULE)) |
1586 |
(rdate (icalendar--get-event-property e 'RDATE)) |
(rdate (icalendar--get-event-property e 'RDATE)) |
1587 |
(duration (icalendar--get-event-property e 'DURATION))) |
(duration (icalendar--get-event-property e 'DURATION))) |
1588 |
(icalendar--dmsg "%s: `%s'" start-d subject) |
(icalendar--dmsg "%s: `%s'" start-d summary) |
1589 |
;; check whether start-time is missing |
;; check whether start-time is missing |
1590 |
(if (and dtstart |
(if (and dtstart |
1591 |
(string= |
(string= |
1603 |
t)))) |
t)))) |
1604 |
(if (and dtend-dec (not (eq dtend-dec dtend-dec-d))) |
(if (and dtend-dec (not (eq dtend-dec dtend-dec-d))) |
1605 |
(message "Inconsistent endtime and duration for %s" |
(message "Inconsistent endtime and duration for %s" |
1606 |
subject)) |
summary)) |
1607 |
(setq dtend-dec dtend-dec-d) |
(setq dtend-dec dtend-dec-d) |
1608 |
(setq dtend-1-dec dtend-1-dec-d))) |
(setq dtend-1-dec dtend-1-dec-d))) |
1609 |
(setq end-d (if dtend-dec |
(setq end-d (if dtend-dec |
1664 |
(setq diary-string |
(setq diary-string |
1665 |
(concat diary-string " " |
(concat diary-string " " |
1666 |
(icalendar--format-ical-event e))) |
(icalendar--format-ical-event e))) |
1667 |
(if do-not-ask (setq subject nil)) |
(if do-not-ask (setq summary nil)) |
1668 |
(icalendar--add-diary-entry diary-string diary-file |
(icalendar--add-diary-entry diary-string diary-file |
1669 |
non-marking subject)) |
non-marking summary)) |
1670 |
;; event was not ok |
;; event was not ok |
1671 |
(setq found-error t) |
(setq found-error t) |
1672 |
(setq error-string |
(setq error-string |
1717 |
(let ((until-1 0)) |
(let ((until-1 0)) |
1718 |
(cond ((string-equal frequency "DAILY") |
(cond ((string-equal frequency "DAILY") |
1719 |
(setq until (icalendar--add-decoded-times |
(setq until (icalendar--add-decoded-times |
1720 |
dtstart-dec |
dtstart-dec |
1721 |
(list 0 0 0 (* (read count) interval) 0 0))) |
(list 0 0 0 (* (read count) interval) 0 0))) |
1722 |
(setq until-1 (icalendar--add-decoded-times |
(setq until-1 (icalendar--add-decoded-times |
1723 |
dtstart-dec |
dtstart-dec |
1914 |
start-t)))) |
start-t)))) |
1915 |
|
|
1916 |
(defun icalendar--add-diary-entry (string diary-file non-marking |
(defun icalendar--add-diary-entry (string diary-file non-marking |
1917 |
&optional subject) |
&optional summary) |
1918 |
"Add STRING to the diary file DIARY-FILE. |
"Add STRING to the diary file DIARY-FILE. |
1919 |
STRING must be a properly formatted valid diary entry. NON-MARKING |
STRING must be a properly formatted valid diary entry. NON-MARKING |
1920 |
determines whether diary events are created as non-marking. If |
determines whether diary events are created as non-marking. If |
1921 |
SUBJECT is not nil it must be a string that gives the subject of the |
SUMMARY is not nil it must be a string that gives the summary of the |
1922 |
entry. In this case the user will be asked whether he wants to insert |
entry. In this case the user will be asked whether he wants to insert |
1923 |
the entry." |
the entry." |
1924 |
(when (or (not subject) |
(when (or (not summary) |
1925 |
(y-or-n-p (format "Add appointment for `%s' to diary? " |
(y-or-n-p (format "Add appointment for `%s' to diary? " |
1926 |
subject))) |
summary))) |
1927 |
(when subject |
(when summary |
1928 |
(setq non-marking |
(setq non-marking |
1929 |
(y-or-n-p (format "Make appointment non-marking? ")))) |
(y-or-n-p (format "Make appointment non-marking? ")))) |
1930 |
(save-window-excursion |
(save-window-excursion |
1931 |
(unless diary-file |
(unless diary-file |
1932 |
(setq diary-file |
(setq diary-file |
1933 |
(read-file-name "Add appointment to this diary file: "))) |
(read-file-name "Add appointment to this diary file: "))) |
1934 |
|
;; Note: make-diary-entry will add a trailing blank char.... :( |
1935 |
(make-diary-entry string non-marking diary-file)))) |
(make-diary-entry string non-marking diary-file)))) |
1936 |
|
|
1937 |
(provide 'icalendar) |
(provide 'icalendar) |