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


引用与反向引用

在深入后续之前,我们先定义两个关于 Org entry 的属性:

(org-E.ref &optional node local)

(org-E.backrefs &optional node)

借助 #'org-E.ref, 我们可以生成任意 Org entry 的引用,其返回的可能是一条带中括号的 Org 链接,也可能是个 marker. 总之,我们暂且称它为 REF. 通过 #'org-E.ref 返回的 REF, 我们可以定位 REF 所指的 Org entry; 而 #'org-E.backrefs 返回的是一个 REF 集合。

注:后续为 #'org-E.ref#'org-E.backrefs 的实现细节,可跳过直转下一节。
注:本文提及的 Org entry 特指 org-element-type 为 headline 的 Org 元素。
注:此处关于 Org entry 引用的定义并非唯一,只是为了演示后续的功能而定义。
注: REF 所指的 Org节点 的类型为 Org entry, 而不是 Org Source Block, 或其他什么东西。

Org entry 引用 (#'org-E.ref):

#+name: 2025-08-29-20-08
#+header: :eval yes
#+begin_src emacs-lisp :lexical t :results silent
(!def 'org-E.ref
 (!let (org-E.ref store-ref full-ref local-ref)
  (!def org-E.ref
   (lambda (&optional node local)
     ;; 因为我们可能返回 marker, 相比于 LINK, marker 是一
     ;; 种不稳定的引用,因此 ‘org-E.ref’ 不使用
     ;; ‘org-referent-get’ 的缓存特性。
     (org-referent-get node
       (lambda nil (store-ref local))
       :no-cache t)))

  (!def store-ref
   ;; 我们统一使用加中括号的 LINK, 且不要 DESC.
   (lambda (local)
     (let* ((link (or ;; 优先使用 ID 链接。
                   ;; ‘org-id-store-link-maybe’ 返回的
                   ;; 链接格式为 id:x.
                   (org-id-store-link-maybe)
                   ;; ‘org-store-link’ 返的 FILE LINK
                   ;; 格式为 [[file:xx]].
                   (org-store-link nil nil)
                   ;; 一些无关联文件且无 ID 的 entry 中,
                   (when local
                     (and-let*
                         ((cid (org-entry-get
                                nil "CUSTOM_ID"))
                          (cid (format
                                "[[#%s]]" cid))))))))
       (cond
        ;; 如果有 LINK 可引用当前节点,返回 LINK,
        ((and
          link
          ;; LINK::*headline 类链接属于非法 ref, 因为此
          ;; 类链接的定位可能不唯一,所以我们不用此类链接。
          (length= (string-split link "::*" t) 1)
          (setq link
                (with-temp-buffer
                  (save-excursion
                    (insert link))
                  (org-element-link-parser))))
         (if local (local-ref link) (full-ref link)))
        ;; 否则使用 marker 引用当前节点。
        ;; (global) ref 可能是 LINK 或 marker; 但对于
        ;; local ref 而言,marker 不算 local, 所以,当
        ;; LINK 不可用时,只有 not local 时才返回 marker.
        ((not local)
         (save-excursion
           ;; 如其名中 E 所暗示, ‘org-E.ref’ 只是对 Org
           ;; entry, 或者说 headline, 的引用。
           (org-back-to-heading-or-point-min)
           ;; 如果当前节点非 Org entry, 直接抛异常。
           (unless (org-element-type-p
                    (org-element-at-point)
                    '(headline))
             (error "Not a valid entry at %S %s"
                    (current-buffer) (point)))
           (point-marker)))))))

  ;; 全局引用。如:
  ;; [[id:ID::SEARCH-OPTION]],
  ;; [[file:path::SEARCH-OPTION]].
  (!def full-ref
   ;; link: org-element link
   (lambda (link)
     (format "[[%s]]"
             (org-element-property
              :raw-link link))))

  ;; 局部引用。如:
  ;; [[#CUSTOM-ID]], [[#SEARCH-OPTION]].
  (!def local-ref
   ;; link: org-element link
   (lambda (link)
     (pcase (org-element-property :type link)
       ("file"
        (and-let*
            ((opt (org-element-property
                   :search-option link))
             (_ (format "[[%s]]" opt)))))
       ("id"
        (and-let*
            ((rl (org-element-property
                  :raw-link link))
             (opt (string-split rl "::" t))
             (_ (length> 1 opt))
             (opt (car (last opt)))
             (_ (format "[[%s]]" opt)))))
       ("custom-id"
        (format "[[%s]]" (org-element-property
                          :raw-link link))))))

  org-E.ref))
#+end_src

Org entry 反向引用 (#'org-E.backrefs):

#+name: 2025-08-29-20-09
#+header: :eval yes
#+begin_src emacs-lisp :lexical t :results silent
(!def 'org-E.backrefs
 (!let (org-E.backrefs
        backrefs search ref-re)
  (!def org-E.backrefs
   (lambda (&optional node)
     ;; 因为我们可能返回 marker, 相比于 LINK, marker 是一
     ;; 种不稳定的引用,因此 ‘org-E.ref’ 不使用
     ;; ‘org-referent-get’ 的缓存特性。
     (org-referent-get node
       backrefs :no-cache t)))

  (!def backrefs
   (lambda nil
     (when-let*
         ((ref (org-E.ref))
          ;; org-E.ref 返回的 LINK 总带中括号。
          (ref (if (not (stringp ref)) ref
                 (string-trim
                  ref "\\[\\[" "\\]\\]")))
          (lref (or (org-E.ref nil t) ""))
          (lref (string-trim
                 lref "\\[\\[" "\\]\\]"))
          ;; 反向引用查找的范围:所有与文件相关的
          ;; Org buffers.
          (bufs
           (seq-filter
            (lambda (b)
              (with-current-buffer b
                (and (derived-mode-p 'org-mode)
                     (buffer-file-name))))
            (buffer-list))))
       (seq-uniq
        (append
         ;; 对于带 option 型链接,我们需特殊处理。
         (when (length> lref 0)
           (search lref))
         ;; external backrefs
         (when (stringp ref)
           (mapcan
            (lambda nil (search ref)) bufs)))))))

  (!def search
   ;; 根据 re 查找 buf 中的引用。
   (lambda (ref &optional buf)
     (let ((m (make-marker))
           (re (ref-re ref)) R
           (buf (or buf (current-buffer))))
       (org-with-point-at (set-marker m 1 buf)
         (save-match-data
           (while (re-search-forward re nil t)
             (and-let*
                 ((r (org-E.ref))
                  ;; 注意: org-E.ref 可能返回 marker.
                  (_ (not (equal r ref)))
                  (_ (not (member r R))))
               (push r R)))))
       R)))

  (!def ref-re
   (lambda (ref)
     (concat
      "\\[" "\\[" ref "\\]"
      "\\(\\[.*?\\]\\)?" "\\]")))

  org-E.backrefs))
#+end_src