krz/org-swift

A dependency-free Swift library that renders org-mode to sanitized HTML. html library org-mode swift

Sources/OrgSwift/AST/OrgFootnotes.swift

799d4ba18ece2bd259bd4760cca46faeae0fd1e1
org-swift/Sources/OrgSwift/AST/OrgFootnotes.swift history · blame · raw

57 lines · 2327 bytes

 1import Foundation
 2
 3/// Assigns footnote numbers in first-reference order and holds the notes they resolve to.
 4///
 5/// Numbering is document-wide and order-of-use, so it belongs to the render pass rather than
 6/// the parse: a renderer walks the tree, calls ``number(for:inline:)`` as it meets each
 7/// reference, then reads ``notes`` to emit the section. It is output-format agnostic —
 8/// the HTML renderer and the SwiftUI one share it.
 9package struct OrgFootnotes {
10    private var numbers: [String: Int] = [:]
11    private var order: [String] = []
12    /// Reference-style definitions, gathered from the document's `[fn:x] …` lines.
13    private var definitions: [String: [OrgObject]] = [:]
14    /// Inline definitions, gathered from `[fn:x:text]` references as they are rendered.
15    private var inlineDefinitions: [String: [OrgObject]] = [:]
16    package var figureNumber = 0
17
18    package init(document: OrgDocument) {
19        for element in document.elements {
20            if case .footnoteDefinition(let label, let content) = element {
21                definitions[label] = content
22            }
23        }
24    }
25
26    package mutating func number(for label: String, inline: [OrgObject]? = nil) -> Int {
27        if let inline, inlineDefinitions[label] == nil { inlineDefinitions[label] = inline }
28        if let existing = numbers[label] { return existing }
29        let next = order.count + 1
30        numbers[label] = next
31        order.append(label)
32        return next
33    }
34
35    /// One numbered note, resolved to its content.
36    package struct Note {
37        package var number: Int
38        package var label: String
39        package var content: [OrgObject]
40        /// An inline footnote's text sits directly in the item; a reference-style definition
41        /// is a paragraph, matching org's exporter.
42        package var isInline: Bool
43    }
44
45    /// The referenced notes, in numbering order. Empty when the document has none, which is
46    /// the renderer's cue to omit the section entirely.
47    package var notes: [Note] {
48        order.map { label in
49            Note(
50                number: numbers[label] ?? 0,
51                label: label,
52                content: inlineDefinitions[label] ?? definitions[label] ?? [],
53                isInline: inlineDefinitions[label] != nil
54            )
55        }
56    }
57}