From mboxrd@z Thu Jan 1 00:00:00 1970 From: Eric Schulte Subject: Org-mode extensions used to publish a dissertation Date: Mon, 04 Aug 2014 20:23:58 -0400 Message-ID: <87y4v3yeap.fsf@gmail.com> Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Return-path: Received: from eggs.gnu.org ([2001:4830:134:3::10]:35939) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1XESY9-0002CP-3A for emacs-orgmode@gnu.org; Mon, 04 Aug 2014 20:24:25 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1XESY4-0008Fk-9q for emacs-orgmode@gnu.org; Mon, 04 Aug 2014 20:24:21 -0400 Received: from mail-oi0-x234.google.com ([2607:f8b0:4003:c06::234]:52717) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1XESY3-0008Fe-OX for emacs-orgmode@gnu.org; Mon, 04 Aug 2014 20:24:16 -0400 Received: by mail-oi0-f52.google.com with SMTP id h136so135685oig.11 for ; Mon, 04 Aug 2014 17:24:14 -0700 (PDT) Received: from bagel (ip98-169-80-201.dc.dc.cox.net. [98.169.80.201]) by mx.google.com with ESMTPSA id ca1sm186262oec.16.2014.08.04.17.24.11 for (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 04 Aug 2014 17:24:12 -0700 (PDT) List-Id: "General discussions about Org-mode." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-orgmode-bounces+geo-emacs-orgmode=m.gmane.org@gnu.org Sender: emacs-orgmode-bounces+geo-emacs-orgmode=m.gmane.org@gnu.org To: Org Mode Mailing List --=-=-= Content-Type: text/plain Hi List, I thoroughly enjoyed using Org-mode to write my dissertation. I was happy to be able to export (mostly) equivalent versions of the document to HTML and PDF. I'd recommend using Org-mode for such a complex writing task to those who are either willing to hack the exporter, or are willing to accept an Org-mode document with inline LaTeX which only /really/ works with the LaTeX backend. As I fall in the former category, here are the small extensions to the Org-mode exporter which I found necessary. Thanks to the new exporting backend they were uniformly easy to implement. They are included in the attached elisp file. Each pagefeed (^L) in the file marks a new section of functionality, the sections are as follows... 1. Ignore Headlines and keep content (discussed here recently) 2. Multi-column Table Cells 3. Wide tables extend into the margins. 4. Wide tables squeezed within the margins 5. "sc" links for the \sc{} latex command 6. "gls" links for the \gls{} family of Glossary commands 7. color links 8. TIKZ figure links 9. Tie certain latex commands to the preceding word. 10. Fix emphasis in text export A simplified version of my Makefile is also attached. I hope someone finds this useful. Best, Eric --=-=-= Content-Type: application/emacs-lisp Content-Disposition: attachment; filename=init.el Content-Transfer-Encoding: quoted-printable (defvar org-mode-base "~/.emacs.d/src/org-mode/" "The directory in which Org-mode is installed.") (add-to-list 'load-path (expand-file-name "lisp" org-mode-base)) (add-to-list 'load-path (expand-file-name "lisp" (expand-file-name "contrib" org-mode-base))) (mapc #'require '(org cl ob-latex ox-html ox-latex bibtex ox-bibtex)) (setq org-latex-table-scientific-notation "$%s\\times10^{%s}$" org-latex-listings t org-confirm-babel-evaluate nil org-latex-table-caption-above nil org-babel-latex-htlatex "htlatex" org-latex-custom-id-as-label t) (add-to-list 'org-latex-classes `("org-paper" ,(with-temp-buffer (insert-file-literally "org-paper-class.tex") (buffer-string)) ("\\chapter{%s}" . "\\chapter*{%s}") ("\\section{%s}" . "\\section*{%s}") ("\\subsection{%s}" . "\\subsection*{%s}") ("\\subsubsection{%s}" . "\\subsubsection*{%s}") ("\\paragraph{%s}" . "\\paragraph*{%s}"))) ;;; Ignore Headlines ;;=20 ;; During export headlines which have the "ignore" tag are removed ;; from the parse tree. Their contents are retained (leading to a ;; possibly invalid parse tree, which nevertheless appears to function ;; correctly with most export backends) all children headlines are ;; retained and are promoted to the level of the ignored parent ;; headline. ;; ;; This makes it possible to add structure to the original Org-mode ;; document which does not effect the exported version, such as in the ;; following examples. ;; ;; Wrapping an abstract in a headline ;; ;; * Abstract :ignore: ;; #+LaTeX: \begin{abstract} ;; #+HTML:
;; ;; ... ;; ;; #+HTML:
;; #+LaTeX: \end{abstract} ;; ;; Placing References under a headline (using ox-bibtex in contrib) ;; ;; * References :ignore: ;; #+BIBLIOGRAPHY: org-paper plain ;; ;; Inserting an appendix for LaTeX using the appendix package. ;; ;; * Appendix :ignore: ;; #+LaTeX: \begin{appendices} ;; ** Reproduction ;; ... ;; ** Definitions ;; #+LaTeX: \end{appendices} ;; (defun org-export-ignore-headlines (data backend info) "Remove headlines tagged \"ignore\" retaining contents and promoting chil= dren. Each headline tagged \"ignore\" will be removed retaining its contents and promoting any children headlines to the level of the parent." (org-element-map data 'headline (lambda (object) (when (member "ignore" (org-element-property :tags object)) (let ((level-top (org-element-property :level object)) level-diff) (mapc (lambda (el) ;; recursively promote all nested headlines (org-element-map el 'headline (lambda (el) (when (equal 'headline (org-element-type el)) (unless level-diff (setq level-diff (- (org-element-property :level = el) level-top))) (org-element-put-property el :level (- (org-element-property :level el) level-diff))))) ;; insert back into parse tree (org-element-insert-before el object)) (org-element-contents object))) (org-element-extract-element object))) info nil) data) (add-hook 'org-export-filter-parse-tree-functions 'org-export-ignore-headli= nes) ;;; Multi-column Table Cells ;; ;; Export table cells with multiple columns using Latex-like syntax. ;; For example in the following the "<3colc>Backends" cell spans 3 ;; columns with it's text centered. ;; ;; | | <3colc>Backends | | | ;; | | LaTeX | HTML | Text | ;; |-----------+-----------------+-------+-------| ;; | extension | .tex | .html | .txt | ;; ;; The `org-export-multicolumn-filter-latex' function is taken from ;; the following. ;; http://thread.gmane.org/gmane.emacs.orgmode/66332/match=3Dlatex+table+mu= lticolumn+cell (defun org-export-multicolumn-filter (row backend info) (cond ((org-export-derived-backend-p backend 'latex) (org-export-multicolumn-filter-latex row backend info)) ((org-export-derived-backend-p backend 'html) (org-export-multicolumn-filter-html row backend info)))) (defun org-export-multicolumn-filter-latex (row backend info) (while (string-match "\\(<\\([0-9]+\\)col\\([lrc]\\)?>[[:blank:]]*\\([^&]+\\)\\)" row) (let ((columns (string-to-number (match-string 2 row))) (start (match-end 0)) (contents (replace-regexp-in-string "\\\\" "\\\\\\\\" (replace-regexp-in-string "[[:blank:]]*$" "" (match-string 4 row)))) (algn (or (match-string 3 row) "l"))) (setq row (replace-match (format "\\\\multicolumn{%d}{%s}{%s}" columns algn content= s) nil nil row 1)) (while (and (> columns 1) (string-match "&" row start)) (setq row (replace-match "" nil nil row)) (decf columns)))) row) (defun org-export-multicolumn-filter-html (row backend info) (while (string-match "class=3D\".*\" *><\\([0-9]+\\)col\\([lrc]\\)?>= ;" row) (let ((columns (string-to-number (match-string 1 row))) (start (match-end 0)) (algn (case (intern (or (match-string 2 row) "l")) (c "center") (r "right") (l "left")))) (setq row (replace-match (format " class=3D\"%s\" colspan=3D\"%s\">" algn columns) nil nil row)) (while (and (> columns 1) (string-match " " row start)) (setq row (replace-match "" nil nil row)) (decf columns)))) row) (add-to-list 'org-export-filter-table-row-functions 'org-export-multicolumn-filter) ;;; Wide tables extend into the margins (defun my-latex-wide-table-filter (table backend info) "Transform \"wide\" environment tables to push into the margins of the pa= ge. This uses the \"changepage\" latex package. To make a table wide, specify \":environment wide\" in the table's #+ATTR_LaTeX line." (when (org-export-derived-backend-p backend 'latex) (let ((width-diff "-2.5cm") (table-env "tabular")) ;; (3) replace end markers (replace-regexp-in-string "^\\\\end\{wide\}" (format "\\\\end{%s}\n\\\\end{adjustwidth}\n" table-env) ;; (2) replace non-centered beginning markers (replace-regexp-in-string "^\\\\begin\{wide\}" (format "\\\\begin{adjustwidth}{%s}{%s}\n\\\\begin{%s}" width-diff width-diff table-env) ;; (1) replace centered beginning markers (replace-regexp-in-string "\\\\centering[\n\r]\\\\begin\{wide\}" (format "\\\\begin{adjustwidth}{%s}{%s}\n\\\\centering\n\\\\begin\{%s\}" width-diff width-diff table-env) table)))))) (add-to-list 'org-export-filter-table-functions 'my-latex-wide-table-filter) ;;; Wide tables squeezed within the margins ;; ;; Uses the "adjustbox" LaTeX package. (defun my-latex-squeeze-table-filter (table backend info) "Transform \"squeeze\" environment tables to fit between margins. This uses the \"adjustbox\" latex package. To squeeze a table, specify \":environment squeeze\" in the table's #+ATTR_LaTeX line." (when (org-export-derived-backend-p backend 'latex) (let ((table-env "tabular")) ;; (3) replace end markers (replace-regexp-in-string "^\\\\end\{squeeze\}" (format "\\\\end{%s}}\n" table-env) ;; (2) replace non-centered beginning markers (replace-regexp-in-string "^\\\\begin\{squeeze\}" ;; \adjustbox{{width=3D0.75\textwidth}} (format "\\\\adjustbox{{width=3D\\\\textwidth}}{\n\\\\begin{%s}" table-env) ;; (1) replace centered beginning markers (replace-regexp-in-string "\\\\centering[\n\r]\\\\begin\{squeeze\}" (format "\\\\adjustbox{{width=3D\\\\textwidth}}{\\\\centering\n\\\\begin\= {%s\}" table-env) table)))))) (add-to-list 'org-export-filter-table-functions 'my-latex-squeeze-table-filter) ;;; "sc" links for the \sc{} latex command (defun org-export-html-small-caps (string backend channel) (when (org-export-derived-backend-p backend 'html) (let ((rx "{\\\\sc ") (fmt "%s")) (with-temp-buffer (insert string) (goto-char (point-min)) (while (re-search-forward rx nil t) (let* ((start (match-beginning 0)) (end (progn (goto-char start) (forward-sexp) (point))) (content (buffer-substring (+ start 5) (- end 1)))) (delete-region start end) (goto-char start) (insert (format fmt content)))) (buffer-string))))) (add-to-list 'org-export-filter-final-output-functions 'org-export-html-small-caps) (defun org-export-latex-sc (tree backend info) "Handle sc: links for latex export." (org-element-map tree 'link (lambda (object) (when (equal (org-element-property :type object) "sc") (org-element-insert-before (cond ((org-export-derived-backend-p backend 'latex) (list 'latex-fragment (list :value (format "{\\sc %s}" (org-element-property :path object)) :post-blank (org-element-property :post-blank object)))) ((org-export-derived-backend-p backend 'html) (list 'export-snippet (list :back-end "html" :value (format "%s" (org-element-property :path object)) :post-blank (org-element-property :post-blank object)))) (:otherwise (error "unsupported backend for `org-export-latex-sc'"))) object) (org-element-extract-element object)))) tree) (org-add-link-type "sc") (add-hook 'org-export-filter-parse-tree-functions 'org-export-latex-sc) ;;; "gls" links for the \gls{} family of Glossary commands (defvar org-export-gls-types '("gls" "glspl" "Gls" "Glspl")) (defun org-export-gls-parse-glossaryentry (string) (with-temp-buffer (insert ",\n") (insert string) (goto-char (point-min)) (cl-remove nil (mapcar (lambda (f) (let ((text (bibtex-text-in-field-bounds (bibtex-search-forward-field f) t))) (when text (cons f text)))) (list "name" "description"))))) (defun org-export-gls-parse-glossary (file) (with-temp-buffer (insert-file-contents-literally file) (goto-char (point-min)) (loop while (re-search-forward "\\newglossaryentry" nil t) collect (save-match-data (cons (buffer-substring (1+ (point)) (progn (forward-sexp) (1- (point)))) (org-export-gls-parse-glossaryentry (buffer-substring (progn (forward-sexp) (backward-sexp) (1+ (point))) (progn (forward-sexp) (1- (point)))))))))) (defun org-export-gls-parse-acronym (file) (with-temp-buffer (insert-file-contents-literally file) (goto-char (point-min)) (loop while (re-search-forward "\\newacronym" nil t) collect (save-match-data (cons (buffer-substring (1+ (point)) (progn (forward-sexp) (1- (point)))) (list (cons "name" (buffer-substring (1+ (point)) (progn (forward-sexp) (1- (point= ))))) (cons "description" (buffer-substring (1+ (point)) (progn (forward-sexp) (1- (point))))))))))) (defvar org-export-gls-glossary (append (org-export-gls-parse-glossary glossary-file) (org-export-gls-parse-acronym glossary-file))) (defun org-export-gls (tree backend info) "Handle gls: links for latex export." (cond ;; latex export is straightforward ((org-export-derived-backend-p backend 'latex) (org-element-map tree 'link (lambda (object) (when (member (org-element-property :type object) org-export-gls-types) (org-element-insert-before (list 'latex-fragment (list :value (format "\\%s{%s}" (org-element-property :type object) (org-element-property :path object)) :post-blank (org-element-property :post-blank object))) object) (org-element-extract-element object))))) ;; HTML export requires some actual work, and a glossary ((and (org-export-derived-backend-p backend 'html) org-export-gls-glossary) (org-element-map tree 'link (lambda (object) (let* ((type (org-element-property :type object)) (proc (cond=20 ((string=3D type "gls") #'identity) ((string=3D type "glspl") (lambda (name) (concat name "s"))) ((string=3D type "Gls") (lambda (name) (let ((chars (string-to-list name))) (apply #'string (cons (capitalize (car chars)) (cdr chars)))))) ((string=3D type "Glspl") (lambda (name) (let ((chars (string-to-list (concat name "s")))) (apply #'string (cons (capitalize (car chars)) (cdr chars))))))))) (when (member type org-export-gls-types) (let ((entry (cdr (assoc (org-element-property :path object) org-export-gls-glossary)))) (unless entry (error "missing glossary entry for %S" (org-element-property :path object))) (org-element-insert-before (list 'export-snippet (list :back-end "html" :value (format "%s" (cdr (assoc "description" entry)) (funcall proc (cdr (assoc "name" entry))= )) :post-blank (org-element-property :post-blank object))) object)) (org-element-extract-element object))))))) tree) (mapc #'org-add-link-type org-export-gls-types) (add-hook 'org-export-filter-parse-tree-functions 'org-export-gls) ;;; color links (defun org-export-latex-color (tree backend info) "Handle color links for latex export." (org-element-map tree 'link (lambda (object) (when (equal (org-element-property :type object) "color") (org-element-insert-before (cond ((org-export-derived-backend-p backend 'latex) (list 'latex-fragment (list :value (format "{\\color{%s}%s}" (org-element-property :path object) (car (org-element-contents object))) :post-blank (org-element-property :post-blank object)))) ((org-export-derived-backend-p backend 'html) (list 'export-snippet (list :back-end "html" :value (format "%s" (org-element-property :path object) (car (org-element-contents object))) :post-blank (org-element-property :post-blank object))))) =20=20=20=20=20=20=20=20=20 object) (org-element-extract-element object)))) tree) (org-add-link-type "color") (add-hook 'org-export-filter-parse-tree-functions 'org-export-latex-color) ;;; TIKZ figure links (defun org-export-tikz-figure (tree backend info) "Convert included tikz files into figures." (org-element-map tree 'keyword (lambda (object) (when (equal (org-element-property :key object) "TIKZ_FIGURE") (org-element-insert-before (cond ;; for latex just \input the file ((org-export-derived-backend-p backend 'latex) (let* ((adjustbox (org-export-read-attribute :attr_latex object = :adjustbox)) (preamble (if adjustbox (format "\\adjustbox{{%s}}{" adjustbox) "")) (postamble (if adjustbox "}" ""))) (list 'latex-environment (list :value (format "\\begin{figure} \\centering %s \\input{%s} %s \\caption%s{\\label{%s}%s} \\end{figure}\n\n" preamble (org-element-property :value object) postamble (let ((it (car (org-export-get-caption object t= )))) (if it (format "[%s]" it) "")) (org-element-property :name object) (let ((main (org-export-get-caption object))) (mapconcat=20 (lambda (it) (concat (if (stringp it) it (org-element-property :value it)) (make-string (or (org-element-property :post-blank it= ) 0) 32))) main ""))) :name (org-element-property :name object) :post-blank (org-element-property :post-blank object)))= )) ((org-export-derived-backend-p backend 'html) (let ((base (org-element-property :value object))) ;; To support the html files sometimes ;; created by htlatex. (if (file-exists-p (concat base ".html")) (progn (error "exporting HTML inline not supported") (list 'export-block (list :name (org-element-property :name object) :caption (org-element-property :caption obje= ct) :begin (org-element-property :begin object) :type "HTML" :value (with-temp-buffer (insert-file-contents-literally (co= ncat base ".html")) (buffer-string))))) (list 'paragraph (list :name (org-element-property :name object) :caption (org-element-property :caption object) :begin (org-element-property :begin object)) (list 'link (list :type "file" :path (cond ;; Try png first because we sometimes ;; use it when svgs are created but ;; are inferior. ((file-exists-p (concat base ".png")) (concat base ".png")) ((file-exists-p (concat base ".svg")) (concat base ".svg")) (:otherwise (warn "no file exists for %s" base) base)) :post-blank (org-element-property :post-blank object)))))))) object) (org-element-extract-element object)))) tree) (add-hook 'org-export-filter-parse-tree-functions 'org-export-tikz-figure) (setq org-bibtex-sticky-commands (list "cite" "ref")) ;;; Tie certain latex commands to the preceding word. (defvar org-export-latex-tied-commands (list "cite" "ref") "List of LaTeX commands tied by `org-export-latex-tie-refs'.") (defun org-export-latex-tie-refs (string backend channel) "Tie all citations and refs to the preceding non-whitespace character. Activate by adding the following to your config. (add-to-list 'org-export-filter-final-output-functions 'org-export-latex-tie-refs) Customize `org-export-latex-tied-commands' to control which latex commands are tied." =20=20 (let ((start 0) (rx (format "\\([^[:space:]\n\r]\\)\\([[:space:]\n\r]\+\\)\\\\%s" (regexp-opt org-export-latex-tied-commands)))) (while (string-match rx string start) (setq start (+ (match-end 0) (- 1 (- (match-end 2) (match-beginning 2= ))))) (setq string (replace-match "~" nil 'literal string 2)))) string) (add-to-list 'org-export-filter-final-output-functions 'org-export-latex-tie-refs) ;;; Fix emphasis in text export ;; ;; Replace /foo/ with 'foo' for text export. ;; (defun org-export-ascii-emph-to-quote (string backend channel) "Covert emphasis slashes into quotes for text export." (when (org-export-derived-backend-p backend 'ascii) (let ((start 0) (rx "\/\\([^.\/,]\+\\)\/\\([[:space:]\n\r.,]\\)")) (while (string-match rx string start) ;; Quotes outside of punctuation. (let ((replace (format (if (member (match-string 2 string) '("," ".")) "'%s%s'" "'%s'%s") (match-string 1 string) (match-string 2 string)))) (setq string (replace-match replace nil 'literal string))) (setq start (match-end 0))))) string) (add-to-list 'org-export-filter-final-output-functions 'org-export-ascii-emph-to-quote) --=-=-= Content-Type: application/octet-stream Content-Disposition: attachment; filename=Makefile Content-Transfer-Encoding: base64 RU1BQ1M9ZW1hY3MKQkFUQ0hfRU1BQ1M9JChFTUFDUykgLS1iYXRjaCAtUSAtbCBpbml0LmVsCgpQ QVBFUjo9cmVwbGFjZS13aXRoLXBhcGVyLWZpbGUtbmFtZQoKYWxsOiAkKFBBUEVSKS5wZGYgJChG SUdfU1ZHKQouUEhPTlk6IGFsbCBwdWJsaXNoIGNsZWFuCgojIEV4cG9ydAolLmh0bWw6ICUub3Jn IGluaXQuZWwKCSQoQkFUQ0hfRU1BQ1MpICQqLm9yZyAtZiBvcmctaHRtbC1leHBvcnQtdG8taHRt bAoKJS50ZXg6ICUub3JnIGluaXQuZWwgb3JnLXBhcGVyLWNsYXNzLnRleAoJJChCQVRDSF9FTUFD UykgJCoub3JnIC1mIG9yZy1sYXRleC1leHBvcnQtdG8tbGF0ZXgKCiUudHh0OiAlLm9yZyBpbml0 LmVsCgkkKEJBVENIX0VNQUNTKSAkKi5vcmcgLWYgb3JnLWV4cG9ydC1hcy11dGY4CgolLnBkZjog JS50ZXggJChGSUdfVEVYKQoJcm0gLWYgZGlzc2VydGF0aW9uLmF1eAoJcGRmbGF0ZXggXFxub25z dG9wbW9kZVxcaW5wdXQgJCoudGV4CiMgVW5jb21tZW50IGlmIHVzaW5nIEJpYnRleAojIGlmIFsg LWYgJCouYmliIF07dGhlbiBiaWJ0ZXggLS1taW4tY3Jvc3NyZWY9MTAwICQqO2ZpCgl3aGlsZSBn cmVwICJSZXJ1biB0byBnZXQiICQqLmxvZzsgZG8gXAoJCXBkZmxhdGV4IFxcbm9uc3RvcG1vZGVc XGlucHV0ICQqLnRleDsgXAoJZG9uZQojIFVuY29tbWVudCBpZiB1c2luZyBhIEdsb3NzYXJ5CiMg aWYgWyAtZiBnbG9zc2FyeS50ZXggXTt0aGVuIG1ha2VnbG9zc2FyaWVzICQqO2ZpCglwZGZsYXRl eCBcXG5vbnN0b3Btb2RlXFxpbnB1dCAkKi50ZXg7CgolLnBzOiAlLnBkZgoJcGRmMnBzICQqLnBk ZgoKJS5zdmc6ICUudGV4CgkuL2Jpbi90ZXgyc3ZnICQ8CgojIFB1Ymxpc2gKcHVibGlzaC9pbmRl eC5odG1sOiAkKFBBUEVSKS5odG1sCgljcCAkPCAkQAoKcHVibGlzaC8lOiAlCgljcCAkPCAkQAoK cHVibGlzaDogcHVibGlzaC9pbmRleC5odG1sIHB1Ymxpc2gvJChQQVBFUikucGRmCgljaG1vZCBh K3IgJDwKCmNsZWFuOgoJcm0gLWYgKn4gKi5hdXggKi5sb2cgKi5kdmkgKi5ibGcgKi5iYmwgKi50 b2MgKi5vdXQgKi5odG1sICoudnJiICouc25tICoubmF2ICoucHMgKi5sb2YgKi5sb2wgKi5sb3Qg Ki5nbGcgKi5nbG8gKi5nbHMgKi5nbHNkZWZzICoueGR5CglybSAtZiAkKFBBUEVSKS50ZXggJChQ QVBFUikucGRmICQoUEFQRVIpLmh0bWwgJChQQVBFUikudHh0IHB1Ymxpc2gvaW5kZXguaHRtbCBw dWJsaXNoLyQoUEFQRVIpLnBkZgo= --=-=-= Content-Type: text/plain -- Eric Schulte https://cs.unm.edu/~eschulte PGP: 0x614CA05D (see https://u.fsf.org/yw) --=-=-=--