0

I've been trying to achieve the following:

* Riviere
Contents of Riviere title

* Riviere 
More contents of Riviere

turn into:

* Riviere
Contents of Riviere title
More contents of Riviere

I found a function that almost gets the job done. I tried adding a heading2 variable and changing the condition to match heading and heading2:

(defun promote-screenings-and-readings (&optional backend)
  (org-map-entries
   (lambda ()
     (let ((point (point))
           (heading (buffer-substring-no-properties
                     (point)
                     (save-excursion
                       (forward-line)
                       (point))))
           (heading2 (save-excursion
                       (org-backward-heading-same-level 1)
                       (forward-line))))
       (when (or (string= heading heading2))
         (org-map-entries
          (lambda ()
            (unless (= (point) point)
              (org-promote)))
          nil 'tree)
         (forward-line)
         (delete-region point (point)))))))

But it still doesn't work. That's how I set heading2, right? If I change string= heading "* Anything here\n" the heading gets deleted.

The reason behind this is I keep all my college slides inside an org file, and I want to avoid repetitive headlines when exporting to something other than Beamer. There are 5k lines in the document, so I think using the :no_title: method would be inefficient.

  • I don't have an answer to your question, but wanted to point out that there are built-in functions to get the heading, such as org-heading-components and elements thereof can be obtained using the function nth and the corresponding N. See also org-element-at-point; e.g., to get the title, (let* ((element (org-element-at-point)) (title (org-element-property :raw-value element))) title). I personally like to programmatically go to the bottom of an org-mode file and search backwards, using something like (while (re-search-backward org-complex-heading-regexp nil t) ...) – lawlist May 21 at 2:55
  • ... continued from my initial comment: And here is another example of how to get the contents of a heading when point is at the beginning thereof: (when (looking-at org-complex-heading-regexp) (let ((stars (match-string-no-properties 1)) (status (match-string-no-properties 2)) (priority (match-string-no-properties 3)) (title (match-string-no-properties 4)) (tags (match-string-no-properties 5))) (list stars status priority title tags))) And, to get the whole kitten kaboodle, match 0; e.g., (when (looking-at org-complex-heading-regexp) (match-string-no-properties 0)) ... – lawlist May 21 at 3:08
  • @lawlist, I used org-element previously and understand what you're telling but I don't quite follow how you get the element at a point and then the element after OR before it. Doing recursively as you suggested would be optimal I think. What I don't get is how I get element at point and the element before it to compare both strings. I will try to recover the point in the second var in let and try to go to previous heading – cadr May 22 at 18:23
0

To merge all subtrees/entries that share the same headline, you can try the following:

(defun merge-subtrees ()
  (interactive)
  (let (headlines)
    (save-excursion
      (goto-char (point-min))
      (while (re-search-forward org-complex-heading-regexp nil t)
        (let ((headline (match-string 4)))
          (if (member headline headlines)
              (let* ((beg (line-beginning-position))
                     (end (save-excursion (org-end-of-subtree)))
                     (contents (buffer-substring (point) end)))
                (delete-region beg (save-excursion
                                     (org-end-of-subtree t t)))
                (save-excursion
                  (when (re-search-backward (concat "^\\*.?+ "
                                                    headline
                                                    "\\($\\|[ \t]+:.+\\)")
                                            nil t)
                    (org-end-of-subtree)
                    (insert contents))))
            (push headline headlines)))))))

Now to merge only consecutive subtrees/entries that share the same headline, try the following:

(defun merge-subtrees ()
  (interactive)
  (save-excursion
    (goto-char (point-max))
    (while (re-search-backward org-complex-heading-regexp nil t)
      (let ((headline (match-string 4)))
        (when (save-excursion
                (outline-previous-heading)
                (and (looking-at org-complex-heading-regexp)
                     (string= headline (match-string 4))))
          (let ((beg (org-entry-beginning-position))
                (end (save-excursion
                       (org-end-of-meta-data t)
                       (point))))
            (when (> beg (point-min))
              (delete-region beg end))))))))
| improve this answer | |
  • Sorry for late answer. How to modify so it just merges if it's directly below it? Thanks for taking your time to help me – cadr May 26 at 0:15
  • @cadr I changed the answer. See if that works for you. – jagrg May 26 at 14:46
  • Yes! It works! Thank you so much, sincerely, I thought my approach was close enough but you and @lawlist came up with total different solution. It always delete the first entry due to the recursion form. If you are in the first heading and do (outline-previous-heading), the point stays in the same position and it gets flushed. I just added a if statement around the when case: (if (not (string-match (what-line) "(Line 1)")). Thank you so much for your help – cadr May 27 at 19:00
  • @cadr, thanks. I see the problem. Should be fixed now. – jagrg May 27 at 23:50
0

The following answer does not modify the original org-mode buffer containing the raw data, but instead gathers the raw data into a list and then generates a new buffer containing a modified version of the original data. My personal preference is to go to the bottom of the working buffer and search backwards -- this is because point is at the beginning of a heading at each stop of the re-... search. [There is a line of code that uses nreverse on the initial gathered-data because we searched from the bottom of the file to the top of the file -- the assumption is that the original raw data contained a desired order, whether that be chronological or something else.] It is, of course, possible to search forward with a modified version of this example ... it is also possible to sort the data ..., but that is beyond the scope of this example.

TEST DATA -- ORG-MODE BUFFER:

* Baz is the best.
Testing ten eleven twelve.

* Riviere had a cup of tea.
Contents of Riviere title.

* Foo is having toast.
Testing one two three.

* Bar had a baby.
Testing seven eight nine.

* Riviere had a cup of tea.
More contents of Riviere.

* Baz is the best.
Testing thirteen fourteen fifteen.

* Foo is having toast.
Testing four five six.

EXAMPLE CODE:

(save-excursion
  (unless (eq major-mode 'org-mode)
    (error "This example only works with an 'org-mode buffer."))
  (goto-char (point-max))
  (let ((kill-ring nil)
        (kill-ring-yank-pointer nil)
        (interprogram-cut-function nil)
        (interprogram-paste-function nil)
        gathered-result
        end-result)
    (while (re-search-backward org-complex-heading-regexp nil t)
      (let ((entire-heading (match-string 0))
            subtree)
          (org-copy-subtree)
          (setq subtree (car kill-ring))
        (push (list (cons entire-heading subtree)) gathered-result)))
    (setq gathered-result (nreverse gathered-result))
    (mapc
      (lambda (x)
        (if (= 0 (length end-result))
          (push x end-result)
          (let (test)
            (setq end-result (mapcar
                               (lambda (y)
                                 (if (equal (car (car x)) (car (car y)))
                                   (progn
                                     (setq test t)
                                     (append x y))
                                   y))
                               end-result))
            (when (null test)
              (push x end-result)))))
      gathered-result)
    (let ((buffer (get-buffer-create "*OUTPUT*")))
      (with-current-buffer buffer
        (unless (eq major-mode 'org-mode)
          (org-mode))
        (let ((buffer-read-only nil))
          (erase-buffer)
          (mapc
            (lambda (z)
              (if (= 1 (length z))
                (let ((string (replace-regexp-in-string "\n+$" "" (cdr (car z)))))
                  (insert string "\n\n"))
                (let ((string (replace-regexp-in-string "\n+$" "" (car (car z)))))
                  (insert string "\n"))
                (mapc
                  (lambda (w)
                    (let ((string (replace-regexp-in-string (concat "^" (car w) "\n") "" (cdr w))))
                      (setq string (replace-regexp-in-string "\n+$" "" string))
                      (insert string "\n")))
                  z)
                (insert "\n")))
            end-result)))
      (unless (get-buffer-window buffer)
        (display-buffer buffer t)))
    end-result))

*OUTPUT* BUFFER -- USING EXAMPLE CODE:

* Bar had a baby.
Testing seven eight nine.

* Riviere had a cup of tea.
Contents of Riviere title.
More contents of Riviere.

* Baz is the best.
Testing ten eleven twelve.
Testing thirteen fourteen fifteen.

* Foo is having toast.
Testing one two three.
Testing four five six.
| improve this answer | |
  • Sorry for late answer. How to modify so it just merges if it's directly below it? Thanks for taking your time to help me – cadr May 26 at 0:16
  • In general, I do not give examples involving delete-region -- out of fear that the O.P. or other forum reader in the future may not take appropriate backup measures and delete original data that is permanently lost .... – lawlist May 26 at 1:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.