1
1
require 'json'
2
2
3
+ # This is an Asciidoctor backend that allows extracting the content
4
+ # from tagged snippets of text.
3
5
class TagsConverter
4
6
include Asciidoctor ::Converter
5
7
register_for ( "tags" )
6
8
9
+ # Hash{String => String} - map from tag ID to its text contents.
7
10
@tag_map = { }
11
+ # Array<String> - the stack of currently open sections in the
12
+ # section tree. Each `Section` contains
13
+ #
14
+ # * title: String - Section title.
15
+ # * id: String - Generated ID for the title which can be used for HTML links.
16
+ # * children: Array<Section> - Child sections.
17
+ # * tags: Array<String> - List of tags in this section directly (not in children).
18
+ #
19
+ # This always starts with a root section with an empty title and id.
20
+ @section_stack = [ ]
21
+
22
+ # Prefix on IDs that we require. We can't look at all IDs because
23
+ # it includes a load of auto-generated ones.
8
24
@prefix = ""
9
25
10
26
def initialize ( backend , opts = { } )
@@ -18,23 +34,64 @@ def initialize(backend, opts = {})
18
34
# `node` is an `AbstractNode`.
19
35
def convert ( node , transform = node . node_name , opts = nil )
20
36
if transform == "document" then
37
+ # This is the top level node. First clear the outputs.
21
38
@tag_map = { }
39
+ # Root node of the section tree. For simplicity we always
40
+ # have one root node with an empty title.
41
+ @section_stack = [ {
42
+ "title" => "" ,
43
+ "id" => "" ,
44
+ "children" => [ ] ,
45
+ "tags" => [ ] ,
46
+ } ]
47
+
22
48
# Calling node.content will recursively call convert() on all the nodes
23
49
# and also expand blocks, creating inline nodes. We call this to convert
24
50
# all nodes to text, and record their content in the tag map. Then we
25
51
# throw away the text and output the tag map as JSON instead.
26
52
node . content
53
+
54
+ # We must always add and remove an equal number of sections from the stack
55
+ # and we started with one so should end with one.
56
+ fail "Tags backend section logic error" if @section_stack . length != 1
57
+
27
58
JSON . pretty_generate ( {
28
59
"tags" : @tag_map ,
60
+ "sections" : @section_stack . first ,
29
61
} )
30
62
else
31
- # Output the text content of this node.
63
+
64
+ # If it's a section add it to the section tree.
65
+ if transform == "section" then
66
+ section = {
67
+ "title" => node . title ,
68
+ "id" => node . id ,
69
+ "children" => [ ] ,
70
+ "tags" => [ ] ,
71
+ }
72
+
73
+ @section_stack . last [ "children" ] << section
74
+ @section_stack << section
75
+ end
76
+
77
+ # Recursively get the text content of this node.
32
78
content = if node . inline? then node . text else node . content end
79
+
80
+ # Capture the content in the tag map and section tree if
81
+ # this node is tagged appropriately.
33
82
unless node . id . nil?
34
83
if node . id . start_with? ( @prefix )
35
84
@tag_map [ node . id ] = content
85
+ @section_stack . last [ "tags" ] << node . id
36
86
end
37
87
end
88
+
89
+ # If it's a section, we've recursed through it (via `node.content`)
90
+ # so pop it from the stack.
91
+ if transform == "section" then
92
+ @section_stack . pop ( )
93
+ end
94
+
38
95
content
39
96
end
40
97
end
0 commit comments