diff --git a/src/lib/parser.ts b/src/lib/parser.ts index 57ae5e6..60a4b29 100644 --- a/src/lib/parser.ts +++ b/src/lib/parser.ts @@ -1,19 +1,34 @@ -import { Asciidoctor, Document, Extensions, type ProcessorOptions } from 'asciidoctor'; +import { AbstractNode, Asciidoctor, Block, Document, Extensions, Section, type ProcessorOptions } from 'asciidoctor'; -class Pharos extends Asciidoctor { +export default class Pharos extends Asciidoctor { private html?: string | Document; + /** + * A map of node IDs to the nodes themselves. + */ + private nodes: Map = new Map(); + + /** + * A map of node IDs to the integer event kind that will be used to represent the node. + */ + private kinds: Map = new Map(); + + /** + * A map of index IDs to the IDs of the nodes they reference. + */ + private indices: Map> = new Map>(); + constructor() { super(); const pharos = this; - this.Extensions.register(function () { + pharos.Extensions.register(function () { const registry = this; registry.treeProcessor(function () { const dsl = this; dsl.process(function (document) { const treeProcessor = this; - pharos.pharosTreeProcessor(treeProcessor, document); + pharos.treeProcessor(treeProcessor, document); }); }) }); @@ -24,8 +39,93 @@ class Pharos extends Asciidoctor { this.Block } - private pharosTreeProcessor(treeProcessor: Extensions.TreeProcessor, document: Document) { - // Recursively map sections to their note kinds down to five levels of depth. - document.getSections(); + /** + * Walks the Asciidoctor Abstract Syntax Tree (AST) and performs the following mappings: + * - Each node ID is mapped to the node itself. + * - Each node ID is mapped to an integer event kind that will be used to represent the node. + * - Each ID of a node containing children is mapped to the set of IDs of its children. + */ + private treeProcessor(treeProcessor: Extensions.TreeProcessor, document: Document) { + const id = document.getId(); + this.nodes.set(id, document); + this.kinds.set(id, 30040); + this.indices.set(id, new Set()); + + /** FIFO queue (uses `Array.push()` and `Array.shift()`). */ + const queue: AbstractNode[] = document.getBlocks(); + + while (queue.length > 0) { + const block = queue.shift(); + if (!block) { + continue; + } + + if (block instanceof Section) { + const children = this.processSection(block); + queue.push(...children); + } else { + this.processBlock(block as Block); + } + } + } + + /** + * Processes a section of the Asciidoctor AST. + * @param section The section to process. + * @returns An array of the section's child nodes. If there are no child nodes, returns an empty + * array. + * @remarks Sections are mapped as kind 30040 indices by default. + */ + private processSection(section: Section): AbstractNode[] { + const id = section.getId(); + + // Prevent duplicates. + if (this.nodes.has(id)) { + return []; + } + + this.nodes.set(id, section); + this.kinds.set(id, 30040); // Sections are indices by default. + this.indices.set(id, new Set()); + + const parentId = section.getParent()?.getId(); + if (!parentId) { + return []; + } + + // Add the section to its parent index. + this.indices.get(parentId)?.add(id); + + // Limit to 5 levels of section depth. + if (section.getLevel() >= 5) { + return []; + } + + return section.getBlocks(); + } + + /** + * Processes a block of the Asciidoctor AST. + * @param block The block to process. + * @remarks Blocks are mapped as kind 30041 zettels by default. + */ + private processBlock(block: Block): void { + const id = block.getId(); + + // Prevent duplicates. + if (this.nodes.has(id)) { + return; + } + + this.nodes.set(id, block); + this.kinds.set(id, 30041); // Blocks are zettels by default. + + const parentId = block.getParent()?.getId(); + if (!parentId) { + return; + } + + // Add the block to its parent index. + this.indices.get(parentId)?.add(id); } } \ No newline at end of file