Commit | Line | Data |
---|---|---|
72a81656 CD |
1 | ;;; org-export-latex.el --- LaTeX exporter for Org-mode |
2 | ;; Copyright (C) 2007 Free Software Foundation, Inc. | |
3 | ;; | |
4 | ;; Author: Bastien Guerry <bzg AT altern DOT org> | |
72a81656 | 5 | ;; Keywords: org organizer latex export convert |
0b8568f5 | 6 | ;; Version: $Id: org-export-latex.el,v 0.28a 2007/08/31 06:22:06 guerry Exp guerry $ |
72a81656 CD |
7 | ;; X-URL: <http://www.cognition.ens.fr/~guerry/u/org-export-latex.el> |
8 | ;; | |
9 | ;; This file is part of GNU Emacs. | |
10 | ;; | |
0b8568f5 JW |
11 | ;; GNU Emacs is free software; you can redistribute it and/or modify it |
12 | ;; under the terms of the GNU General Public License as published by the | |
13 | ;; Free Software Foundation; either version 3, or (at your option) any | |
14 | ;; later version. | |
72a81656 | 15 | ;; |
0b8568f5 JW |
16 | ;; GNU Emacs is distributed in the hope that it will be useful, but WITHOUT |
17 | ;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
18 | ;; FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
19 | ;; more details. | |
20 | ;; | |
21 | ;; You should have received a copy of the GNU General Public License along | |
22 | ;; with GNU Emacs; see the file COPYING. If not, write to the Free Software | |
23 | ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | |
24 | ;; 02110-1301, USA. | |
72a81656 | 25 | ;; |
72a81656 | 26 | ;;; Commentary: |
0b8568f5 JW |
27 | ;; |
28 | ;; This library implements a LaTeX exporter for org-mode. | |
72a81656 CD |
29 | ;; |
30 | ;; Put this file into your load-path and the following into your ~/.emacs: | |
31 | ;; (require 'org-export-latex) | |
32 | ;; | |
33 | ;; The interactive functions are similar to those of the HTML exporter: | |
34 | ;; | |
35 | ;; M-x `org-export-as-latex' | |
36 | ;; M-x `org-export-as-latex-batch' | |
37 | ;; M-x `org-export-as-latex-to-buffer' | |
38 | ;; M-x `org-export-region-as-latex' | |
39 | ;; M-x `org-replace-region-by-latex' | |
0b8568f5 | 40 | ;; |
72a81656 CD |
41 | ;;; Code: |
42 | ||
0b8568f5 JW |
43 | (eval-when-compile |
44 | (require 'cl)) | |
45 | ||
72a81656 | 46 | (require 'footnote) |
0b8568f5 | 47 | (require 'org) |
72a81656 | 48 | |
0b8568f5 | 49 | ;;; Variables: |
72a81656 CD |
50 | (defvar org-latex-options-plist nil) |
51 | (defvar org-latex-todo-keywords-1 nil) | |
52 | (defvar org-latex-all-targets-regexp nil) | |
53 | (defvar org-latex-add-level 0) | |
54 | (defvar org-latex-sectioning-depth 0) | |
55 | ||
56 | (defvar org-latex-special-string-regexps | |
57 | '(org-ts-regexp | |
58 | org-scheduled-string | |
59 | org-deadline-string | |
60 | org-clock-string) | |
61 | "A list of regexps to convert as special keywords.") | |
62 | ||
0b8568f5 JW |
63 | (defvar latexp) ; dynamically scoped from org.el |
64 | (defvar re-quote) ; dynamically scoped from org.el | |
65 | (defvar commentsp) ; dynamically scoped from org.el | |
66 | ||
67 | ;;; Custom variables: | |
72a81656 CD |
68 | (defcustom org-export-latex-sectioning-alist |
69 | '((1 "\\section{%s}" "\\section*{%s}") | |
70 | (2 "\\subsection{%s}" "\\subsection*{%s}") | |
71 | (3 "\\subsubsection{%s}" "\\subsubsection*{%s}") | |
72 | (4 "\\paragraph{%s}" "\\paragraph*{%s}") | |
73 | (5 "\\subparagraph{%s}" "\\subparagraph*{%s}")) | |
74 | "Alist of LaTeX commands for inserting sections. | |
75 | Here is the structure of each cell: | |
76 | ||
77 | \(level unnumbered-section numbered-section\) | |
78 | ||
79 | The %s formatter will be replaced by the title of the section." | |
80 | :group 'org-export-latex | |
81 | :type 'alist) | |
82 | ||
83 | (defcustom org-export-latex-emphasis-alist | |
84 | '(("*" "\\textbf{%s}") | |
85 | ("/" "\\emph{%s}") | |
86 | ("_" "\\underline{%s}") | |
87 | ("+" "\\texttt{%s}") | |
88 | ("=" "\\texttt{%s}")) | |
89 | "Alist of LaTeX expressions to convert emphasis fontifiers." | |
90 | :group 'org-export-latex | |
91 | :type 'alist) | |
92 | ||
93 | (defcustom org-export-latex-preamble | |
94 | "\\documentclass[11pt,a4paper]{article} | |
95 | \\usepackage[utf8]{inputenc} | |
96 | \\usepackage[T1]{fontenc} | |
97 | \\usepackage{hyperref}" | |
98 | "Preamble to be inserted at the very beginning of the LaTeX export." | |
99 | :group 'org-export-latex | |
100 | :type 'string) | |
101 | ||
102 | (defcustom org-export-latex-date-format nil | |
103 | "Format string for \\date{...}." | |
104 | :group 'org-export-latex | |
105 | :type 'string) | |
106 | ||
107 | (defcustom org-export-latex-packages-alist nil | |
108 | "Alist of packages to be inserted in the preamble. | |
109 | Each cell is of the forma \( option . package \). | |
110 | ||
111 | For example: | |
112 | ||
113 | \(setq org-export-latex-packages-alist | |
114 | '((\"french\" \"babel\"))" | |
115 | :group 'org-export-latex | |
116 | :type 'alist) | |
117 | ||
118 | (defcustom org-export-latex-low-levels 'description | |
119 | "Choice for converting sections that are below the current | |
120 | admitted level of sectioning. This can be either nil (ignore the | |
121 | sections), 'description (convert them as description lists) or a | |
122 | string to be used instead of \\section{%s} (a %s for inserted the | |
123 | headline is mandatory)." | |
124 | :group 'org-export-latex | |
125 | :type '(choice (const :tag "Ignore" nil) | |
126 | (symbol :tag "Convert as descriptive list" description) | |
127 | (string :tag "Use a section string" :value "\\subparagraph{%s}"))) | |
128 | ||
129 | (defcustom org-export-latex-remove-from-headines | |
130 | '(:todo t :priority t :tags t) | |
131 | "A plist of keywords to remove from headlines. | |
132 | Non-nil means remove this keyword type from the headline. | |
133 | ||
134 | Don't remove the keys, just change their values." | |
135 | :type 'plist | |
136 | :group 'org-export-latex) | |
137 | ||
138 | (defcustom org-export-latex-quotation-marks-convention "en" | |
139 | "Convention for conversion of the quotation marks. | |
140 | This value is overriden by any infile language setup." | |
141 | :group 'org-export-latex | |
142 | :type '(choice (string :tag "english" "en") | |
143 | (string :tag "french" "fr"))) | |
144 | ||
145 | (defcustom org-export-latex-image-default-option "width=10em" | |
146 | "Default option for images." | |
147 | :group 'org-export-latex | |
148 | :type '(string)) | |
149 | ||
150 | (defcustom org-export-latex-coding-system nil | |
151 | "Coding system for the exported LaTex file." | |
152 | :group 'org-export-latex | |
153 | :type 'coding-system) | |
154 | ||
155 | ;; FIXME Do we want this one? | |
156 | ;; (defun org-export-as-latex-and-open (arg) ...) | |
157 | ||
0b8568f5 JW |
158 | |
159 | ;;; Autoload functions: | |
72a81656 CD |
160 | ;;;###autoload |
161 | (defun org-export-as-latex-batch () | |
162 | "Call `org-export-as-latex', may be used in batch processing as | |
163 | emacs --batch | |
164 | --load=$HOME/lib/emacs/org.el | |
165 | --eval \"(setq org-export-headline-levels 2)\" | |
166 | --visit=MyFile --funcall org-export-as-latex-batch" | |
167 | (org-export-as-latex org-export-headline-levels 'hidden)) | |
168 | ||
169 | ;;;###autoload | |
170 | (defun org-export-as-latex-to-buffer (arg) | |
171 | "Call `org-exort-as-latex` with output to a temporary buffer. | |
172 | No file is created. The prefix ARG is passed through to `org-export-as-latex'." | |
173 | (interactive "P") | |
174 | (org-export-as-latex arg nil nil "*Org LaTeX Export*") | |
175 | (switch-to-buffer-other-window "*Org LaTeX Export*")) | |
176 | ||
177 | ;;;###autoload | |
178 | (defun org-replace-region-by-latex (beg end) | |
179 | "Replace the region from BEG to END with its LaTeX export. | |
180 | It assumes the region has `org-mode' syntax, and then convert it to | |
181 | LaTeX. This can be used in any buffer. For example, you could | |
182 | write an itemized list in `org-mode' syntax in an LaTeX buffer and | |
183 | then use this command to convert it." | |
184 | (interactive "r") | |
185 | (let (reg latex buf) | |
186 | (save-window-excursion | |
187 | (if (org-mode-p) | |
188 | (setq latex (org-export-region-as-latex | |
189 | beg end t 'string)) | |
190 | (setq reg (buffer-substring beg end) | |
191 | buf (get-buffer-create "*Org tmp*")) | |
192 | (save-excursion | |
193 | (set-buffer buf) | |
194 | (erase-buffer) | |
195 | (insert reg) | |
196 | (org-mode) | |
197 | (setq latex (org-export-region-as-latex | |
198 | (point-min) (point-max) t 'string))) | |
199 | (kill-buffer buf))) | |
200 | (delete-region beg end) | |
201 | (insert latex))) | |
202 | ||
203 | ;;;###autoload | |
204 | (defun org-export-region-as-latex (beg end &optional body-only buffer) | |
205 | "Convert region from BEG to END in `org-mode' buffer to LaTeX. | |
206 | If prefix arg BODY-ONLY is set, omit file header, footer, and table of | |
207 | contents, and only produce the region of converted text, useful for | |
208 | cut-and-paste operations. | |
209 | If BUFFER is a buffer or a string, use/create that buffer as a target | |
210 | of the converted LaTeX. If BUFFER is the symbol `string', return the | |
211 | produced LaTeX as a string and leave not buffer behind. For example, | |
212 | a Lisp program could call this function in the following way: | |
213 | ||
214 | (setq latex (org-export-region-as-latex beg end t 'string)) | |
215 | ||
216 | When called interactively, the output buffer is selected, and shown | |
217 | in a window. A non-interactive call will only retunr the buffer." | |
218 | (interactive "r\nP") | |
219 | (when (interactive-p) | |
220 | (setq buffer "*Org LaTeX Export*")) | |
221 | (let ((transient-mark-mode t) (zmacs-regions t) | |
222 | rtn) | |
223 | (goto-char end) | |
224 | (set-mark (point)) ;; to activate the region | |
225 | (goto-char beg) | |
226 | (setq rtn (org-export-as-latex | |
227 | nil nil nil | |
228 | buffer body-only)) | |
229 | (if (fboundp 'deactivate-mark) (deactivate-mark)) | |
230 | (if (and (interactive-p) (bufferp rtn)) | |
231 | (switch-to-buffer-other-window rtn) | |
232 | rtn))) | |
233 | ||
234 | ;;;###autoload | |
235 | (defun org-export-as-latex (arg &optional hidden ext-plist | |
236 | to-buffer body-only) | |
237 | "Export current buffer to a LaTeX file." | |
238 | (interactive "P") | |
239 | ;; Make sure we have a file name when we need it. | |
240 | (when (and (not (or to-buffer body-only)) | |
241 | (not buffer-file-name)) | |
242 | (if (buffer-base-buffer) | |
243 | (org-set-local 'buffer-file-name | |
244 | (with-current-buffer (buffer-base-buffer) | |
245 | buffer-file-name)) | |
246 | (error "Need a file name to be able to export"))) | |
247 | ||
248 | (message "Exporting to LaTeX...") | |
249 | (org-update-radio-target-regexp) | |
250 | (org-export-latex-set-initial-vars ext-plist) | |
251 | (let* ((opt-plist org-latex-options-plist) | |
252 | (filename (concat (file-name-as-directory | |
253 | (org-export-directory :LaTeX ext-plist)) | |
254 | (file-name-sans-extension | |
255 | (file-name-nondirectory ;sans-extension | |
256 | buffer-file-name)) ".tex")) | |
257 | (filename (if (equal (file-truename filename) | |
258 | (file-truename buffer-file-name)) | |
259 | (concat filename ".tex") | |
260 | filename)) | |
261 | (buffer (if to-buffer | |
262 | (cond | |
263 | ((eq to-buffer 'string) (get-buffer-create | |
264 | "*Org LaTeX Export*")) | |
265 | (t (get-buffer-create to-buffer))) | |
266 | (find-file-noselect filename))) | |
267 | (region-p (org-region-active-p)) | |
268 | (odd org-odd-levels-only) | |
269 | (preamble (org-export-latex-make-preamble opt-plist)) | |
270 | (skip (plist-get opt-plist :skip-before-1st-heading)) | |
271 | (text (plist-get opt-plist :text)) | |
272 | (first-lines (if skip "" (org-export-latex-first-lines))) | |
273 | (coding-system (and (boundp 'buffer-file-coding-system) | |
274 | buffer-file-coding-system)) | |
275 | (coding-system-for-write (or org-export-latex-coding-system | |
276 | coding-system)) | |
277 | (save-buffer-coding-system (or org-export-latex-coding-system | |
278 | coding-system)) | |
279 | (region (buffer-substring | |
280 | (if region-p (region-beginning) (point-min)) | |
281 | (if region-p (region-end) (point-max)))) | |
282 | (string-for-export | |
283 | ;; FIXME Use org-cleaned-string-for-export instead, only when | |
284 | ;; everyone uses Org >5.04 | |
285 | (org-latex-cleaned-string-for-export | |
286 | region :for-html nil | |
287 | :comments nil | |
288 | :for-LaTeX t | |
289 | :skip-before-1st-heading nil | |
290 | :LaTeX-fragments nil))) | |
291 | (set-buffer buffer) | |
292 | (erase-buffer) | |
0b8568f5 | 293 | |
72a81656 CD |
294 | (unless body-only (insert preamble)) |
295 | (when text (insert (org-export-latex-content text) "\n\n")) | |
296 | (unless skip (insert first-lines)) | |
297 | ||
298 | ;; handle the case where the region does not begin with a section | |
299 | (when region-p | |
300 | (insert (with-temp-buffer | |
301 | (insert string-for-export) | |
302 | (org-export-latex-first-lines)))) | |
303 | ||
304 | (org-export-latex-global | |
305 | (with-temp-buffer | |
306 | (insert string-for-export) | |
307 | (goto-char (point-min)) | |
308 | (re-search-forward "^\\(\\*+\\) " nil t) | |
309 | (let* ((asters (length (match-string 1))) | |
310 | (level (if odd (- asters 2) (- asters 1)))) | |
311 | (setq org-latex-add-level | |
312 | (if odd (1- (/ (1+ asters) 2)) (1- asters))) | |
313 | (org-export-latex-parse-global level odd)))) | |
314 | ||
315 | (unless body-only (insert "\n\\end{document}")) | |
0b8568f5 | 316 | (or to-buffer (save-buffer)) |
72a81656 CD |
317 | (goto-char (point-min)) |
318 | (message "Exporting to LaTeX...done") | |
319 | (if (eq to-buffer 'string) | |
320 | (prog1 (buffer-substring (point-min) (point-max)) | |
321 | (kill-buffer (current-buffer))) | |
322 | (current-buffer)))) | |
323 | ||
72a81656 | 324 | |
0b8568f5 | 325 | ;;; Parsing functions: |
72a81656 CD |
326 | (defun org-export-latex-parse-global (level odd) |
327 | "Parse the current buffer recursively, starting at LEVEL. | |
328 | If ODD is non-nil, assume the buffer only contains odd sections. | |
329 | Return A list reflecting the document structure." | |
330 | (save-excursion | |
331 | (goto-char (point-min)) | |
332 | (let* ((cnt 0) output | |
333 | (depth org-latex-sectioning-depth)) | |
334 | (while (re-search-forward | |
335 | (concat "^\\(\\(?:\\*\\)\\{" | |
336 | (number-to-string (+ (if odd 2 1) level)) | |
337 | "\\}\\) \\(.*\\)$") | |
338 | ;; make sure that there is no upper heading | |
339 | (when (> level 0) | |
340 | (save-excursion | |
341 | (save-match-data | |
342 | (re-search-forward | |
343 | (concat "^\\(\\(?:\\*\\)\\{" | |
344 | (number-to-string level) | |
345 | "\\}\\) \\(.*\\)$") nil t)))) t) | |
346 | (setq cnt (1+ cnt)) | |
347 | (let* ((pos (match-beginning 0)) | |
348 | (heading (match-string 2)) | |
349 | (nlevel (if odd (/ (+ 3 level) 2) (1+ level)))) | |
350 | (save-excursion | |
351 | (narrow-to-region | |
352 | (point) | |
353 | (save-match-data | |
354 | (if (re-search-forward | |
355 | (concat "^\\(\\(?:\\*\\)\\{" | |
356 | (number-to-string (+ (if odd 2 1) level)) | |
357 | "\\}\\) \\(.*\\)$") nil t) | |
358 | (match-beginning 0) | |
359 | (point-max)))) | |
360 | (goto-char (point-min)) | |
361 | (setq output | |
362 | (append output | |
363 | (list | |
364 | (list | |
365 | `(pos . ,pos) | |
366 | `(level . ,nlevel) | |
367 | `(occur . ,cnt) | |
368 | `(heading . ,heading) | |
369 | `(content . ,(org-export-latex-parse-content)) | |
370 | `(subcontent . ,(org-export-latex-parse-subcontent | |
371 | level odd))))))) | |
372 | (widen))) | |
373 | (list output)))) | |
374 | ||
375 | (defun org-export-latex-parse-content () | |
376 | "Extract the content of a section." | |
377 | (let ((beg (point)) | |
378 | (end (if (re-search-forward "^\\(\\*\\)+ .*$" nil t) | |
379 | (progn (beginning-of-line) (point)) | |
380 | (point-max)))) | |
381 | (buffer-substring beg end))) | |
382 | ||
383 | (defun org-export-latex-parse-subcontent (level odd) | |
384 | "Extract the subcontent of a section at LEVEL. | |
385 | If ODD Is non-nil, assume subcontent only contains odd sections." | |
386 | (if (not (re-search-forward | |
387 | (concat "^\\(\\(?:\\*\\)\\{" | |
388 | (number-to-string (+ (if odd 4 2) level)) | |
389 | "\\}\\) \\(.*\\)$") | |
390 | nil t)) | |
391 | nil ; subcontent is nil | |
392 | (org-export-latex-parse-global (+ (if odd 2 1) level) odd))) | |
393 | ||
0b8568f5 JW |
394 | |
395 | ;;; Rendering functions: | |
72a81656 CD |
396 | (defun org-export-latex-global (content) |
397 | "Export CONTENT to LaTeX. | |
398 | CONTENT is an element of the list produced by | |
399 | `org-export-latex-parse-global'." | |
400 | (if (eq (car content) 'subcontent) | |
401 | (mapc 'org-export-latex-sub (cdr content)) | |
402 | (org-export-latex-sub (car content)))) | |
403 | ||
404 | (defun org-export-latex-sub (subcontent) | |
405 | "Export the list SUBCONTENT to LaTeX. | |
406 | SUBCONTENT is an alist containing information about the headline | |
407 | and its content." | |
408 | (mapc (lambda(x) (org-export-latex-subcontent x)) subcontent)) | |
409 | ||
410 | (defun org-export-latex-subcontent (subcontent) | |
411 | "Export each cell of SUBCONTENT to LaTeX." | |
412 | (let ((heading (org-export-latex-fontify-headline | |
413 | (cdr (assoc 'heading subcontent)))) | |
414 | (level (- (cdr (assoc 'level subcontent)) | |
415 | org-latex-add-level)) | |
416 | (occur (number-to-string (cdr (assoc 'occur subcontent)))) | |
417 | (content (cdr (assoc 'content subcontent))) | |
418 | (subcontent (cadr (assoc 'subcontent subcontent))) | |
419 | (num (plist-get org-latex-options-plist :section-numbers))) | |
420 | (cond | |
421 | ;; Normal conversion | |
422 | ((<= level org-latex-sectioning-depth) | |
423 | (let ((sec (assoc level org-export-latex-sectioning-alist))) | |
424 | (insert (format (if num (cadr sec) (caddr sec)) heading) "\n")) | |
425 | (insert (org-export-latex-content content)) | |
426 | (cond ((stringp subcontent) (insert subcontent)) | |
427 | ((listp subcontent) (org-export-latex-sub subcontent)))) | |
428 | ;; At a level under the hl option: we can drop this subsection | |
429 | ((> level org-latex-sectioning-depth) | |
430 | (cond ((eq org-export-latex-low-levels 'description) | |
431 | (insert (format "\\begin{description}\n\n\\item[%s]\n\n" heading)) | |
432 | (insert (org-export-latex-content content)) | |
433 | (cond ((stringp subcontent) (insert subcontent)) | |
434 | ((listp subcontent) (org-export-latex-sub subcontent))) | |
435 | (insert "\\end{description}\n")) | |
436 | ((stringp org-export-latex-low-levels) | |
437 | (insert (format org-export-latex-low-levels heading) "\n") | |
438 | (insert (org-export-latex-content content)) | |
439 | (cond ((stringp subcontent) (insert subcontent)) | |
440 | ((listp subcontent) (org-export-latex-sub subcontent))))))))) | |
441 | ||
0b8568f5 JW |
442 | |
443 | ;;; Exporting internals: | |
444 | (defun org-latex-protect (string) | |
445 | (add-text-properties 0 (length string) '(org-protected t) string) string) | |
446 | ||
447 | (defun org-export-latex-protect-char-in-string (char-list string) | |
448 | "Add org-protected text-property to char from CHAR-LIST in STRING." | |
449 | (with-temp-buffer | |
450 | (save-match-data | |
451 | (insert string) | |
452 | (goto-char (point-min)) | |
453 | (while (re-search-forward (regexp-opt char-list) nil t) | |
454 | (add-text-properties (match-beginning 0) | |
455 | (match-end 0) '(org-protected t))) | |
456 | (buffer-string)))) | |
457 | ||
458 | (defun org-export-latex-set-initial-vars (ext-plist) | |
459 | "Store org local variables required for LaTeX export. | |
460 | EXT-PLIST is an optional additional plist." | |
461 | (setq org-latex-todo-keywords-1 org-todo-keywords-1 | |
462 | org-latex-all-targets-regexp | |
463 | (org-make-target-link-regexp (org-all-targets)) | |
464 | org-latex-options-plist | |
465 | (org-combine-plists (org-default-export-plist) ext-plist | |
466 | (org-infile-export-plist)) | |
467 | org-latex-sectioning-depth | |
468 | (let ((hl-levels (plist-get org-latex-options-plist :headline-levels)) | |
469 | (sec-depth (length org-export-latex-sectioning-alist))) | |
470 | ;; Fall back on org-export-latex-sectioning-alist length if | |
471 | ;; headline-levels goes beyond it | |
472 | (if (> hl-levels sec-depth) sec-depth hl-levels)))) | |
473 | ||
474 | (defun org-export-latex-make-preamble (opt-plist) | |
475 | "Make the LaTeX preamble and return it as a string. | |
476 | Argument OPT-PLIST is the options plist for current buffer." | |
477 | (let ((toc (plist-get opt-plist :table-of-contents))) | |
478 | (format (concat org-export-latex-preamble | |
479 | " | |
480 | %s | |
481 | ||
482 | \\begin{document} | |
483 | ||
484 | \\title{%s} | |
485 | %s | |
486 | %s | |
487 | \\maketitle | |
488 | %s | |
489 | %s | |
490 | ") | |
491 | (if org-export-latex-packages-alist | |
492 | (mapconcat (lambda(p) | |
493 | (if (equal "" (car p)) | |
494 | (format "\\usepackage{%s}" (cadr p)) | |
495 | (format "\\usepackage[%s]{%s}" | |
496 | (car p) (cadr p)))) | |
497 | org-export-latex-packages-alist "\n") "") | |
498 | (or (plist-get opt-plist :title) | |
499 | (and (not | |
500 | (plist-get opt-plist :skip-before-1st-heading)) | |
501 | (org-export-grab-title-from-buffer)) | |
502 | (and buffer-file-name | |
503 | (file-name-sans-extension | |
504 | (file-name-nondirectory buffer-file-name))) | |
505 | "UNTITLED") | |
506 | (if (plist-get opt-plist :author-info) | |
507 | (format "\\author{%s}" | |
508 | (or (plist-get opt-plist :author) user-full-name)) | |
509 | (format "%%\\author{%s}" | |
510 | (or (plist-get opt-plist :author) user-full-name))) | |
511 | (if (plist-get opt-plist :timestamps) | |
512 | (format "\\date{%s}" | |
513 | (format-time-string (or org-export-latex-date-format | |
514 | (car org-time-stamp-formats)))) | |
515 | "%\\date{}") | |
516 | (if (and (plist-get opt-plist :section-numbers) toc) | |
517 | (format "\\setcounter{tocdepth}{%s}" | |
518 | (plist-get opt-plist :headline-levels)) "") | |
519 | (if (and (plist-get opt-plist :section-numbers) toc) | |
520 | "\\tableofcontents" "")))) | |
521 | ||
522 | (defun org-export-latex-first-lines (&optional comments) | |
523 | "Export the first lines before first headline. | |
524 | COMMENTS is either nil to replace them with the empty string or a | |
525 | formatting string like %%%%s if we want to comment them out." | |
526 | (save-excursion | |
527 | (goto-char (point-min)) | |
528 | (let* ((end (if (re-search-forward "^\\*" nil t) | |
529 | (goto-char (match-beginning 0)) | |
530 | (goto-char (point-max))))) | |
531 | (org-export-latex-content | |
532 | (org-latex-cleaned-string-for-export | |
533 | (buffer-substring (point-min) end) | |
534 | :for-html nil | |
535 | :for-LaTeX t | |
536 | :comments nil | |
537 | :skip-before-1st-heading nil | |
538 | :LaTeX-fragments nil))))) | |
539 | ||
540 | (defun org-export-latex-keywords-maybe (remove-list) | |
72a81656 CD |
541 | "Maybe remove keywords depending on rules in REMOVE-LIST." |
542 | (goto-char (point-min)) | |
543 | (let ((re-todo (mapconcat 'identity org-latex-todo-keywords-1 "\\|"))) | |
544 | ;; convert TODO keywords | |
545 | (when (re-search-forward (concat "^\\(" re-todo "\\)") nil t) | |
546 | (if (plist-get remove-list :todo) | |
547 | (replace-match "") | |
548 | (replace-match (format "\\texttt{%s}" (match-string 1)) t t))) | |
549 | ;; convert priority string | |
550 | (when (re-search-forward "\\[\\\\#.\\]" nil t) | |
551 | (if (plist-get remove-list :priority) | |
552 | (replace-match "") | |
553 | (replace-match (format "\\texttt{%s}" (match-string 0)) t t))) | |
554 | ;; convert tags | |
555 | (when (re-search-forward "\\(:[a-zA-Z0-9]+\\)+:" nil t) | |
0b8568f5 JW |
556 | (if (or (not org-export-with-tags) |
557 | (plist-get remove-list :tags)) | |
72a81656 CD |
558 | (replace-match "") |
559 | (replace-match (format "\\texttt{%s}" (match-string 0)) t t))))) | |
560 | ||
561 | (defun org-export-latex-fontify-headline (headline) | |
562 | "Fontify special words in a HEADLINE." | |
563 | (with-temp-buffer | |
564 | ;; FIXME: org-inside-LaTeX-fragment-p doesn't work when the $...$ is at | |
565 | ;; the beginning of the buffer - inserting "\n" is safe here though. | |
566 | (insert "\n" headline) | |
567 | (goto-char (point-min)) | |
72a81656 CD |
568 | (org-export-latex-special-chars |
569 | (plist-get org-latex-options-plist :sub-superscript)) | |
0b8568f5 JW |
570 | (when (plist-get org-latex-options-plist :emphasize) |
571 | (org-export-latex-fontify)) | |
572 | (org-export-latex-keywords-maybe | |
72a81656 CD |
573 | org-export-latex-remove-from-headines) |
574 | (org-export-latex-links) | |
575 | (org-trim (buffer-substring-no-properties (point-min) (point-max))))) | |
576 | ||
0b8568f5 JW |
577 | (defun org-export-latex-fix-invisible-strings () |
578 | "Comment out (INVISIBLE) warnings." | |
579 | (goto-char (point-min)) | |
580 | (while (re-search-forward "(INVISIBLE)" nil t) | |
581 | (replace-match "%\\&"))) | |
582 | ||
72a81656 CD |
583 | (defun org-export-latex-content (content) |
584 | "Convert CONTENT string to LaTeX." | |
585 | (with-temp-buffer | |
586 | (insert content) | |
587 | (org-export-latex-quotation-marks) | |
72a81656 CD |
588 | (org-export-latex-special-chars |
589 | (plist-get org-latex-options-plist :sub-superscript)) | |
0b8568f5 JW |
590 | (when (plist-get org-latex-options-plist :emphasize) |
591 | (org-export-latex-fontify)) | |
72a81656 | 592 | (org-export-latex-links) |
0b8568f5 | 593 | (org-export-latex-keywords) |
72a81656 CD |
594 | (org-export-latex-itemize) |
595 | (org-export-latex-enumerate) | |
596 | (org-export-latex-tables | |
597 | (plist-get org-latex-options-plist :tables)) | |
598 | (org-export-latex-fixed-width | |
599 | (plist-get org-latex-options-plist :fixed-width)) | |
0b8568f5 | 600 | (org-export-latex-fix-invisible-strings) |
72a81656 CD |
601 | (buffer-substring (point-min) (point-max)))) |
602 | ||
72a81656 CD |
603 | (defun org-export-latex-quotation-marks () |
604 | "Export question marks depending on language conventions. | |
605 | Local definition of the language overrides | |
606 | `org-export-latex-quotation-marks-convention' which overrides | |
607 | `org-export-default-language'." | |
608 | (let* ((lang (or (plist-get org-latex-options-plist :language) | |
609 | org-export-latex-quotation-marks-convention)) | |
610 | (quote-rpl (if (equal lang "fr") | |
611 | '(("\\(\\s-\\)\"" "«~") | |
612 | ("\\(\\S-\\)\"" "~»") | |
613 | ("\\(\\s-\\)'" "`")) | |
614 | '(("\\(\\s-\\)\"" "``") | |
615 | ("\\(\\S-\\)\"" "''") | |
616 | ("\\(\\s-\\)'" "`"))))) | |
617 | (mapc (lambda(l) (goto-char (point-min)) | |
618 | (while (re-search-forward (car l) nil t) | |
619 | (let ((rpl (concat (match-string 1) (cadr l)))) | |
620 | (org-latex-protect rpl) | |
621 | (org-if-unprotected | |
622 | (replace-match rpl t t))))) quote-rpl))) | |
623 | ||
624 | ;; | chars/string in Org | normal environment | math environment | | |
625 | ;; |-----------------------+-----------------------+-----------------------| | |
626 | ;; | & # % $ | \& \# \% \$ | \& \# \% \$ | | |
627 | ;; | { } _ ^ \ | \ { \ } \_ \^ \\ | { } _ ^ \ | | |
628 | ;; |-----------------------+-----------------------+-----------------------| | |
629 | ;; | a_b and a^b | $a_b$ and $a^b$ | a_b and a^b | | |
630 | ;; | a_abc and a_{abc} | $a_a$bc and $a_{abc}$ | a_abc and a_{abc} | | |
631 | ;; | \tau and \mu | $\tau$ and $\mu$ | \tau and \mu | | |
632 | ;; |-----------------------+-----------------------+-----------------------| | |
633 | ;; | \_ \^ | \_ \^ | \_ \^ | | |
634 | ;; | \(a=\mu\mbox{m}\) | \(a=\mu\mbox{m}\) | \(a=\mu\mbox{m}\) | | |
635 | ;; | \[\beta^2-a=0\] | \[\beta^2-a=0\] | \[\beta^2-a=0\] | | |
636 | ;; | $x=22\tau$ | $x=22\tau$ | $x=22\tau$ | | |
637 | ;; | $$\alpha=\sqrt{a^3}$$ | $$\alpha=\sqrt{a^3}$$ | $$\alpha=\sqrt{a^3}$$ | | |
638 | ||
639 | (defun org-export-latex-special-chars (sub-superscript) | |
640 | "Export special characters to LaTeX. | |
641 | If SUB-SUPERSCRIPT is non-nil, convert \\ and ^. | |
642 | See the `org-export-latex.el' code for a complete conversion table." | |
643 | (goto-char (point-min)) | |
644 | (mapc (lambda(c) | |
645 | (goto-char (point-min)) | |
646 | (while (re-search-forward c nil t) | |
647 | ;; Put the point where to check for org-protected | |
648 | (unless (get-text-property (match-beginning 2) 'org-protected) | |
649 | (cond ((member (match-string 2) '("\\$" "$")) | |
650 | (if (equal (match-string 2) "\\$") | |
651 | (replace-match (concat (match-string 1) "$" | |
652 | (match-string 3)) t t) | |
653 | (replace-match (concat (match-string 1) "\\$" | |
654 | (match-string 3)) t t))) | |
655 | ((member (match-string 2) '("&" "#" "%")) | |
656 | (if (equal (match-string 1) "\\") | |
657 | (replace-match (match-string 2) t t) | |
658 | (replace-match (concat (match-string 1) "\\" | |
659 | (match-string 2)) t t))) | |
660 | ((equal (match-string 2) "~") | |
0b8568f5 JW |
661 | (cond ((equal (match-string 1) "\\") nil) |
662 | ((eq 'org-link (get-text-property 0 'face (match-string 2))) | |
663 | (replace-match (concat (match-string 1) "\\~") t t)) | |
664 | (t (replace-match | |
665 | (org-latex-protect | |
666 | (concat (match-string 1) "\\~{}")) t t)))) | |
72a81656 CD |
667 | ((member (match-string 2) '("{" "}")) |
668 | (unless (save-match-data (org-inside-LaTeX-fragment-p)) | |
669 | (if (equal (match-string 1) "\\") | |
670 | (replace-match (match-string 2) t t) | |
671 | (replace-match (concat (match-string 1) "\\" | |
672 | (match-string 2)) t t))))) | |
673 | (unless (save-match-data (org-inside-LaTeX-fragment-p)) | |
674 | (cond ((equal (match-string 2) "\\") | |
675 | (replace-match (or (save-match-data | |
676 | (org-export-latex-treat-backslash-char | |
677 | (match-string 1) | |
678 | (match-string 3))) "") t t)) | |
679 | ((member (match-string 2) '("_" "^")) | |
680 | (replace-match (or (save-match-data | |
681 | (org-export-latex-treat-sub-super-char | |
682 | sub-superscript | |
683 | (match-string 1) | |
684 | (match-string 2) | |
685 | (match-string 3))) "") t t))))))) | |
686 | '("^\\([^\n$]*?\\|^\\)\\(\\\\?\\$\\)\\([^\n$]*\\)$" | |
0b8568f5 JW |
687 | "\\([a-za-z0-9]+\\|[ \t\n]\\|\\b\\|\\\\\\)\\(_\\|\\^\\)\\([a-za-z0-9]+\\|[ \t\n]\\|[:punct:]\\|{[a-za-z0-9]+}\\|([a-za-z0-9]+)\\)" |
688 | "\\(.\\|^\\)\\(\\\\\\)\\([ \t\n]\\|[a-zA-Z&#%{}\"]+\\)" | |
72a81656 CD |
689 | "\\(.\\|^\\)\\(&\\)" |
690 | "\\(.\\|^\\)\\(#\\)" | |
691 | "\\(.\\|^\\)\\(%\\)" | |
692 | "\\(.\\|^\\)\\({\\)" | |
693 | "\\(.\\|^\\)\\(}\\)" | |
694 | "\\(.\\|^\\)\\(~\\)"))) | |
695 | ||
696 | (defun org-export-latex-treat-sub-super-char | |
697 | (subsup string-before char string-after) | |
698 | "Convert the \"_\" and \"^\" characters to LaTeX. | |
699 | SUBSUP corresponds to the ^: option in the #+OPTIONS line. | |
700 | Convert CHAR depending on STRING-BEFORE and STRING-AFTER." | |
701 | (cond ((equal string-before "\\") | |
702 | (concat string-before char string-after)) | |
703 | ;; this is part of a math formula | |
704 | ((and (string-match "\\S-+" string-before) | |
705 | (string-match "\\S-+" string-after)) | |
0b8568f5 | 706 | (cond ((eq 'org-link (get-text-property 0 'face char)) |
72a81656 CD |
707 | (concat string-before "\\" char string-after)) |
708 | ((save-match-data (org-inside-LaTeX-fragment-p)) | |
709 | (if subsup | |
710 | (cond ((eq 1 (length string-after)) | |
711 | (concat string-before char string-after)) | |
712 | ((string-match "[({]?\\([^)}]+\\)[)}]?" string-after) | |
713 | (format "%s%s{%s}" string-before char | |
714 | (match-string 1 string-after)))))) | |
0b8568f5 | 715 | ((and subsup |
72a81656 CD |
716 | (> (length string-after) 1) |
717 | (string-match "[({]?\\([^)}]+\\)[)}]?" string-after)) | |
718 | (format "$%s%s{%s}$" string-before char | |
719 | (match-string 1 string-after))) | |
0b8568f5 JW |
720 | (subsup (concat "$" string-before char string-after "$")) |
721 | (t (concat string-before "\\" char string-after)))) | |
72a81656 CD |
722 | (t (concat string-before "\\" char string-after)))) |
723 | ||
724 | (defun org-export-latex-treat-backslash-char (string-before string-after) | |
725 | "Convert the \"$\" special character to LaTeX. | |
726 | The conversion is made depending of STRING-BEFORE and STRING-AFTER." | |
727 | (cond ((member (list string-after) org-html-entities) | |
728 | ;; backslash is part of a special entity (like "\alpha") | |
729 | (concat string-before "$\\" | |
730 | (or (cdar (member (list string-after) org-html-entities)) | |
731 | string-after) "$")) | |
732 | ((and (not (string-match "^[ \n\t]" string-after)) | |
0b8568f5 | 733 | (not (string-match "[ \t]\\'\\|^" string-before))) |
72a81656 CD |
734 | ;; backslash is inside a word |
735 | (concat string-before "$\\backslash$" string-after)) | |
736 | ((not (or (equal string-after "") | |
737 | (string-match "^[ \t\n]" string-after))) | |
738 | ;; backslash might escape a character (like \#) or a user TeX | |
739 | ;; macro (like \setcounter) | |
740 | (concat string-before "\\" string-after)) | |
741 | ((and (string-match "^[ \t\n]" string-after) | |
742 | (string-match "[ \t\n]\\'" string-before)) | |
743 | ;; backslash is alone, convert it to $\backslash$ | |
744 | (concat string-before "$\\backslash$" string-after)) | |
745 | (t (concat string-before "$\\backslash$" string-after)))) | |
746 | ||
0b8568f5 JW |
747 | (defun org-export-latex-keywords () |
748 | "Convert special keywords to LaTeX. | |
749 | Regexps are those from `org-latex-special-string-regexps'." | |
750 | (let ((rg org-latex-special-string-regexps) r) | |
751 | (while (setq r (pop rg)) | |
752 | (goto-char (point-min)) | |
753 | (while (re-search-forward (eval r) nil t) | |
754 | (replace-match (format "\\\\texttt{%s}" (match-string 0)) t))))) | |
755 | ||
756 | ;; FIXME - we need better implementation for nested lists | |
72a81656 CD |
757 | (defun org-export-latex-fixed-width (opt) |
758 | "When OPT is non-nil convert fixed-width sections to LaTeX." | |
759 | (goto-char (point-min)) | |
760 | (while (re-search-forward "^[ \t]*:" nil t) | |
761 | (if opt | |
762 | (progn (goto-char (match-beginning 0)) | |
763 | (insert "\\begin{verbatim}\n") | |
764 | (while (looking-at "^\\([ \t]*\\):\\(.*\\)$") | |
765 | (replace-match (concat (match-string 1) | |
766 | (match-string 2)) t t) | |
767 | (forward-line)) | |
768 | (insert "\\end{verbatim}\n\n")) | |
769 | (progn (goto-char (match-beginning 0)) | |
770 | (while (looking-at "^\\([ \t]*\\):\\(.*\\)$") | |
771 | (replace-match (concat "%" (match-string 1) | |
772 | (match-string 2)) t t) | |
773 | (forward-line)))))) | |
774 | ||
0b8568f5 | 775 | ;; FIXME Use org-export-highlight-first-table-line ? |
72a81656 CD |
776 | (defun org-export-latex-tables (opt) |
777 | "When OPT is non-nil convert tables to LaTeX." | |
778 | (goto-char (point-min)) | |
779 | (while (re-search-forward "^\\([ \t]*\\)|" nil t) | |
780 | ;; Re-align the table to update org-table-last-alignment | |
0b8568f5 | 781 | (save-window-excursion (save-match-data (org-table-align))) |
72a81656 CD |
782 | (let (tbl-list |
783 | (beg (match-beginning 0)) | |
784 | (end (save-excursion | |
785 | (re-search-forward | |
786 | (concat "^" (regexp-quote (match-string 1)) | |
787 | "[^|]\\|\\'") nil t) (match-beginning 0)))) | |
788 | (beginning-of-line) | |
789 | (while (not (eq end (point))) | |
790 | (if (looking-at "[ \t]*|\\([^-|].+\\)|[ \t]*$") | |
791 | (push (split-string (org-trim (match-string 1)) "|") tbl-list) | |
792 | (push 'hline tbl-list)) | |
793 | (forward-line)) | |
0b8568f5 | 794 | ;; comment region out instead of deleting it ? |
72a81656 CD |
795 | (apply 'delete-region (list beg end)) |
796 | (when opt (insert (orgtbl-to-latex (nreverse tbl-list) | |
797 | nil) "\n\n"))))) | |
798 | ||
72a81656 CD |
799 | (defun org-export-latex-list (srch0 srch1 srch2 rpl0 rpl1) |
800 | "Convert lists to LaTeX." | |
801 | (goto-char (point-min)) | |
802 | (while (re-search-forward srch0 nil t) | |
803 | (let* ((beg (match-beginning 0)) | |
804 | (prefix (regexp-quote (match-string 1))) | |
805 | (end-string (when (re-search-forward srch1 nil t) | |
806 | (match-string 0)))) | |
807 | (goto-char beg) (insert rpl0) | |
808 | (while (re-search-forward | |
809 | (concat "^" prefix srch2) | |
810 | (if (not end-string) | |
811 | (point-max) | |
812 | (save-match-data | |
813 | (save-excursion | |
814 | (re-search-forward | |
815 | (regexp-quote end-string) nil t)))) t) | |
816 | (replace-match | |
817 | (concat "\\item " | |
818 | (if (match-string 1) | |
819 | (format "\\texttt{%s}" (match-string 1)))) | |
820 | t t)) | |
821 | (goto-char (if end-string | |
822 | (progn (re-search-forward | |
823 | (regexp-quote end-string) nil t) | |
824 | (match-beginning 0)) | |
825 | (point-max))) | |
826 | (skip-chars-backward "\n") (forward-line 2) | |
827 | (insert rpl1)))) | |
828 | ||
829 | (defun org-export-latex-itemize () | |
830 | "Convert item list to LaTeX." | |
831 | (org-export-latex-list | |
832 | "^\\([ \t]*\\)-" | |
833 | "^[^ \n\t-]+.*$" | |
834 | "- ?\\(\\[.+\\]\\)?" | |
835 | "\\begin{itemize}\n" | |
836 | "\\end{itemize}\n")) | |
837 | ||
838 | (defun org-export-latex-enumerate () | |
839 | "Convert numeric list to LaTeX." | |
840 | (org-export-latex-list | |
841 | "^\\([ \t]*\\)[0-9]+[\.)] \\(\\[.+\\]\\)? ?" | |
842 | "^[^ \n\t0-9]+.*$" | |
843 | "[0-9]+[\.)] ?\\(\\[.+\\]\\)?" | |
844 | "\\begin{enumerate}\n" | |
845 | "\\end{enumerate}\n")) | |
846 | ||
847 | (defun org-export-latex-fontify () | |
848 | "Convert fontification to LaTeX." | |
849 | (goto-char (point-min)) | |
850 | (while (re-search-forward org-emph-re nil t) | |
851 | ;; The match goes one char after the *string* | |
852 | (unless (get-text-property (1- (point)) 'org-protected) | |
853 | (replace-match | |
854 | (concat (match-string 1) | |
855 | (format | |
856 | (org-export-latex-protect-char-in-string | |
857 | '("\\" "{" "}") | |
858 | (cadr (assoc (match-string 3) | |
859 | org-export-latex-emphasis-alist))) | |
860 | (match-string 4)) | |
861 | (match-string 5)) t t) | |
862 | (backward-char)))) | |
863 | ||
72a81656 CD |
864 | (defun org-export-latex-links () |
865 | ;; Make sure to use the LaTeX hyperref and graphicx package | |
866 | ;; or send some warnings. | |
867 | "Convert links to LaTeX." | |
868 | (goto-char (point-min)) | |
869 | (while (re-search-forward org-bracket-link-analytic-regexp nil t) | |
870 | (org-if-unprotected | |
871 | (goto-char (match-beginning 0)) | |
872 | (let* ((re-radio org-latex-all-targets-regexp) | |
873 | (remove (list (match-beginning 0) (match-end 0))) | |
874 | (type (match-string 2)) | |
875 | (raw-path (match-string 3)) | |
876 | (full-raw-path (concat (match-string 1) raw-path)) | |
877 | (desc (match-string 5)) | |
878 | imgp radiop | |
879 | ;; define the path of the link | |
880 | (path (cond | |
881 | ((member type '("http" "https" "ftp")) | |
882 | (concat type ":" raw-path)) | |
883 | ((and re-radio (string-match re-radio raw-path)) | |
884 | (setq radiop t)) | |
885 | ((equal type "mailto") | |
886 | (concat type ":" raw-path)) | |
887 | ((equal type "file") | |
888 | (if (and (or (org-file-image-p (expand-file-name raw-path)) | |
889 | (string-match "\\.eps$" raw-path)) | |
890 | (equal desc full-raw-path)) | |
891 | (setq imgp t) | |
892 | (progn (when (string-match "\\(.+\\)::.+" raw-path) | |
893 | (setq raw-path (match-string 1 raw-path))) | |
894 | (if (file-exists-p raw-path) | |
895 | (concat type "://" (expand-file-name raw-path)) | |
896 | (concat type "://" (org-export-directory | |
897 | :LaTeX org-latex-options-plist) | |
898 | raw-path)))))))) | |
899 | ;; process with link inserting | |
900 | (apply 'delete-region remove) | |
901 | (cond ((and imgp (plist-get org-latex-options-plist :inline-images)) | |
902 | (insert (format "\\includegraphics[%s]{%s}" | |
903 | ;; image option should be set be a comment line | |
904 | org-export-latex-image-default-option | |
905 | (expand-file-name raw-path)))) | |
906 | ;; FIXME: what about caption? image properties? | |
907 | (radiop (insert (format "\\hyperref[%s]{%s}" raw-path desc))) | |
908 | (path (insert (format "\\href{%s}{%s}" path desc))) | |
909 | (t (insert "\\texttt{" desc "}"))))))) | |
910 | ||
911 | ||
0b8568f5 | 912 | ;;; org-latex-cleaned-string-for-export: |
72a81656 CD |
913 | (defun org-latex-cleaned-string-for-export (string &rest parameters) |
914 | "Cleanup a buffer STRING so that links can be created safely." | |
915 | (interactive) | |
916 | (let* ((re-radio (and org-target-link-regexp | |
917 | (concat "\\([^<]\\)\\(" org-target-link-regexp "\\)"))) | |
918 | (re-plain-link (concat "\\([^[<]\\)" org-plain-link-re)) | |
919 | (re-angle-link (concat "\\([^[]\\)" org-angle-link-re)) | |
920 | (re-archive (concat ":" org-archive-tag ":")) | |
921 | (re-quote (concat "^\\*+[ \t]+" org-quote-string "\\>")) | |
922 | (htmlp (plist-get parameters :for-html)) | |
923 | (latexp (plist-get parameters :for-LaTeX)) | |
924 | (commentsp (plist-get parameters :comments)) | |
925 | (inhibit-read-only t) | |
926 | (outline-regexp "\\*+ ") | |
927 | a b xx | |
928 | rtn p) | |
929 | (save-excursion | |
930 | (set-buffer (get-buffer-create " org-mode-tmp")) | |
931 | (erase-buffer) | |
932 | (insert string) | |
933 | ;; Remove license-to-kill stuff | |
934 | (while (setq p (text-property-any (point-min) (point-max) | |
935 | :org-license-to-kill t)) | |
936 | (delete-region p (next-single-property-change p :org-license-to-kill))) | |
937 | ||
938 | (let ((org-inhibit-startup t)) (org-mode)) | |
939 | (untabify (point-min) (point-max)) | |
940 | ||
941 | ;; Get the correct stuff before the first headline | |
942 | (when (plist-get parameters :skip-before-1st-heading) | |
943 | (goto-char (point-min)) | |
944 | (when (re-search-forward "^\\*+[ \t]" nil t) | |
945 | (delete-region (point-min) (match-beginning 0)) | |
946 | (goto-char (point-min)) | |
947 | (insert "\n"))) | |
948 | (when (plist-get parameters :add-text) | |
949 | (goto-char (point-min)) | |
950 | (insert (plist-get parameters :add-text) "\n")) | |
951 | ||
952 | ;; Get rid of archived trees | |
953 | (when (not (eq org-export-with-archived-trees t)) | |
954 | (goto-char (point-min)) | |
955 | (while (re-search-forward re-archive nil t) | |
956 | (if (not (org-on-heading-p t)) | |
957 | (org-end-of-subtree t) | |
958 | (beginning-of-line 1) | |
959 | (setq a (if org-export-with-archived-trees | |
960 | (1+ (point-at-eol)) (point)) | |
961 | b (org-end-of-subtree t)) | |
962 | (if (> b a) (delete-region a b))))) | |
963 | ||
964 | ;; Get rid of property drawers | |
965 | (unless org-export-with-property-drawer | |
966 | (goto-char (point-min)) | |
967 | (while (re-search-forward "^[ \t]*:PROPERTIES:[ \t]*\n\\([^@]*?\n\\)?[ \t]*:END:[ \t]*\n" nil t) | |
968 | (replace-match ""))) | |
969 | ||
970 | ;; Find targets in comments and move them out of comments, | |
971 | ;; but mark them as targets that should be invisible | |
972 | (goto-char (point-min)) | |
973 | (while (re-search-forward "^#.*?\\(<<<?[^>\r\n]+>>>?\\).*" nil t) | |
974 | (replace-match "\\1(INVISIBLE)")) | |
975 | ||
976 | ;; Specific LaTeX cleaning | |
977 | (when latexp | |
978 | (require 'org-export-latex nil t) | |
979 | (org-export-latex-cleaned-string)) | |
980 | ||
981 | ;; Protect stuff from HTML processing | |
982 | (goto-char (point-min)) | |
983 | (let ((formatters `((,htmlp "HTML" "BEGIN_HTML" "END_HTML"))) fmt) | |
984 | (while (re-search-forward "^[ \t]*:.*\\(\n[ \t]*:.*\\)*" nil t) | |
985 | (add-text-properties (match-beginning 0) (match-end 0) | |
986 | '(org-protected t))) | |
987 | (while formatters | |
988 | (setq fmt (pop formatters)) | |
989 | (when (car fmt) | |
990 | (goto-char (point-min)) | |
991 | (while (re-search-forward (concat "^#\\+" (cadr fmt) | |
992 | ":[ \t]*\\(.*\\)") nil t) | |
993 | (replace-match "\\1" t) | |
994 | (add-text-properties | |
995 | (point-at-bol) (min (1+ (point-at-eol)) (point-max)) | |
996 | '(org-protected t)))) | |
997 | (goto-char (point-min)) | |
998 | (while (re-search-forward | |
999 | (concat "^#\\+" | |
1000 | (caddr fmt) "\\>.*\\(\\(\n.*\\)*?\n\\)#\\+" | |
1001 | (cadddr fmt) "\\>.*\n?") nil t) | |
1002 | (if (car fmt) | |
1003 | (add-text-properties (match-beginning 1) (1+ (match-end 1)) | |
1004 | '(org-protected t)) | |
1005 | (delete-region (match-beginning 0) (match-end 0)))) | |
1006 | (goto-char (point-min)) | |
1007 | (while (re-search-forward re-quote nil t) | |
1008 | (goto-char (match-beginning 0)) | |
1009 | (end-of-line 1) | |
1010 | (add-text-properties (point) (org-end-of-subtree t) | |
1011 | '(org-protected t))))) | |
1012 | ||
72a81656 CD |
1013 | ;; Find matches for radio targets and turn them into internal links |
1014 | (goto-char (point-min)) | |
1015 | (when re-radio | |
1016 | (while (re-search-forward re-radio nil t) | |
1017 | (org-if-unprotected | |
1018 | (replace-match "\\1[[\\2]]")))) | |
1019 | ||
1020 | ;; Find all links that contain a newline and put them into a single line | |
1021 | (goto-char (point-min)) | |
1022 | (while (re-search-forward "\\(\\(\\[\\|\\]\\)\\[[^]]*?\\)[ \t]*\n[ \t]*\\([^]]*\\]\\(\\[\\|\\]\\)\\)" nil t) | |
1023 | (org-if-unprotected | |
1024 | (replace-match "\\1 \\3") | |
1025 | (goto-char (match-beginning 0)))) | |
1026 | ||
1027 | ;; Convert LaTeX fragments to images | |
1028 | (when (plist-get parameters :LaTeX-fragments) | |
1029 | (org-format-latex | |
1030 | (concat "ltxpng/" (file-name-sans-extension | |
1031 | (file-name-nondirectory | |
1032 | org-current-export-file))) | |
1033 | org-current-export-dir nil "Creating LaTeX image %s")) | |
1034 | (message "Exporting...") | |
1035 | ||
1036 | ;; Normalize links: Convert angle and plain links into bracket links | |
1037 | ;; Expand link abbreviations | |
1038 | (goto-char (point-min)) | |
1039 | (while (re-search-forward re-plain-link nil t) | |
1040 | (goto-char (1- (match-end 0))) | |
1041 | (org-if-unprotected | |
1042 | (let* ((s (concat (match-string 1) "[[" (match-string 2) | |
1043 | ":" (match-string 3) "]]"))) | |
1044 | ;; added 'org-protected property to links | |
0b8568f5 | 1045 | (put-text-property 0 (length s) 'face 'org-link s) |
72a81656 CD |
1046 | (replace-match s t t)))) |
1047 | (goto-char (point-min)) | |
1048 | (while (re-search-forward re-angle-link nil t) | |
1049 | (goto-char (1- (match-end 0))) | |
1050 | (org-if-unprotected | |
1051 | (let* ((s (concat (match-string 1) "[[" (match-string 2) | |
1052 | ":" (match-string 3) "]]"))) | |
0b8568f5 | 1053 | (put-text-property 0 (length s) 'face 'org-link s) |
72a81656 CD |
1054 | (replace-match s t t)))) |
1055 | (goto-char (point-min)) | |
1056 | (while (re-search-forward org-bracket-link-regexp nil t) | |
1057 | (org-if-unprotected | |
1058 | (let* ((s (concat "[[" (setq xx (save-match-data | |
1059 | (org-link-expand-abbrev (match-string 1)))) | |
1060 | "]" | |
1061 | (if (match-end 3) | |
1062 | (match-string 2) | |
1063 | (concat "[" xx "]")) | |
1064 | "]"))) | |
0b8568f5 | 1065 | (put-text-property 0 (length s) 'face 'org-link s) |
72a81656 CD |
1066 | (replace-match s t t)))) |
1067 | ||
1068 | ;; Find multiline emphasis and put them into single line | |
1069 | (when (plist-get parameters :emph-multiline) | |
1070 | (goto-char (point-min)) | |
1071 | (while (re-search-forward org-emph-re nil t) | |
1072 | (if (not (= (char-after (match-beginning 3)) | |
1073 | (char-after (match-beginning 4)))) | |
1074 | (org-if-unprotected | |
1075 | (subst-char-in-region (match-beginning 0) (match-end 0) | |
1076 | ?\n ?\ t) | |
1077 | (goto-char (1- (match-end 0)))) | |
1078 | (goto-char (1+ (match-beginning 0)))))) | |
1079 | ||
1080 | (setq rtn (buffer-string))) | |
1081 | (kill-buffer " org-mode-tmp") | |
1082 | rtn)) | |
1083 | ||
72a81656 CD |
1084 | (defun org-export-latex-cleaned-string () |
1085 | "Clean stuff in the LaTeX export." | |
1086 | ||
0b8568f5 | 1087 | ;; Preserve line breaks |
72a81656 CD |
1088 | (goto-char (point-min)) |
1089 | (while (re-search-forward "\\\\\\\\" nil t) | |
1090 | (add-text-properties (match-beginning 0) (match-end 0) | |
1091 | '(org-protected t))) | |
1092 | ||
0b8568f5 | 1093 | ;; Convert LaTeX to @LaTeX{} |
72a81656 CD |
1094 | (goto-char (point-min)) |
1095 | (let ((case-fold-search nil) rpl) | |
1096 | (while (re-search-forward "\\([^+_]\\)LaTeX" nil t) | |
1097 | (replace-match (org-latex-protect | |
1098 | (concat (match-string 1) "\\LaTeX{}")) t t))) | |
1099 | ||
0b8568f5 | 1100 | ;; Convert horizontal rules |
72a81656 CD |
1101 | (goto-char (point-min)) |
1102 | (while (re-search-forward "^----+.$" nil t) | |
1103 | (replace-match (org-latex-protect "\\hrule") t t)) | |
1104 | ||
1105 | ;; Remove COMMENT subtrees | |
1106 | ;; What about QUOTE subtrees? | |
1107 | (goto-char (point-min)) | |
1108 | (while (re-search-forward | |
1109 | (concat "^\\*+ \\(" org-comment-string "\\)") | |
1110 | nil t) | |
1111 | (beginning-of-line) | |
1112 | (org-cut-subtree)) | |
0b8568f5 JW |
1113 | |
1114 | ;; Protect LaTeX \commands{...} | |
72a81656 | 1115 | (goto-char (point-min)) |
0b8568f5 | 1116 | (while (re-search-forward "\\\\[a-zA-Z]+\\(?:\\[.*\\]\\)?{.*}" nil t) |
72a81656 CD |
1117 | (add-text-properties (match-beginning 0) (match-end 0) |
1118 | '(org-protected t))) | |
1119 | ||
1120 | ;; Replace radio links | |
1121 | (goto-char (point-min)) | |
1122 | (let ((search (concat "<<<?" org-latex-all-targets-regexp ">?>>"))) | |
1123 | (while (re-search-forward search nil t) | |
1124 | (replace-match | |
1125 | (org-latex-protect (format "\\label{%s}" (match-string 1))) t t))) | |
1126 | ||
0b8568f5 | 1127 | ;; Delete @<...> constructs |
72a81656 | 1128 | (goto-char (point-min)) |
0b8568f5 JW |
1129 | ;; Thanks to Daniel Clemente for this regexp |
1130 | (while (re-search-forward "@<\\(?:[^\"\n]\\|\".*\"\\)*?>" nil t) | |
72a81656 CD |
1131 | (replace-match "")) |
1132 | ||
0b8568f5 | 1133 | ;; Add #+BEGIN_LaTeX before any \begin{...} |
72a81656 CD |
1134 | (goto-char (point-min)) |
1135 | (while (re-search-forward "^ *\\\\begin{" nil t) | |
1136 | (replace-match "#+BEGIN_LaTeX:\n\\&" t)) | |
1137 | ||
0b8568f5 | 1138 | ;; Add #+END_LaTeX after any \end{...} |
72a81656 CD |
1139 | (goto-char (point-min)) |
1140 | (while (re-search-forward "^ *\\\\end{.+}.*$" nil t) | |
1141 | (replace-match "\\&\n#+END_LaTeX" t)) | |
1142 | ||
72a81656 CD |
1143 | ;; Protect stuff from LaTeX processing. |
1144 | ;; We will get rid on this once org.el integrate org-export-latex.el | |
72a81656 CD |
1145 | (goto-char (point-min)) |
1146 | (let ((formatters `((,latexp "LaTeX" "BEGIN_LaTeX" "END_LaTeX"))) fmt) | |
1147 | (while (re-search-forward "^[ \t]*:.*\\(\n[ \t]*:.*\\)*" nil t) | |
1148 | (add-text-properties (match-beginning 0) (match-end 0) | |
1149 | '(org-protected t))) | |
1150 | (while formatters | |
1151 | (setq fmt (pop formatters)) | |
1152 | (when (car fmt) | |
1153 | (goto-char (point-min)) | |
1154 | (while (re-search-forward (concat "^#\\+" (cadr fmt) | |
0b8568f5 JW |
1155 | ;; ":[ \t]*\\(.*\\)") nil t) |
1156 | ;; FIXME: authorize spaces after #+LaTeX: | |
1157 | ;; to get list correctly exported | |
1158 | ":\\(.*\\)") nil t) | |
72a81656 CD |
1159 | (replace-match "\\1" t) |
1160 | (add-text-properties | |
1161 | (point-at-bol) (min (1+ (point-at-eol)) (point-max)) | |
1162 | '(org-protected t)))) | |
1163 | (goto-char (point-min)) | |
1164 | (while (re-search-forward | |
1165 | (concat "^#\\+" | |
1166 | (caddr fmt) "\\>.*\\(\\(\n.*\\)*?\n\\)#\\+" | |
1167 | (cadddr fmt) "\\>.*\n?") nil t) | |
1168 | (if (car fmt) | |
1169 | (add-text-properties (match-beginning 1) (1+ (match-end 1)) | |
1170 | '(org-protected t)) | |
1171 | (delete-region (match-beginning 0) (match-end 0)))) | |
1172 | (goto-char (point-min)) | |
1173 | (while (re-search-forward re-quote nil t) | |
1174 | (goto-char (match-beginning 0)) | |
1175 | (end-of-line 1) | |
1176 | (add-text-properties (point) (org-end-of-subtree t) | |
0b8568f5 JW |
1177 | '(org-protected t))))) |
1178 | ||
1179 | ;; Remove or replace comments | |
1180 | ;; If :comments is set, use this char for commenting out comments and | |
1181 | ;; protect them. otherwise delete them | |
1182 | (goto-char (point-min)) | |
1183 | (while (re-search-forward "^#\\(.*\n?\\)" nil t) | |
1184 | (if commentsp | |
1185 | (progn (add-text-properties | |
1186 | (match-beginning 0) (match-end 0) '(org-protected t)) | |
1187 | (replace-match (format commentsp (match-string 1)) t t)) | |
1188 | (replace-match ""))) | |
1189 | ||
1190 | ;; When converting to LaTeX, replace footnotes | |
1191 | ;; FIXME: don't protect footnotes from conversion | |
1192 | (when (plist-get org-latex-options-plist :footnotes) | |
1193 | (goto-char (point-min)) | |
1194 | (while (re-search-forward "\\[[0-9]+\\]" nil t) | |
1195 | (when (save-match-data | |
1196 | (save-excursion (beginning-of-line) | |
1197 | (looking-at "[^:|]"))) | |
1198 | (let ((foot-beg (match-beginning 0)) | |
1199 | (foot-end (match-end 0)) | |
1200 | (foot-prefix (match-string 0)) | |
1201 | footnote footnote-rpl) | |
1202 | (when (and (re-search-forward (regexp-quote foot-prefix) nil t)) | |
1203 | (replace-match "") | |
1204 | (let ((end (save-excursion | |
1205 | (if (re-search-forward "^$\\|\\[[0-9]+\\]" nil t) | |
1206 | (match-beginning 0) (point-max))))) | |
1207 | (setq footnote | |
1208 | (concat | |
1209 | (org-trim (buffer-substring (point) end)) | |
1210 | ;; FIXME stupid workaround for cases where | |
1211 | ;; `org-bracket-link-analytic-regexp' matches | |
1212 | ;; }. as part of the link. | |
1213 | " ")) | |
1214 | (delete-region (point) end))) | |
1215 | (goto-char foot-beg) | |
1216 | (delete-region foot-beg foot-end) | |
1217 | (setq footnote-rpl (format "\\footnote{%s}" footnote)) | |
1218 | (add-text-properties 0 10 '(org-protected t) footnote-rpl) | |
1219 | (add-text-properties (1- (length footnote-rpl)) | |
1220 | (length footnote-rpl) | |
1221 | '(org-protected t) footnote-rpl) | |
1222 | (insert footnote-rpl)))) | |
1223 | ||
1224 | ;; Replace footnote section tag for LaTeX | |
1225 | (goto-char (point-min)) | |
1226 | (while (re-search-forward | |
1227 | (concat "^" footnote-section-tag-regexp) nil t) | |
1228 | (replace-match "")))) | |
72a81656 CD |
1229 | |
1230 | (provide 'org-export-latex) | |
1231 | ||
1232 | ;;; org-export-latex.el ends here |