Search code examples
htmloutputorg-modeattrplantuml

How can I add "#+ATTR_HTML" around "#+RESULTS" preview output of Source Block in Org-mode of Emacs?


I am using plantuml in org, and it works fine. However, I am trying to make some change by adding some HTML tag automatically, just before the #+RESULTS output tag, so that can get a beautiful HTML output effect.

My example code as following:

#+BEGIN_SRC plantuml :file test.png
@startuml

  package "Some Group" {
          HTTP - [First Component]
          [Another Component]
  }

  node "Other Groups" {
          FTP - [Second Component]
          [First Component] --> FTP
  } 

  cloud {
          [Example 1]
  }


  database "MySql" {
          folder "This is my folder" {
                  [Folder 3]
          }
          frame "Foo" {
                  [Frame 4]
          }
  }


  [Another Component] --> [Example 1]
  [Example 1] --> [Folder 3]
  [Folder 3] --> [Frame 4]

  @enduml
#+END_SRC

After "C-c C-c" of plantuml, you will get the following output just below the plantuml code block:

#+RESULTS:
[[file:test.png]]

However, I want it automatically appended the above #+RESULT tags as following, which would be show bellow the plantuml source-block in the Emacs:

#+ATTR_HTML: :width 80% 
#+ATTR_ORG: :width 80% 
#+ATTR_HTML: :style background-color: white; border-radius: 8px; box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
#+RESULTS:
[[file:test.png]]

Then, I can output the org file to HTML by "C-c C-e h o" and get a beautiful HTML.

How can I make it?

Thanks !


Solution

  • The standard technique is to give a name to the source block:

    #+NAME: foo
    #+BEGIN_SRC plantuml :file test.png
    @startuml
    ...
    #+END_SRC
    

    When you evaluate the code block, it will produce a #+RESULTS: header like this:

    #+RESULTS: foo
    [[file:test.png]]
    

    Now you can add your #+ATTR_* declarations before the result:

    #+ATTR_HTML: :width 80% 
    #+ATTR_ORG: :width 
    #+ATTR_HTML: :style background-color: white; border-radius: 8px; box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
    #+RESULTS: foo
    [[file:test.png]]
    

    Rerunning the code block will put the file link in the same place and will leave the #+ATTR-* stuff alone. You can then export and get a "beautiful HTML"!

    EDIT: the main thrust of the above is avoiding the manual work that would be required if the block is NOT named: you would have to keep moving the attribute declarations to the newly generated #+RESULTS: section after each re-evaluation of the source block (and cleaning up the old results). From my POV, that's the annoying part, and the naming of the block takes care of it very nicely.

    The addition of the attribute declarations then becomes a one-time task that I don't particularly wish to automate, mainly because the attributes are bound to be different for different blocks, so anything that I do for one block will have to be changed entirely for another: I'd rather do the one-time tasks manually.

    That said, you can automate the attribute insertion partially, e.g. by creating a function to do the insertions and binding it to a key:

    (defun insert-attr-decls ()
      (interactive)
      (insert "#+ATTR_HTML: :width 80%\n")
      (insert "#+ATTR_ORG: :width 80%\n") 
      (insert "#+ATTR_HTML: :style background-color: white; border-radius: 8px; box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);\n"))
    
    (define-key org-mode-map [f10] 'insert-attr-decls)
    

    Then after the first evaluation, place point just before the #+RESULTS: foo line that is generated and press F10. From my POV, this is where I would stop (if I went this far).

    The next step would be to search for the place where the insertion should go and then call the function above - something like this although I'm sure it can be improved:

    (defun insert-attr-decls ()
      (insert "#+ATTR_HTML: :width 80%\n")
      (insert "#+ATTR_ORG: :width 80%\n") 
      (insert "#+ATTR_HTML: :style background-color: white; border-radius: 8px; box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);\n"))
    
    (defun insert-attr-decls-at (s)
      (let ((case-fold-search t))
       (if (search-forward s nil t)
        (progn 
           (search-backward s nil t)
           (insert-attr-decls)))))
    
    (defun insert-attr-decls-at-results ()
      (interactive)
      (save-excursion
       (insert-attr-decls-at "#+RESULTS:")))
    
    (define-key org-mode-map [f10] 'insert-attr-decls-at-results)
    

    That already is a quantum jump in complexity and it probably contains bugs: what will happen e.g. if there is no #+RESULTS: string in the buffer? I have not tested. So if somebody else is willing to spend the time to produce a "perfect" function, then yes, maybe I'll take it and use it. But unless this is going to be my bread-and-butter work, I'd rather spend my time elsewhere: you can always automate it later if it indeed proves to be a time sink.

    The next step (which I would not do) would be to have C-c C-c evaluate the block and add the attribute declarations. You can do that by adding insert-attr-decls-at-results to the org-babel-after-execute-hook variable:

    (add-hook 'org-babel-after-execute-hook 'insert-attr-decls-at-results)
    

    The trouble here is that at every evaluation, you'd get one more attribute declaration block added: you really have to rewrite the insertion function to check whether there is an attribute declaration block already and if so, do nothing. That's left as an exercise.

    The moral of the story AFAIAC is: you can automate everything, but not everything is worth automating.