kjambunathan / org-mode-ox-odt

The Authoritative fork of Org mode's ODT exporter
GNU General Public License v3.0
47 stars 9 forks source link

how to export checkboxes #99

Closed ouboub closed 3 years ago

ouboub commented 3 years ago

Hi

Any idea how to export checkboxes to odt?

I mean not just simply having [ ] in the odt document but having them translated as actual boxes.

Thanks and regards

Uwe Brauer

ouboub commented 3 years ago

Either using latex $\boxtimes$ or UTF8 ☒

kjambunathan commented 3 years ago

I will install this patch sometime in the future ...

Patch & suggested User-settings

(custom-set-variables
 '(org-entities-user
   '(
     ("boxminus"    "\\boxminus" t  "⊟"    "[-]"   "[-]"   "⊟")
     ("boxtimes"    "\\boxtimes" t  "⊠"    "[x]"   "[x]"   "⊠")
     ("square"      "\\square" t    "□"  "[ ]"   "[ ]"   "□"))))

(defun org-odt--checkbox (item)
  "Return check-box string associated to ITEM."
  (let ((checkbox (org-element-property :checkbox item)))
    (if (not checkbox) ""
      (format "<text:span text:style-name=\"%s\">%s </text:span>"
          "OrgCode" (cl-case checkbox
              ;; ⊠ &timesb; &boxtimes; &#x022A0; &#8864; SQUARED TIMES
              (on (or (nth 6 (org-entity-get "boxtimes"))
                  (on "[&#x2713;]") ; CHECK MARK
                  ))
              ;; □ &squ; &square; &Square; &#x025A1; &#9633; WHITE SQUARE
              (off (or (nth 6 (org-entity-get "square"))
                   (off "[ ]")))
              ;; ⊟ &minusb; &boxminus; &#x0229F; &#8863; SQUARED MINUS
              (trans (or (nth 6 (org-entity-get "boxminus"))
                     (trans "[-]"))))))))

Use of entities allows for more flexibility

Use of entities allows one to choose whatever checkboxes one wants. There doesn't seem to be a "consistent" set of unicode symbols that allow for "tristate" checkboxes.

Instead of the suggestion above one can go with, for example, these set of characters

