基于Org节点网络的属性探究


链接子树

回到我们开头遇到的问题,为避免将节点拷贝到不同视图的节点中,我们需要定义一种超越大纲结构的关系。一种方案如下:

节点的链接子树:

* 节点A
:PROPERTIES:
:ID: xxx
:END:
* 节点B
** [[id:xxx][节点A]]

如上述 Org 片段所示,节点A 以链接的形式作为 节点B 的子树。借此定义, B 的子节点除了物理意义上存于其下的 Org entry 外,还包括这些——被链接所指的 Org entries——链接子树:

属性: #'org-T.peers.linktree, 链接子树。

#+name: 2025-08-29-20-18
#+header: :lexical t :eval yes :results silent
#+begin_src emacs-lisp
(!def 'org-T.peers.linktree
 (!let (org-T.peers.linktree linktree linknode)
  (!def org-T.peers.linktree
   (lambda (&optional node)
     (org-referent-get node linktree :no-cache t)))

  (!def linktree
   (lambda nil
     (remq nil (org-map-entries
                linknode nil 'tree))))

  (!def linknode
   (lambda nil
     (and-let*
         ((title (org-entry-get nil "ITEM"))
          (link (with-temp-buffer
                  (save-excursion (insert title))
                  (org-element-link-parser)))
          (_ (org-element-type-p link '(link)))
          (link (org-element-property :raw-link link))
          (_ (format "[[%s]]" link))))))

  org-T.peers.linktree))
#+end_src

在上述定义的链接子树关系的基础上,我们将先前的任务节点重新组织成如下的 Org 片段。任务节点物理上依旧存储于“项目”节点中,并以链接子树的形式,在逻辑上作为“日期”节点的子节点:

* 项目
** 项目A
*** 编码a
:LOGBOOK:
CLOCK: [2025-08-18 Mon 09:30]--[2025-08-18 Mon 11:30] =>  2:00
:END:
*** 测试a
:LOGBOOK:
CLOCK: [2025-08-20 Wed 14:00]--[2025-08-20 Wed 18:00] =>  4:00
:END:
** 项目B
*** 分析b
:LOGBOOK:
CLOCK: [2025-08-22 Fri 10:25]--[2025-08-22 Fri 11:50] =>  1:25
:END:
*** 编码b
:LOGBOOK:
CLOCK: [2025-08-25 Mon 09:45]--[2025-08-25 Mon 11:50] =>  2:05
:END:
* 日期
** 32周投入
*** [[*编码a][编码a]]
*** [[*测试a][测试a]]
*** [[*分析b][分析b]]
** 33周投入
*** [[*编码b][编码b]]

基于上述 Org 片段,我们可通过如下代码生成不同视角的报表:

#+name: 2025-08-29-20-19
#+header: :lexical t :eval no
#+begin_src emacs-lisp :var node="[[*项目]]"
(let* ((n node)
       ;; 一种新的 clock-sum 属性。
       (clock-sum
        (lambda (n)
          (+
           ;; 除了整棵 tree 的 clock 数据外,
           (org-T.clock-sum n)
           ;; 还包括所有 linktree 的 clock 数据。
           (apply
            #'+
            (mapcar
             #'org-T.clock-sum
             (org-T.peers.linktree n))))))
       (formatter
        (lambda (mins)
          (format "%d:%02d"
                  (/ mins 60) (% mins 60))))
       (N (org-T.peers.tree n))
       (item (seq-mapn
              (lambda (i l)
                (if (= l 1) i
                  (concat
                   "\\_"
                   (make-string (* 2 (1- l)) ? ) i
                   (make-string (1- l) ?|))))
              (mapcar #'org-E.title N)
              (mapcar #'org-E.level N)))
       (clock (mapcar clock-sum N))
       (clock (mapcar formatter clock)))
  (seq-mapn #'list item clock))
#+end_src

项目视角:

#+call: 2025-08-29-20-19(node="[[*项目]]") :eval no

#+RESULTS:
| 项目        | 9:30 |      |      |
| \_  项目A   |      | 6:00 |      |
| \_    编码a |      |      | 2:00 |
| \_    测试a |      |      | 4:00 |
| \_  项目B   |      | 3:30 |      |
| \_    分析b |      |      | 1:25 |
| \_    编码b |      |      | 2:05 |

日期视角:

#+call: 2025-08-29-20-19(node="[[*日期]]") :eval no

#+RESULTS:
| 日期         | 9:30 |      |      |
| \_  32周投入 |      | 7:25 |      |
| \_    编码a  |      |      | 2:00 |
| \_    测试a  |      |      | 4:00 |
| \_    分析b  |      |      | 1:25 |
| \_  33周投入 |      | 2:05 |      |
| \_    编码b  |      |      | 2:05 |