2

How can we count the number of words inside a heading? (excluding the properties drawer and the actual heading title)

An example:

**Answer
:PROPERTIES:
:ID: <guid>
:END:
This is some text here

What I have been able to accomplish:

(org-id-goto "guid")
(org-mark-subtree)
(count-words (region-beginning) (region-end))

What I want is only counting This is some text here, not the entire heading, unlike what we get via the org-mark-subtree.

Edit: Let me perhaps describe my usecase a little more. What I am using org mode in this instance is for keeping track of word-limited essays, given a prompt. So my org file looks more like this:

 * DRAFTING Essay 1
 ** Prompt
    Describe a place in 60 words
 ** Answer
    It is very beautiful...

What I need is to count all words under the answer heading. Unable to find a way to do so, I created an ID so that I could use it to navigate, but that seems to add the overhead of the properties drawer. If I could find a way to only get the number of words, I can subtract the (in this case, fixed) number of words/characters taken by the heading and the properties drawer and get what I need.

5
  • (1) Please say how the code you've written doesn't do what you want. It's not clear what you do want. (2) For region-beginning and region-end to do what you want, you need to select that text as the region - set mark and point at the beginning and end, for instance. (3) Can you do what you want with a keyboard macro, i.e., interactively, recording your keystrokes? If so, provide those keystrokes/actions, as a recipe to show what you want done.
    – Drew
    Dec 28, 2021 at 22:53
  • @Drew: org-mark-subtree does set the region but it marks the whole subtree (from the headline ** Answer to the end of the buffer or the beginning of the next subtree at the same or higher level (whichever is encountered first). So the region includes the headline (** Answer) and the properties drawer (and possibly more things like scheduling information), both (all?) of which the OP wants to exclude from the count.
    – NickD
    Dec 28, 2021 at 23:30
  • @NickD: That's why I wrote "...what you want, you need to select that text". I meant select (only) the text you want to act on.
    – Drew
    Dec 29, 2021 at 2:19
  • 1
    I wrote up my own super complex regexp a few years ago to extract what I call the notes section of the subtree, which is the area @mon seeks to extract. However, my custom regexp is rather brittle, relying upon certain components that will always exist in the formation of the subtree ... Perhaps the O.P. can move to the end of the subtree and then extract the data between the end of org-property-end-re and subtree end.
    – lawlist
    Dec 29, 2021 at 4:44
  • 1
    Another idea to consider ... the org-agenda.el library has a mechanism to display the notes section of the subtree (with a toggle function) .... Perhaps the O.P. could use a similar mechanism to programmatically isolate and extract the data from said notes section.
    – lawlist
    Dec 29, 2021 at 4:51

1 Answer 1

2

Let the parser decide what real text, worthy to be word-counted, is.
You can use org-element-parse-headline and org-element-map for that purpose.

Below, there is an Elisp function org+-count-words-of-heading that demonstrates the principle.

Use it by putting point into the section where you want to count words and by M-x org+-count-words-of-heading. See the doc string of the function for more options.

(require 'cl-lib)
(require 'org-element)

(defun org-element-parse-headline (&optional granularity visible-only)
  "Parse current headline.
GRANULARITY and VISIBLE-ONLY are like the args of `org-element-parse-buffer'."
  (let ((level (org-current-level)))
    (org-element-map
    (org-element-parse-buffer granularity visible-only)
    'headline
      (lambda (el)
    (and
     (eq (org-element-property :level el) level)
     (<= (org-element-property :begin el) (point))
     (<= (point) (org-element-property :end el))
     el))
      nil 'first-match 'no-recursion)))

(cl-defun org+-count-words-of-heading (&key (worthy '(paragraph bold italic underline code footnote-reference link strike-through subscript superscript table table-row table-cell))
                        (no-recursion nil))
  "Count words in the section of the current heading.
WORTHY is a list of things worthy to be counted.
This list should at least include the symbols:
paragraph, bold, italic, underline and strike-through.

If NO-RECURSION is non-nil don't count the words in subsections."
  (interactive (and current-prefix-arg
            (list :no-recursion t)))
  (let ((word-count 0))
    (org-element-map
    (org-element-contents (org-element-parse-headline))
    '(paragraph table)
      (lambda (par)
    (org-element-map
        par
        worthy
        (lambda (el)
          (cl-incf
           word-count
           (cl-loop
        for txt in (org-element-contents el)
        when (eq (org-element-type txt) 'plain-text)
        sum
        (with-temp-buffer
          (insert txt)
          (count-words (point-min) (point-max))))
           ))))
      nil nil (and no-recursion 'headline)
      )
      (when (called-interactively-p 'any)
      (message "Word count in section: %d" word-count))
    word-count))
5
  • It doesn't seem to work: org-element-contents: Symbol’s function definition is void: org-element-parse-headline (As it is painfully obvious by now, I am a beginner to elisp. Would it be also possible to include a few comments as to what the code is doing?)
    – mon
    Dec 29, 2021 at 9:22
  • @mon Sorry, org-element-parse-headline was from my lib org+. I pasted it here. Could you try again?
    – Tobias
    Dec 29, 2021 at 9:31
  • The only strange thing I noted so far is that it also counts numbers as words. Don't know whether this is good or bad.
    – Tobias
    Dec 29, 2021 at 9:33
  • it works! re:numbers being counted as words, that is good for me, because the website I will be submitting too will also count them. Perhaps the only thing missing would be updating it live in the modeline, but I'm not too worried about that, it is more of an enhancement. Thanks!
    – mon
    Dec 29, 2021 at 10:54
  • 2
    @mon You asked: "Would it be also possible to include a few comments as to what the code is doing?". The most important thing to know is how you get help for each of the used functions. Place point behind the function you need help for and press C-h f RET. This opens a help buffer with the doc string of the function. Please try that at first and come back if there are still open questions after that.
    – Tobias
    Dec 29, 2021 at 12:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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