From b6176f61b6304083f50b8e636a84a06254d42f32 Mon Sep 17 00:00:00 2001 From: Kaushal Modi Date: Wed, 12 Jul 2017 19:26:51 -0400 Subject: [PATCH] Make Org table exports work; Add examples --- example-site/content-org/all-posts.org | 58 ++++++++ example-site/content/posts/simple-table.md | 11 ++ .../content/posts/table-bottom-border.md | 11 ++ .../posts/table-only-rule-after-first-row.md | 11 ++ .../posts/table-top-and-bottom-border.md | 11 ++ .../content/posts/table-top-border.md | 11 ++ ...table-with-borders-and-rule-after-first.md | 11 ++ .../posts/table-with-narrowest-columns.md | 11 ++ .../content/posts/table-with-rules.md | 13 ++ ox-blackfriday.el | 130 ++++++++++-------- 10 files changed, 223 insertions(+), 55 deletions(-) create mode 100644 example-site/content/posts/simple-table.md create mode 100644 example-site/content/posts/table-bottom-border.md create mode 100644 example-site/content/posts/table-only-rule-after-first-row.md create mode 100644 example-site/content/posts/table-top-and-bottom-border.md create mode 100644 example-site/content/posts/table-top-border.md create mode 100644 example-site/content/posts/table-with-borders-and-rule-after-first.md create mode 100644 example-site/content/posts/table-with-narrowest-columns.md create mode 100644 example-site/content/posts/table-with-rules.md diff --git a/example-site/content-org/all-posts.org b/example-site/content-org/all-posts.org index 9b4dcf5..e196cd1 100644 --- a/example-site/content-org/all-posts.org +++ b/example-site/content-org/all-posts.org @@ -141,6 +141,64 @@ Here is the first post on Emacs. :EXPORT_FILE_NAME: emacs-post-2 :END: Here is the second post on Emacs. +* Tables :table: +** Simple Table +:PROPERTIES: +:EXPORT_FILE_NAME: simple-table +:END: +| h1 | h2 | +| a | b | +| c | d | +** Table with narrowest cols +:PROPERTIES: +:EXPORT_FILE_NAME: table-with-narrowest-columns +:END: +| 1 | 2 | 3 | +| a | b | e | +| c | d | f | +** Table with top border +:PROPERTIES: +:EXPORT_FILE_NAME: table-top-border +:END: +|---+---+---+---| +| 1 | 2 | 3 | 4 | +| a | b | e | g | +| c | d | f | h | +** Table with bottom border +:PROPERTIES: +:EXPORT_FILE_NAME: table-bottom-border +:END: +| 1 | 2 | 3 | 4 | +| a | b | e | g | +| c | d | f | h | +|---+---+---+---| +** Table with top and bottom border +:PROPERTIES: +:EXPORT_FILE_NAME: table-top-and-bottom-border +:END: +|---+---+---+---| +| 1 | 2 | 3 | 4 | +| a | b | e | g | +| c | d | f | h | +|---+---+---+---| +** Table with rule after first row +:PROPERTIES: +:EXPORT_FILE_NAME: table-only-rule-after-first-row +:END: +| 1 | 2 | 3 | 4 | +|---+---+---+---| +| a | b | e | g | +| c | d | f | h | +** Table with borders and rule after first +:PROPERTIES: +:EXPORT_FILE_NAME: table-with-borders-and-rule-after-first +:END: +|---+---+---+---| +| 1 | 2 | 3 | 4 | +|---+---+---+---| +| a | b | e | g | +| c | d | f | h | +|---+---+---+---| * TODO Pre-Draft State :PROPERTIES: :EXPORT_FILE_NAME: draft-state-todo diff --git a/example-site/content/posts/simple-table.md b/example-site/content/posts/simple-table.md new file mode 100644 index 0000000..ee2db19 --- /dev/null +++ b/example-site/content/posts/simple-table.md @@ -0,0 +1,11 @@ ++++ +title = "Simple Table" +date = 2017-07-12T19:25:56-04:00 +tags = ["table"] +draft = false ++++ + +h1 | h2 +---|---- +a | b +c | d diff --git a/example-site/content/posts/table-bottom-border.md b/example-site/content/posts/table-bottom-border.md new file mode 100644 index 0000000..a1b89e8 --- /dev/null +++ b/example-site/content/posts/table-bottom-border.md @@ -0,0 +1,11 @@ ++++ +title = "Table with bottom border" +date = 2017-07-12T19:26:06-04:00 +tags = ["table"] +draft = false ++++ + +1 | 2 | 3 | 4 +---|---|---|---- + a | b | e | g + c | d | f | h diff --git a/example-site/content/posts/table-only-rule-after-first-row.md b/example-site/content/posts/table-only-rule-after-first-row.md new file mode 100644 index 0000000..44d966b --- /dev/null +++ b/example-site/content/posts/table-only-rule-after-first-row.md @@ -0,0 +1,11 @@ ++++ +title = "Table with rule after first row" +date = 2017-07-12T19:26:13-04:00 +tags = ["table"] +draft = false ++++ + +1 | 2 | 3 | 4 +---|---|---|---- + a | b | e | g + c | d | f | h diff --git a/example-site/content/posts/table-top-and-bottom-border.md b/example-site/content/posts/table-top-and-bottom-border.md new file mode 100644 index 0000000..bd0b9dc --- /dev/null +++ b/example-site/content/posts/table-top-and-bottom-border.md @@ -0,0 +1,11 @@ ++++ +title = "Table with top and bottom border" +date = 2017-07-12T19:26:10-04:00 +tags = ["table"] +draft = false ++++ + +1 | 2 | 3 | 4 +---|---|---|---- + a | b | e | g + c | d | f | h diff --git a/example-site/content/posts/table-top-border.md b/example-site/content/posts/table-top-border.md new file mode 100644 index 0000000..46c584b --- /dev/null +++ b/example-site/content/posts/table-top-border.md @@ -0,0 +1,11 @@ ++++ +title = "Table with top border" +date = 2017-07-12T19:26:04-04:00 +tags = ["table"] +draft = false ++++ + +1 | 2 | 3 | 4 +---|---|---|---- + a | b | e | g + c | d | f | h diff --git a/example-site/content/posts/table-with-borders-and-rule-after-first.md b/example-site/content/posts/table-with-borders-and-rule-after-first.md new file mode 100644 index 0000000..ea8cf96 --- /dev/null +++ b/example-site/content/posts/table-with-borders-and-rule-after-first.md @@ -0,0 +1,11 @@ ++++ +title = "Table with borders and rule after first" +date = 2017-07-12T19:26:16-04:00 +tags = ["table"] +draft = false ++++ + +1 | 2 | 3 | 4 +---|---|---|---- + a | b | e | g + c | d | f | h diff --git a/example-site/content/posts/table-with-narrowest-columns.md b/example-site/content/posts/table-with-narrowest-columns.md new file mode 100644 index 0000000..0646b31 --- /dev/null +++ b/example-site/content/posts/table-with-narrowest-columns.md @@ -0,0 +1,11 @@ ++++ +title = "Table with narrowest cols" +date = 2017-07-12T19:26:00-04:00 +tags = ["table"] +draft = false ++++ + +1 | 2 | 3 +---|---|---- + a | b | e + c | d | f diff --git a/example-site/content/posts/table-with-rules.md b/example-site/content/posts/table-with-rules.md new file mode 100644 index 0000000..ef844d6 --- /dev/null +++ b/example-site/content/posts/table-with-rules.md @@ -0,0 +1,13 @@ ++++ +title = "Table with rules" +date = 2017-07-12T18:52:43-04:00 +tags = ["table"] +draft = false ++++ + +The rules don’t matter in Markdown export. + + 1 | 2 | 3 +---|---|---- + a | b | e + c | d | f diff --git a/ox-blackfriday.el b/ox-blackfriday.el index 61d56f4..ad1d463 100644 --- a/ox-blackfriday.el +++ b/ox-blackfriday.el @@ -34,6 +34,14 @@ (require 'ox-md) (require 'ox-publish) + +(defvar width-cookies nil) +(defvar width-cookies-table nil) + +(defconst blackfriday-table-left-border "") +(defconst blackfriday-table-right-border " ") +(defconst blackfriday-table-separator "| ") + ;;; User-Configurable Variables @@ -70,7 +78,6 @@ ;;; Transcode Functions ;;;; Paragraph - (defun org-blackfriday-paragraph (paragraph contents info) "Transcode PARAGRAPH element into Blackfriday Markdown format. CONTENTS is the paragraph contents. INFO is a plist used as a @@ -83,9 +90,7 @@ communication channel." (replace-regexp-in-string "\\`#" "\\#" contents nil t) contents))) - ;;;; Src Block - (defun org-blackfriday-src-block (src-block _contents info) "Transcode SRC-BLOCK element into Blackfriday Markdown format. @@ -96,24 +101,13 @@ INFO is a plist used as a communication channel." (suffix "```")) (concat prefix code suffix))) - ;;;; Strike-Through - (defun org-blackfriday-strike-through (_strike-through contents _info) "Transcode strike-through text from Org to Blackfriday Markdown. CONTENTS contains the text with strike-through markup." (format "~~%s~~" contents)) - ;;;; Table-Common - -(defvar width-cookies nil) -(defvar width-cookies-table nil) - -(defconst blackfriday-table-left-border "") -(defconst blackfriday-table-right-border " ") -(defconst blackfriday-table-separator "| ") - (defun org-blackfriday-table-col-width (table column info) "Return width of TABLE at given COLUMN using INFO. @@ -140,7 +134,8 @@ in the column, or by the maximum cell with in the column." (max (length (org-export-data (org-element-contents - (elt (if specialp (car (org-element-contents row)) + (elt (if specialp + (car (org-element-contents row)) (org-element-contents row)) column)) info)) @@ -148,7 +143,6 @@ in the column, or by the maximum cell with in the column." info) (puthash column max-width width-cookies)))))) - (defun org-blackfriday-make-hline-builder (table info char) "Return a function to horizontal lines in TABLE. Draw the lines using INFO with given CHAR. @@ -160,61 +154,86 @@ INFO is a plist used as a communication channel." (setq max-width 1)) (make-string max-width ,char)))) - ;;;; Table-Cell - (defun org-blackfriday-table-cell (table-cell contents info) "Transcode TABLE-CELL element from Org into Blackfriday. CONTENTS is content of the cell. INFO is a plist used as a communication channel." + (message "[ox-bf-table-cell DBG] In contents: %s" contents) (let* ((table (org-export-get-parent-table table-cell)) (column (cdr (org-export-table-cell-address table-cell info))) (width (org-blackfriday-table-col-width table column info)) (left-border (if (org-export-table-cell-starts-colgroup-p table-cell info) "" " ")) (right-border (if (org-export-table-cell-ends-colgroup-p table-cell info) "" " |")) - (data (or contents ""))) - (setq contents - (concat data - (make-string (max 0 (- width (string-width data))) - ?\s))) - (concat left-border contents right-border))) - + (data (or contents "")) + (cell (concat left-border + data + (make-string (max 0 (- width (string-width data))) ?\s) + right-border)) + (cell-width (length cell))) + ;; Each cell needs to be at least 3 characters wide (4 chars, + ;; including the table border char "|"); otherwise the export + ;; is not rendered as a table + (when (< cell-width 4) + (setq cell (concat (make-string (- 4 cell-width) ? ) cell))) + (message "[ox-bf-table-cell DBG] Cell:\n%s" cell) + cell)) ;;;; Table-Row +(defvar org-blackfriday--hrule-inserted nil + "State variable to keep track if the horizontal rule after +first row is already inserted.") (defun org-blackfriday-table-row (table-row contents info) "Transcode TABLE-ROW element from Org into Blackfriday. CONTENTS is cell contents of TABLE-ROW. INFO is a plist used as a communication channel." - (let ((table (org-export-get-parent-table table-row))) - (when (and (eq 'rule (org-element-property :type table-row)) - ;; In Blackfriday, rule is valid only at second row. - (eq 1 (cl-position - table-row - (org-element-map table 'table-row 'identity info)))) - (let* ((table (org-export-get-parent-table table-row)) - ;; (headerp (org-export-table-row-starts-header-p table-row info)) - (build-rule (org-blackfriday-make-hline-builder table info ?-)) - (cols (cdr (org-export-table-dimensions table info)))) - (setq contents - (concat blackfriday-table-left-border - (mapconcat (lambda (col) (funcall build-rule col)) - (number-sequence 0 (- cols 1)) - blackfriday-table-separator) - blackfriday-table-right-border)))) - contents)) - - + (let* ((table (org-export-get-parent-table table-row)) + (row-num (cl-position ;Begins with 0 + table-row + (org-element-map table 'table-row #'identity info))) + (row contents)) ;If CONTENTS is `nil', row has to be returned as `nil' too + ;; Reset the state variable when the first row of the table is + ;; received. + (when (eq 0 row-num) + (setq org-blackfriday--hrule-inserted nil)) + + (message "[ox-bf-table-row DBG] Row # %0d In contents: %s,\ntable-row: %S" row-num contents table-row) + (when row + (progn + (when (and (eq 'rule (org-element-property :type table-row)) + ;; In Blackfriday, rule is valid only at second row. + (eq 1 row-num)) + (let* ((table (org-export-get-parent-table table-row)) + ;; (headerp (org-export-table-row-starts-header-p table-row info)) + (build-rule (org-blackfriday-make-hline-builder table info ?-)) + (cols (cdr (org-export-table-dimensions table info)))) + (setq row (concat blackfriday-table-left-border + (mapconcat (lambda (col) + (funcall build-rule col)) + (number-sequence 0 (- cols 1)) + blackfriday-table-separator) + blackfriday-table-right-border)))) + + ;; If the first table row is "abc | def", it needs to have a rule + ;; under it for Blackfriday to detect the whole object as a table. + (when (and (stringp row) + (null org-blackfriday--hrule-inserted)) + (let ((rule (replace-regexp-in-string "[^|]" "-" row))) + (setq row (concat row "\n" rule)) + (setq org-blackfriday--hrule-inserted t))))) + (message "[ox-bf-table-row DBG] Row:\n%s" row) + row)) ;;;; Table - (defun org-blackfriday-table (table contents info) "Transcode TABLE element from Org into Blackfriday. CONTENTS is contents of the table. INFO is a plist holding contextual information." + (message "[ox-bf-table DBG] In contents: %s" contents) (let* ((rows (org-element-map table 'table-row 'identity info)) (no-header (or (<= (length rows) 1))) (cols (cdr (org-export-table-dimensions table info))) @@ -225,19 +244,23 @@ contextual information." (build-rule (org-blackfriday-make-hline-builder table info ?-)) (columns (number-sequence 0 (- cols 1)))) (concat blackfriday-table-left-border - (mapconcat (lambda (col) (funcall build-empty-cell col)) + (mapconcat (lambda (col) + (funcall build-empty-cell col)) columns blackfriday-table-separator) blackfriday-table-right-border "\n" blackfriday-table-left-border - (mapconcat (lambda (col) (funcall build-rule col)) + (mapconcat (lambda (col) + (funcall build-rule col)) columns blackfriday-table-separator) - blackfriday-table-right-border "\n")))))) - (concat (when no-header (funcall build-dummy-header)) - (replace-regexp-in-string "\n\n" "\n" contents)))) + blackfriday-table-right-border "\n"))))) + (tbl (concat (when no-header + (funcall build-dummy-header)) + (replace-regexp-in-string "\n\n" "\n" contents)))) + (message "[ox-bf-table DBG] Tbl:\n%s" tbl) + tbl)) ;;;; Table of contents - (defun org-blackfriday-format-toc (headline info) "Return an appropriate table of contents entry for HEADLINE. @@ -250,7 +273,6 @@ INFO is a plist used as a communication channel." (concat indent "- [" title "]" "(#" anchor ")"))) ;;;; Footnote section - (defun org-blackfriday-footnote-section (info) "Format the footnote section. INFO is a plist used as a communication channel." @@ -280,9 +302,7 @@ INFO is a plist used as a communication channel." fn-alist "\n")))))) - ;;;; Template - (defun org-blackfriday-inner-template (contents info) "Return body of document after converting it to Markdown syntax. CONTENTS is the transcoded contents string. INFO is a plist @@ -299,7 +319,7 @@ holding export options." "\n")))) (org-trim (concat toc-string toc-tail contents "\n" (org-blackfriday-footnote-section info))))) - + ;;; Interactive functions ;;;###autoload