🗷⮽🞏

            character: 🗷 (displayed as 🗷) (codepoint 128503, #o372767, #x1f5f7)
              charset: unicode (Unicode (ISO10646))
code point in charset: 0x1F5F7
               script: symbol
               syntax: w    which means: word
             category: .:Base
             to input: type "C-x 8 RET 1f5f7" or "C-x 8 RET BALLOT BOX WITH BOLD SCRIPT X"
            character: ⮽ (displayed as ⮽) (codepoint 11197, #o25675, #x2bbd)
              charset: unicode-bmp (Unicode Basic Multilingual Plane (U+0000..U+FFFF))
code point in charset: 0x2BBD
               script: symbol
               syntax: _    which means: symbol
             category: .:Base
             to input: type "C-x 8 RET 2bbd" or "C-x 8 RET BALLOT BOX WITH LIGHT X"
            character: 🞏 (displayed as 🞏) (codepoint 128911, #o373617, #x1f78f)
              charset: unicode (Unicode (ISO10646))
code point in charset: 0x1F78F
               script: symbol
               syntax: w    which means: word
             category: .:Base
             to input: type "C-x 8 RET 1f78f" or "C-x 8 RET MEDIUM WHITE SQUARE"

Unit test

- [-] test
  - [ ] one
  - [X] two 

- \boxminus test
  - \square one
  - \boxtimes two

How the above snippet is rendered

Screenshot from 2021-09-29 15-09-45

Notes on "Tristate" checkboxes in LibreOffice

  1. Form controls in LibreOffice - The Document Foundation Wiki
  2. Check Box - Apache OpenOffice Wiki

FWIW, this is how tristate checkbox controls are rendered in LibreOffice

Screenshot from 2021-09-29 15-28-52

Screenshot from 2021-09-29 15-34-22

tristate checkbox control.zip

kjambunathan commented 3 years ago

Re-opening this issue ...

kjambunathan commented 3 years ago

TLDR: Use of checkbox control--instead of unicode characters--cannot be easily accomplished with what is right now in the ox-odt.el.

ouboub commented 3 years ago

Two comments:

1. Can that patch easily applied to the clone github repository? I
   might give it a try for testing.

2. I realized that I should add, that I need to generate an odt
   document, in which checkboxes are placed in tables, a feature
   that is not supported by org mode as of today, unfortunately. So
   I think that cannot be solved by any exporter.
kjambunathan commented 3 years ago

I have improved typesetting of checkboxes in https://github.com/kjambunathan/org-mode-ox-odt/commit/33eb2bc464affbfdf651611c9f74a572db09544a. See the commit log / diff for details.

#+options: e:t

#+begin_src emacs-lisp :results silent :exports none
  (dolist (new-entity
       '(
         ;; ("name"         "LaTeX"         "LaTeX mathp"  "HTML"        "ASCII"  "Latin1"  "utf-8")
        ("checkboxon"   "\\boxtimes"    t              "&#x1F5F7;"  "[x]"    "[x]"     "&#x1F5F7;") ; 🗷 - BALLOT BOX WITH BOLD SCRIPT X
        ("checkboxoff"  "\\square"      t              "&#x1F78F;"  "[ ]"    "[ ]"     "&#x1F78F;") ; 🞏 - MEDIUM WHITE SQUARE
        ("checkboxwip"  "\\boxminus"    t              "&#x2BBD;"   "[-]"    "[-]"     "&#x2BBD;" ) ; ⮽ - BALLOT BOX WITH LIGHT X
        ))
    (map-delete org-entities-user (car new-entity))
    (customize-set-variable 'org-entities-user (cons new-entity org-entities-user))
    (customize-save-variable 'org-entities-user org-entities-user))
#+end_src

- [-] Organize party [1/4]
  - [-] call people [2/3]
    - [ ] Peter
    - [X] Sarah
    - [X] Sam
  - [ ] order food
  - [ ] think about what music to play
  - [X] talk to the neighbors

- \checkboxwip Organize party
  - \checkboxwip call people
    - \checkboxoff Peter
    - \checkboxon Sarah
    - \checkboxon Sam
  - \checkboxoff order food
  - \checkboxoff think about what music to play
  - \checkboxon talk to the neighbors

This is the how the exported documented looks like:

Screenshot from 2021-10-02 09-30-59

kjambunathan commented 3 years ago
  1. I realized that I should add, that I need to generate an odt document, in which checkboxes are placed in tables, a feature that is not supported by org mode as of today, unfortunately. So I think that cannot be solved by any exporter.

The context is this

shrink table in columnmode view (poor man's issue system)

Let me help you define and (partially) solve the problem for you.

With headlines like this

Screenshot from 2021-10-02 15-13-01

instead of generating a colview table like this

Screenshot from 2021-10-02 15-20-13

you can generate a colview table like this

Screenshot from 2021-10-02 15-11-31

or a checklist like this

Screenshot from 2021-10-02 15-12-05

The zip file below has the needed recipes.

colview-as-checklist-as-opposed-to-a-table.zip

Btw, as the .org file mentions the solution provided by Marco Wahl in https://lists.gnu.org/archive/html/emacs-orgmode/2021-09/msg00408.html will not work when the dblock has affiliated keywords like caption.

You will have better luck if you reword your original request along the following lines ... The :format field of columnview dblock, merely collects the fields. What you need is a second level formatter that emits a formatted string with the collected fields. i.e., if the fields separated by | you get a table, instead if they are preceded by - and separated by ` you get a checklist. The second level formatter would allow specification of the "constant" strings in the formatter. The current implementation only looks for%STUFF` and skips over other "constant" strings. (It is difficult to explain, but you can infer what I mean.)

(I will leave you with this half-baked response. My focus is ODT exporter. Anything outside of it is not of any interest to me ... I hope I have shared enough information so that you can build upon what I have offered you.)

  1. I realized that I should add, that I need to generate an odt document, in which checkboxes are placed in tables, a feature that is not supported by org mode as of today, unfortunately. So I think that cannot be solved by any exporter.

You provide little context and little in way of what you want.

With such a request it is unlikely that someone (including me) would step up to define and solve the problem for you. You probably know this. You have been in FLOSS lists much longer than I have been. So, next time, please provide a better context and definition of what you want. I will be more than happy to help.

Appendix

#+options: ':nil *:t -:t ::t <:t H:3 \n:nil ^:t arch:headline
#+options: author:t broken-links:mark c:nil creator:nil
#+options: d:(not "LOGBOOK") date:t e:t email:nil f:t inline:t num:t
#+options: p:nil pri:nil prop:nil stat:t tags:t tasks:t tex:t
#+options: timestamp:t title:t toc:t todo:nil |:t
#+title: checkbox-2
#+date: <2021-10-02 Sat>
#+author: Jambunathan K
#+email: kjambunathan@debian
#+language: en
#+select_tags: export
#+exclude_tags: noexport
#+creator: Emacs 28.0.50 (Org mode 9.4.4)

#+STARTUP: align
#+STARTUP: shrink

* A Useful Hack

Set TODO keywords as Checkbox states

This will be useful in [[*Columnview as a checklist][Columnview as a checklist]]

#+begin_src emacs-lisp :results silent :exports code
  (setq org-todo-keywords
    '((sequence "[ ]" "[-]"  "|" "[X]" )))

  (org-update-all-dblocks)
  (call-interactively 'save-buffer)
#+end_src

* [ ] Top-level issue [2/4]
   :PROPERTIES:
   :COLUMNS:  %50ITEM(Problem) %10Is(Issue Nr) %7TODO(TODO) %26TAGS(Which) %17Date(Date)
   :ID:       Issues
   :COOKIE_DATA: todo recursive
   :END:

** [X] First child issue first child issue first child issue
   :PROPERTIES:
   :Date:     <2021-09-25 Sat>
   :Is:       #9
   :URL:    https://github.com/kjambunathan/org-mode-ox-odt/issues/99
   :END:

** [X] Second child issue second child issue second child issue second child issue
   :PROPERTIES:
   :Date:     <2021-09-25 Sat>
   :Is:       NA
   :URL:    https://lists.gnu.org/archive/html/emacs-orgmode/2021-09/msg00321.html
   :END:

** [-] Third child issue third child issue third child issue third child issue
   :PROPERTIES:
   :Date:     <2021-09-25 sáb>
   :Is:       #9
   :URL:
   :END:

** [ ] Fourth child issue fourth child issue fourth child issue fourth child issue
   :PROPERTIES:
   :Date:     <2021-09-25 sáb>
   :Is:       #9
   :URL:
   :END:

* Column Views

** Stock Columnview

#+BEGIN: columnview :id Issues :hlines 2 :skip-empty-rows t :indent nil
#+CAPTION: Stock columnview
| Problem                                                                     | Issue Nr | TODO | Which | Date             |
|-----------------------------------------------------------------------------+----------+------+-------+------------------|
| Top-level issue                                                             |          | [ ]  |       |                  |
|-----------------------------------------------------------------------------+----------+------+-------+------------------|
| First child issue first child issue first child issue                       | #9       | [X]  |       | <2021-09-25 Sat> |
|-----------------------------------------------------------------------------+----------+------+-------+------------------|
| Second child issue second child issue second child issue second child issue | NA       | [X]  |       | <2021-09-25 Sat> |
|-----------------------------------------------------------------------------+----------+------+-------+------------------|
| Third child issue third child issue third child issue third child issue     | #9       | [-]  |       | <2021-09-25 sáb> |
|-----------------------------------------------------------------------------+----------+------+-------+------------------|
| Fourth child issue fourth child issue fourth child issue fourth child issue | #9       | [ ]  |       | <2021-09-25 sáb> |
#+END:

** COMMENT Columnview with Shrink

The code below is from [this message](https://lists.gnu.org/archive/html/emacs-orgmode/2021-09/msg00408.html).

#+name: columnview2
#+begin_src emacs-lisp :results silent :exports none
  (defun org-dblock-write:columnview2 (params)
    "Write the column view table.

  Like org-dblock-write:columnview but write a line with shrink widths
  taken from the
  column view format.

  PARAMS is the same as in `org-dblock-write:columnview'."
    (insert (format "|%s|\n"
            (mapconcat
             (lambda (x) (concat "<" (number-to-string x) ">"))
             (mapcar (lambda (x) (nth 2 x))
                 (org-columns-compile-format
                  (plist-get params
                     :format)))
             "|")))
    (org-dblock-write:columnview params))
#+end_src

~org-dblock-write:columnview2~ as defined above doesn't allow a
caption to be added to a dynamic block.

The dynamic block below is OK

#+BEGIN: columnview2 :id Issues :hlines 2 :skip-empty-rows t :indent nil  :format "%10ITEM(Problem)  %5Is(Issue) %12TODO  %12Date"
| <10>                                                                        | <5>   | <12> | <12>             |
| Problem                                                                     | Issue | TODO | Date             |
|-----------------------------------------------------------------------------+-------+------+------------------|
| Top-level issue                                                             |       | [ ]  |                  |
|-----------------------------------------------------------------------------+-------+------+------------------|
| First child issue first child issue first child issue                       | #9    | [X]  | <2021-09-25 Sat> |
|-----------------------------------------------------------------------------+-------+------+------------------|
| Second child issue second child issue second child issue second child issue | NA    | [X]  | <2021-09-25 Sat> |
|-----------------------------------------------------------------------------+-------+------+------------------|
| Third child issue third child issue third child issue third child issue     | #9    | [-]  | <2021-09-25 sáb> |
|-----------------------------------------------------------------------------+-------+------+------------------|
| Fourth child issue fourth child issue fourth child issue fourth child issue | #9    | [ ]  | <2021-09-25 sáb> |
#+END:

... but not this one

# #+BEGIN: columnview2 :id Issues :hlines 2 :skip-empty-rows t :indent nil  :format "%10ITEM(Problem)  %5Is(Issue) %12TODO  %12Date"
# #+CAPTION: Columnview with Shrink
# | <10>                                                                        | <5>   | <12> | <12>             |
# | Problem                                                                     | Issue | TODO | Date             |
# |-----------------------------------------------------------------------------+-------+------+------------------|
# | Top-level issue                                                             |       | [ ]  |                  |
# |-----------------------------------------------------------------------------+-------+------+------------------|
# | First child issue first child issue first child issue                       | #9    | [X]  | <2021-09-25 Sat> |
# |-----------------------------------------------------------------------------+-------+------+------------------|
# | Second child issue second child issue second child issue second child issue | NA    | [X]  | <2021-09-25 Sat> |
# |-----------------------------------------------------------------------------+-------+------+------------------|
# | Third child issue third child issue third child issue third child issue     | #9    | [-]  | <2021-09-25 sáb> |
# |-----------------------------------------------------------------------------+-------+------+------------------|
# | Fourth child issue fourth child issue fourth child issue fourth child issue | #9    | [ ]  | <2021-09-25 sáb> |
# #+END:

** Columnview with alignment, width and colgroups

#+CAPTION:  Column view with widths, alignment, colgroups
#+name: columnview3
#+begin_src emacs-lisp :results silent :exports none
  (defun org-dblock-write:columnview3 (params)
    "Write the column view table.

  Use `:preamble'. Ignore the widths in `:format' string."
    (when (plist-get params :preamble)
      (insert (plist-get params :preamble) "\n"))
    (org-dblock-write:columnview params))
#+end_src

The function above has same problems wrt caption.

#+BEGIN: columnview3 :id Issues :hlines 2 :skip-empty-rows t :indent nil :format "%12TODO %10ITEM(Problem) %5Is(Issue) %12Date" :preamble "| <r2> | <l10> | <5> | <12> |\n| / | | < | |"
| <r2> | <l10>                                                                       | <5>   | <12>             |
|    / |                                                                             | <     |                  |
| TODO | Problem                                                                     | Issue | Date             |
|------+-----------------------------------------------------------------------------+-------+------------------|
|  [ ] | Top-level issue                                                             |       |                  |
|------+-----------------------------------------------------------------------------+-------+------------------|
|  [X] | First child issue first child issue first child issue                       | #9    | <2021-09-25 Sat> |
|------+-----------------------------------------------------------------------------+-------+------------------|
|  [X] | Second child issue second child issue second child issue second child issue | NA    | <2021-09-25 Sat> |
|------+-----------------------------------------------------------------------------+-------+------------------|
|  [-] | Third child issue third child issue third child issue third child issue     | #9    | <2021-09-25 sáb> |
|------+-----------------------------------------------------------------------------+-------+------------------|
|  [ ] | Fourth child issue fourth child issue fourth child issue fourth child issue | #9    | <2021-09-25 sáb> |
#+END:

** Columnview as a checklist

#+CAPTION: Column view as proper as checklist
#+name: checklist
#+begin_src emacs-lisp :results silent :exports none
  (defun org-dblock-write:checklist (params)
    "Write the column view table.

  PARAMS is a property list of parameters:

  `:id' (mandatory)

      The ID property of the entry where the columns view should be
      built.  When the symbol `local', call locally.  When `global'
      call column view with the cursor at the beginning of the
      buffer (usually this means that the whole buffer switches to
      column view).  When \"file:path/to/file.org\", invoke column
      view at the start of that file.  Otherwise, the ID is located
      using `org-id-find'.

  `:exclude-tags'

      List of tags to exclude from column view table.

  `:format'

      When non-nil, specify the column view format to use.

  `:hlines'

      When non-nil, insert a hline before each item.  When
      a number, insert a hline before each level inferior or equal
      to that number.

  `:indent'

      When non-nil, indent each ITEM field according to its level.

  `:match'

      When set to a string, use this as a tags/property match filter.

  `:maxlevel'

      When set to a number, don't capture headlines below this level.

  `:skip-empty-rows'

      When non-nil, skip rows where all specifiers other than ITEM
      are empty.

  `:skip-header'

      When non-nil, skip the header."
    (let ((table
       (let ((id (plist-get params :id))
         view-file view-pos)
         (pcase id
           (`global nil)
           ((or `local `nil) (setq view-pos (point)))
           ((and (let id-string (format "%s" id))
             (guard (string-match "^file:\\(.*\\)" id-string)))
        (setq view-file (match-string-no-properties 1 id-string))
        (unless (file-exists-p view-file)
          (user-error "No such file: %S" id-string)))
           ((and (let idpos (org-find-entry-with-id id)) (guard idpos))
        (setq view-pos idpos))
           ((let `(,filename . ,position) (org-id-find id))
        (setq view-file filename)
        (setq view-pos position))
           (_ (user-error "Cannot find entry with :ID: %s" id)))
         (with-current-buffer (if view-file (get-file-buffer view-file)
                    (current-buffer))
           (org-with-wide-buffer
        (when view-pos (goto-char view-pos))
        (org-columns--capture-view (plist-get params :maxlevel)
                       (plist-get params :match)
                       (plist-get params :skip-empty-rows)
                       (plist-get params :exclude-tags)
                       (plist-get params :format)
                       view-pos))))))
      (when table
    ;; Prune level information from the table.  Also normalize
    ;; headings: remove stars, add indentation entities, if
    ;; required, and possibly precede some of them with a horizontal
    ;; rule.
    (let ((item-index
           (let ((p (assoc "ITEM" org-columns-current-fmt-compiled)))
         (and p (cl-position p
                     org-columns-current-fmt-compiled
                     :test #'equal))))
          (hlines (plist-get params :hlines))
          (indent (plist-get params :indent))
          new-table)
      ;; Copy header and first rule.
      ;; (push (pop table) new-table)
      ;; (push (pop table) new-table)
      (cond
       ((plist-get params :skip-header)
        (pop table)
        (pop table))
       (t
        (push (pop table) new-table)
        (push (pop table) new-table)))

      (dolist (row table (setq table (nreverse new-table)))
        (let ((level (car row)))
          (when (and (not (eq (car new-table) 'hline))
             (or (eq hlines t)
                 (and (numberp hlines) (<= level hlines))))
        (push 'hline new-table))
          ;; (when item-index
          ;;   (let ((item (org-columns--clean-item (nth item-index (cdr row)))))
          ;;    (setf (nth item-index (cdr row))
          ;;          (if (and indent (> level 1))
          ;;          (concat "\\_" (make-string (* 2 (1- level)) ?\s) item)
          ;;        item))))
          (setcar (cdr row) (format "%s- %s" (make-string (* 2 (1- level)) ?\s) (cadr row)))
          (push (cdr row) new-table))))
    (let ((content-lines (org-split-string (plist-get params :content) "\n")))
      ;; Insert affiliated keywords before the table.
      (when content-lines
        (while (string-match-p "\\`[ \t]*#\\+" (car content-lines))
          (insert (pop content-lines) "\n")))
      (save-excursion
        ;; Insert table at point.
        (insert
         (mapconcat (lambda (row)
              (if (eq row 'hline) ""
                (format "%s" (mapconcat #'identity row " "))))
            table
            "\n")))))))
#+end_src

*Column view as proper as checklist*

#+BEGIN: checklist :id Issues :skip-header t :hlines 2 :skip-empty-rows t :indent nil :format "%12TODO %10ITEM(Problem) %5Is(Issue) %12Date"

- [ ] Top-level issue [2/4]  

  - [X] First child issue first child issue first child issue #9 <2021-09-25 Sat>

  - [X] Second child issue second child issue second child issue second child issue NA <2021-09-25 Sat>

  - [-] Third child issue third child issue third child issue third child issue #9 <2021-09-25 sáb>

  - [ ] Fourth child issue fourth child issue fourth child issue fourth child issue #9 <2021-09-25 sáb>
#+END:

# Local Variables:
# eval: (menu-bar--toggle-truncate-long-lines)
# End:
kjambunathan commented 3 years ago

If you haven't already noticed ... the snippet below is the one that will help you generate "first class" checklists from the colview definition.

A Useful Hack

Set TODO keywords as Checkbox states

This will be useful in [[*Columnview as a checklist][Columnview as a checklist]]

  (setq org-todo-keywords
    '((sequence "[ ]" "[-]"  "|" "[X]" )))

You will have better luck if you reword your original request along the following lines ...

(I think) you may also need a way to "map" todo keywords like TODO, DONE etc. to their equivalent "ascii" or "ballot box" versions for export purposes.

ouboub commented 3 years ago

Thanks very much for your help, I really appreciate this quick, detailed and helpful responses. Just allow me to clarify two things.

  1. you said I should have been more specific with my request, well maybe but I also found out that when I am too specific people find it well to specific and do not respond. That happened to me in the past and that is why I left some details out
  2. your code is very useful for the question I asked in the context of https://lists.gnu.org/archive/html/emacs-orgmode/2021-09/msg00408.html, however the checkbox question in tables comes from a different task: I have to fill and submit an application form that was sent to me in printed form, but my answer I should submit in electronic form. I started to scan it and tried to edit the resulting pdf, which was a considerable ordeal, I tried to convert it to odt, without much success, so I decided to rewrite it from the beginning in org mode and then convert/export it. That application form has two difficulties a.) large fields to be filled with quite some text b.) checkboxes in tables for a) I find your list --> table extremely useful, for b) I thought of using finally the UTF8 identities.

Now with your code I might try to write it as org checkboxes and try to export it, I am however not completely sure that it will work. In any case you answer is very useful for the question I brought up on the org-mode list regards UB

kjambunathan commented 3 years ago

Fix available in ox-odt-9.3.7.327.tar or later versions.