markbridge 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/markbridge/all.rb +4 -7
- data/lib/markbridge/ast/document.rb +1 -1
- data/lib/markbridge/ast/element.rb +2 -2
- data/lib/markbridge/ast/list.rb +2 -2
- data/lib/markbridge/ast/table.rb +6 -12
- data/lib/markbridge/ast/text.rb +5 -1
- data/lib/markbridge/bbcode.rb +4 -0
- data/lib/markbridge/gem_loader.rb +2 -3
- data/lib/markbridge/html.rb +4 -0
- data/lib/markbridge/mediawiki.rb +4 -0
- data/lib/markbridge/parsers/bbcode/closing_strategies/base.rb +0 -10
- data/lib/markbridge/parsers/bbcode/closing_strategies/reordering.rb +17 -4
- data/lib/markbridge/parsers/bbcode/closing_strategies/tag_reconciler.rb +64 -44
- data/lib/markbridge/parsers/bbcode/handler_registry.rb +21 -11
- data/lib/markbridge/parsers/bbcode/handlers/attachment_handler.rb +17 -12
- data/lib/markbridge/parsers/bbcode/handlers/base_handler.rb +0 -10
- data/lib/markbridge/parsers/bbcode/handlers/code_handler.rb +6 -10
- data/lib/markbridge/parsers/bbcode/handlers/image_handler.rb +9 -17
- data/lib/markbridge/parsers/bbcode/handlers/list_handler.rb +1 -5
- data/lib/markbridge/parsers/bbcode/handlers/list_item_handler.rb +1 -2
- data/lib/markbridge/parsers/bbcode/handlers/quote_handler.rb +6 -18
- data/lib/markbridge/parsers/bbcode/handlers/raw_handler.rb +2 -6
- data/lib/markbridge/parsers/bbcode/handlers/self_closing_handler.rb +4 -4
- data/lib/markbridge/parsers/bbcode/handlers/table_cell_handler.rb +1 -1
- data/lib/markbridge/parsers/bbcode/handlers/table_handler.rb +2 -2
- data/lib/markbridge/parsers/bbcode/handlers/table_row_handler.rb +3 -3
- data/lib/markbridge/parsers/bbcode/parser.rb +5 -8
- data/lib/markbridge/parsers/bbcode/parser_state.rb +12 -18
- data/lib/markbridge/parsers/bbcode/peekable_enumerator.rb +9 -59
- data/lib/markbridge/parsers/bbcode/raw_content_collector.rb +2 -2
- data/lib/markbridge/parsers/bbcode/scanner.rb +49 -63
- data/lib/markbridge/parsers/bbcode/tokens/tag_end_token.rb +1 -5
- data/lib/markbridge/parsers/bbcode/tokens/tag_start_token.rb +1 -6
- data/lib/markbridge/parsers/bbcode/tokens/text_token.rb +1 -7
- data/lib/markbridge/parsers/bbcode/tokens/token.rb +1 -1
- data/lib/markbridge/parsers/bbcode.rb +1 -0
- data/lib/markbridge/parsers/html/handler_registry.rb +32 -49
- data/lib/markbridge/parsers/html/handlers/base_handler.rb +0 -2
- data/lib/markbridge/parsers/html/handlers/image_handler.rb +1 -4
- data/lib/markbridge/parsers/html/parser.rb +3 -13
- data/lib/markbridge/parsers/media_wiki/inline_parser.rb +56 -67
- data/lib/markbridge/parsers/media_wiki/inline_tag_registry.rb +103 -0
- data/lib/markbridge/parsers/media_wiki/parser.rb +51 -76
- data/lib/markbridge/parsers/media_wiki.rb +1 -0
- data/lib/markbridge/parsers/text_formatter/handler_registry.rb +5 -37
- data/lib/markbridge/parsers/text_formatter/parser.rb +3 -8
- data/lib/markbridge/processors/discourse_markdown/code_block_tracker.rb +24 -17
- data/lib/markbridge/processors/discourse_markdown/detectors/base.rb +9 -15
- data/lib/markbridge/processors/discourse_markdown/detectors/event.rb +11 -10
- data/lib/markbridge/processors/discourse_markdown/detectors/poll.rb +11 -39
- data/lib/markbridge/processors/discourse_markdown/detectors/upload.rb +38 -63
- data/lib/markbridge/processors/discourse_markdown/scanner.rb +25 -33
- data/lib/markbridge/renderers/discourse/builders/list_item_builder.rb +6 -6
- data/lib/markbridge/renderers/discourse/html_escaper.rb +20 -0
- data/lib/markbridge/renderers/discourse/markdown_escaper.rb +49 -49
- data/lib/markbridge/renderers/discourse/render_context.rb +23 -11
- data/lib/markbridge/renderers/discourse/renderer.rb +54 -12
- data/lib/markbridge/renderers/discourse/rendering_interface.rb +12 -4
- data/lib/markbridge/renderers/discourse/tag.rb +14 -1
- data/lib/markbridge/renderers/discourse/tag_library.rb +30 -25
- data/lib/markbridge/renderers/discourse/tags/align_tag.rb +15 -7
- data/lib/markbridge/renderers/discourse/tags/bold_tag.rb +2 -0
- data/lib/markbridge/renderers/discourse/tags/code_tag.rb +14 -9
- data/lib/markbridge/renderers/discourse/tags/email_tag.rb +5 -3
- data/lib/markbridge/renderers/discourse/tags/event_tag.rb +3 -1
- data/lib/markbridge/renderers/discourse/tags/heading_tag.rb +6 -2
- data/lib/markbridge/renderers/discourse/tags/horizontal_rule_tag.rb +2 -2
- data/lib/markbridge/renderers/discourse/tags/image_tag.rb +13 -2
- data/lib/markbridge/renderers/discourse/tags/italic_tag.rb +2 -0
- data/lib/markbridge/renderers/discourse/tags/line_break_tag.rb +2 -2
- data/lib/markbridge/renderers/discourse/tags/list_item_tag.rb +24 -47
- data/lib/markbridge/renderers/discourse/tags/list_tag.rb +10 -15
- data/lib/markbridge/renderers/discourse/tags/mention_tag.rb +5 -1
- data/lib/markbridge/renderers/discourse/tags/paragraph_tag.rb +10 -0
- data/lib/markbridge/renderers/discourse/tags/poll_tag.rb +9 -2
- data/lib/markbridge/renderers/discourse/tags/quote_tag.rb +2 -0
- data/lib/markbridge/renderers/discourse/tags/spoiler_tag.rb +9 -0
- data/lib/markbridge/renderers/discourse/tags/strikethrough_tag.rb +2 -0
- data/lib/markbridge/renderers/discourse/tags/table_tag.rb +12 -8
- data/lib/markbridge/renderers/discourse/tags/underline_tag.rb +10 -3
- data/lib/markbridge/renderers/discourse/tags/upload_tag.rb +29 -2
- data/lib/markbridge/renderers/discourse/tags/url_tag.rb +5 -3
- data/lib/markbridge/renderers/discourse.rb +1 -0
- data/lib/markbridge/textformatter.rb +4 -0
- data/lib/markbridge/version.rb +1 -1
- data/lib/markbridge.rb +8 -8
- metadata +8 -2
|
@@ -27,55 +27,38 @@ module Markbridge
|
|
|
27
27
|
# Create the default handler registry with common HTML tags
|
|
28
28
|
# @return [HandlerRegistry]
|
|
29
29
|
def self.default
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
nil # Return nil - void element, no children
|
|
63
|
-
end,
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
# List handlers
|
|
67
|
-
registry.register(%w[ul ol], Handlers::ListHandler.new)
|
|
68
|
-
registry.register("li", Handlers::ListItemHandler.new)
|
|
69
|
-
|
|
70
|
-
# Table handlers (thead/tbody/tfoot are transparent - unregistered tags pass through)
|
|
71
|
-
registry.register("table", Handlers::TableHandler.new)
|
|
72
|
-
registry.register("tr", Handlers::TableRowHandler.new)
|
|
73
|
-
registry.register(%w[td th], Handlers::TableCellHandler.new)
|
|
74
|
-
|
|
75
|
-
# Paragraph handler (transparent - doesn't create AST node)
|
|
76
|
-
registry.register("p", Handlers::ParagraphHandler.new)
|
|
77
|
-
|
|
78
|
-
registry
|
|
30
|
+
new.tap do |registry|
|
|
31
|
+
registry.register(%w[b strong], Handlers::SimpleHandler.new(AST::Bold))
|
|
32
|
+
registry.register(%w[i em], Handlers::SimpleHandler.new(AST::Italic))
|
|
33
|
+
registry.register(%w[s strike del], Handlers::SimpleHandler.new(AST::Strikethrough))
|
|
34
|
+
registry.register("u", Handlers::SimpleHandler.new(AST::Underline))
|
|
35
|
+
registry.register("sup", Handlers::SimpleHandler.new(AST::Superscript))
|
|
36
|
+
registry.register("sub", Handlers::SimpleHandler.new(AST::Subscript))
|
|
37
|
+
registry.register(%w[code pre tt], Handlers::RawHandler.new(AST::Code))
|
|
38
|
+
registry.register("a", Handlers::UrlHandler.new)
|
|
39
|
+
registry.register("img", Handlers::ImageHandler.new)
|
|
40
|
+
registry.register("blockquote", Handlers::QuoteHandler.new)
|
|
41
|
+
registry.register(
|
|
42
|
+
"br",
|
|
43
|
+
lambda do |element:, parent:|
|
|
44
|
+
parent << AST::LineBreak.new
|
|
45
|
+
nil
|
|
46
|
+
end,
|
|
47
|
+
)
|
|
48
|
+
registry.register(
|
|
49
|
+
"hr",
|
|
50
|
+
lambda do |element:, parent:|
|
|
51
|
+
parent << AST::HorizontalRule.new
|
|
52
|
+
nil
|
|
53
|
+
end,
|
|
54
|
+
)
|
|
55
|
+
registry.register(%w[ul ol], Handlers::ListHandler.new)
|
|
56
|
+
registry.register("li", Handlers::ListItemHandler.new)
|
|
57
|
+
registry.register("table", Handlers::TableHandler.new)
|
|
58
|
+
registry.register("tr", Handlers::TableRowHandler.new)
|
|
59
|
+
registry.register(%w[td th], Handlers::TableCellHandler.new)
|
|
60
|
+
registry.register("p", Handlers::ParagraphHandler.new)
|
|
61
|
+
end
|
|
79
62
|
end
|
|
80
63
|
|
|
81
64
|
# Build a registry from the default configuration with optional customization
|
|
@@ -11,8 +11,6 @@ module Markbridge
|
|
|
11
11
|
# @param parent [AST::Element] the parent AST node
|
|
12
12
|
# @return [AST::Element, nil] the created element if children should be processed, nil otherwise
|
|
13
13
|
def process(element:, parent:)
|
|
14
|
-
# Default: do nothing, subclasses override
|
|
15
|
-
nil
|
|
16
14
|
end
|
|
17
15
|
|
|
18
16
|
# The element class created by this handler
|
|
@@ -72,15 +72,14 @@ module Markbridge
|
|
|
72
72
|
# @param node [Nokogiri::XML::Text]
|
|
73
73
|
# @param parent [AST::Element]
|
|
74
74
|
def process_text_node(node, parent)
|
|
75
|
-
|
|
76
|
-
parent << AST::Text.new(text) unless text.empty?
|
|
75
|
+
parent << AST::Text.new(node.text)
|
|
77
76
|
end
|
|
78
77
|
|
|
79
78
|
# Process an element node
|
|
80
79
|
# @param node [Nokogiri::XML::Element]
|
|
81
80
|
# @param parent [AST::Element]
|
|
82
81
|
def process_element_node(node, parent)
|
|
83
|
-
tag_name = node.name
|
|
82
|
+
tag_name = node.name
|
|
84
83
|
return if IGNORED_TAGS.include?(tag_name)
|
|
85
84
|
|
|
86
85
|
handler = @handlers[tag_name]
|
|
@@ -106,18 +105,9 @@ module Markbridge
|
|
|
106
105
|
# @param node [Nokogiri::XML::Element]
|
|
107
106
|
# @param parent [AST::Element]
|
|
108
107
|
def handle_unknown_tag(node, parent)
|
|
109
|
-
@unknown_tags[node.name
|
|
108
|
+
@unknown_tags[node.name] += 1
|
|
110
109
|
process_children(node, parent)
|
|
111
110
|
end
|
|
112
|
-
|
|
113
|
-
# Check if an element is a void element (self-closing)
|
|
114
|
-
# @param tag_name [String]
|
|
115
|
-
# @return [Boolean]
|
|
116
|
-
def void_element?(tag_name)
|
|
117
|
-
%w[area base br col embed hr img input link meta param source track wbr].include?(
|
|
118
|
-
tag_name.downcase,
|
|
119
|
-
)
|
|
120
|
-
end
|
|
121
111
|
end
|
|
122
112
|
end
|
|
123
113
|
end
|
|
@@ -5,14 +5,19 @@ module Markbridge
|
|
|
5
5
|
module MediaWiki
|
|
6
6
|
# Parses inline MediaWiki markup within a line of text.
|
|
7
7
|
# Handles bold ('''), italic (''), links ([[...]]), external links ([...]),
|
|
8
|
-
# and HTML inline tags
|
|
8
|
+
# and HTML inline tags via an InlineTagRegistry.
|
|
9
|
+
#
|
|
10
|
+
# @example With custom registry
|
|
11
|
+
# registry = InlineTagRegistry.build_from_default do |r|
|
|
12
|
+
# r.register("mark", :formatting, AST::Bold)
|
|
13
|
+
# end
|
|
14
|
+
# parser = InlineParser.new(inline_tag_registry: registry)
|
|
9
15
|
class InlineParser
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
@
|
|
14
|
-
@
|
|
15
|
-
@text_buffer = +""
|
|
16
|
+
MAX_INLINE_DEPTH = 20
|
|
17
|
+
|
|
18
|
+
def initialize(inline_tag_registry: nil, depth: 0)
|
|
19
|
+
@registry = inline_tag_registry || InlineTagRegistry.default
|
|
20
|
+
@depth = depth
|
|
16
21
|
end
|
|
17
22
|
|
|
18
23
|
# Parse inline markup and append resulting AST nodes to the parent element.
|
|
@@ -28,29 +33,18 @@ module Markbridge
|
|
|
28
33
|
|
|
29
34
|
while @pos < @length
|
|
30
35
|
char = @input[@pos]
|
|
31
|
-
next_char = @pos + 1 < @length ? @input[@pos + 1] : nil
|
|
32
36
|
|
|
33
37
|
case char
|
|
34
38
|
when "'"
|
|
35
|
-
|
|
36
|
-
parse_bold_italic
|
|
37
|
-
else
|
|
38
|
-
@text_buffer << char
|
|
39
|
-
@pos += 1
|
|
40
|
-
end
|
|
39
|
+
consecutive_apostrophes_at(@pos) >= 2 ? parse_bold_italic : append_literal(char)
|
|
41
40
|
when "["
|
|
42
41
|
flush_text
|
|
43
|
-
|
|
44
|
-
parse_internal_link
|
|
45
|
-
else
|
|
46
|
-
parse_external_link
|
|
47
|
-
end
|
|
42
|
+
@input[@pos + 1] == "[" ? parse_internal_link : parse_external_link
|
|
48
43
|
when "<"
|
|
49
44
|
flush_text
|
|
50
45
|
parse_html_tag
|
|
51
46
|
else
|
|
52
|
-
|
|
53
|
-
@pos += 1
|
|
47
|
+
append_literal(char)
|
|
54
48
|
end
|
|
55
49
|
end
|
|
56
50
|
|
|
@@ -59,22 +53,18 @@ module Markbridge
|
|
|
59
53
|
|
|
60
54
|
private
|
|
61
55
|
|
|
62
|
-
|
|
56
|
+
def append_literal(char)
|
|
57
|
+
@text_buffer << char
|
|
58
|
+
@pos += 1
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Precondition: caller has verified @input[@pos..@pos+1] is "''".
|
|
63
62
|
def parse_bold_italic
|
|
64
63
|
start = @pos
|
|
65
|
-
count =
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
count
|
|
69
|
-
|
|
70
|
-
if count < 2
|
|
71
|
-
@text_buffer << @input[@pos]
|
|
72
|
-
@pos += 1
|
|
73
|
-
else
|
|
74
|
-
flush_text
|
|
75
|
-
@pos += count
|
|
76
|
-
parse_apostrophe_formatting(count, start)
|
|
77
|
-
end
|
|
64
|
+
count = [consecutive_apostrophes_at(@pos), 5].min
|
|
65
|
+
flush_text
|
|
66
|
+
@pos += count
|
|
67
|
+
parse_apostrophe_formatting(count, start)
|
|
78
68
|
end
|
|
79
69
|
|
|
80
70
|
# Parse apostrophe-delimited formatting (bold, italic, or bold+italic).
|
|
@@ -99,9 +89,7 @@ module Markbridge
|
|
|
99
89
|
def build_formatting_element(apostrophe_count)
|
|
100
90
|
case apostrophe_count
|
|
101
91
|
when 5
|
|
102
|
-
|
|
103
|
-
bold << AST::Italic.new
|
|
104
|
-
bold
|
|
92
|
+
AST::Bold.new << AST::Italic.new
|
|
105
93
|
when 3
|
|
106
94
|
AST::Bold.new
|
|
107
95
|
when 2
|
|
@@ -115,8 +103,17 @@ module Markbridge
|
|
|
115
103
|
end
|
|
116
104
|
|
|
117
105
|
# Parse inner content and append to a parent element.
|
|
106
|
+
# Respects MAX_INLINE_DEPTH to prevent stack overflow from deeply nested markup.
|
|
118
107
|
def parse_inner_content(content, parent:)
|
|
119
|
-
|
|
108
|
+
if @depth + 1 >= MAX_INLINE_DEPTH
|
|
109
|
+
parent << AST::Text.new(content)
|
|
110
|
+
return
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
InlineParser.new(inline_tag_registry: @registry, depth: @depth + 1).parse(
|
|
114
|
+
content,
|
|
115
|
+
parent:,
|
|
116
|
+
)
|
|
120
117
|
end
|
|
121
118
|
|
|
122
119
|
# Collect text until we find n consecutive apostrophes.
|
|
@@ -127,14 +124,13 @@ module Markbridge
|
|
|
127
124
|
def collect_until_apostrophes(count)
|
|
128
125
|
start = @pos
|
|
129
126
|
while @pos < @length
|
|
130
|
-
if
|
|
127
|
+
if consecutive_apostrophes_at(@pos) >= count
|
|
131
128
|
content = @input[start...@pos]
|
|
132
129
|
@pos += count
|
|
133
130
|
return content
|
|
134
131
|
end
|
|
135
132
|
@pos += 1
|
|
136
133
|
end
|
|
137
|
-
nil
|
|
138
134
|
end
|
|
139
135
|
|
|
140
136
|
# Count consecutive apostrophes starting at position.
|
|
@@ -142,9 +138,7 @@ module Markbridge
|
|
|
142
138
|
# @param pos [Integer]
|
|
143
139
|
# @return [Integer]
|
|
144
140
|
def consecutive_apostrophes_at(pos)
|
|
145
|
-
|
|
146
|
-
count += 1 while pos + count < @length && @input[pos + count] == "'"
|
|
147
|
-
count
|
|
141
|
+
@input[pos..].each_char.take_while { |c| c == "'" }.length
|
|
148
142
|
end
|
|
149
143
|
|
|
150
144
|
# Parse [[internal link]] or [[target|display text]].
|
|
@@ -196,7 +190,6 @@ module Markbridge
|
|
|
196
190
|
@parent << url
|
|
197
191
|
end
|
|
198
192
|
|
|
199
|
-
# Parse an HTML tag (<code>, <nowiki>, <pre>, <br>, <s>, <del>, <u>, <ins>, <sup>, <sub>).
|
|
200
193
|
def parse_html_tag
|
|
201
194
|
tag_match = @input[@pos..].match(%r{\A<(/?)([a-z]+)(?: [^>]*)?\s*(/?)>}i)
|
|
202
195
|
unless tag_match
|
|
@@ -211,34 +204,30 @@ module Markbridge
|
|
|
211
204
|
tag_name = tag_match[2].downcase
|
|
212
205
|
|
|
213
206
|
# Closing/self-closing tags and unknown tags are treated as literal text
|
|
214
|
-
|
|
207
|
+
entry = @registry[tag_name]
|
|
208
|
+
if closing || self_closing || !entry
|
|
215
209
|
advance_as_text(full_match)
|
|
216
210
|
return
|
|
217
211
|
end
|
|
218
212
|
|
|
219
|
-
|
|
220
|
-
when "nowiki"
|
|
221
|
-
handle_nowiki_tag(full_match)
|
|
222
|
-
when "code", "pre"
|
|
223
|
-
handle_paired_raw_tag(tag_name, full_match, AST::Code)
|
|
224
|
-
when "br"
|
|
225
|
-
@pos += full_match.length
|
|
226
|
-
@parent << AST::LineBreak.new
|
|
227
|
-
when "s", "del"
|
|
228
|
-
handle_paired_tag(tag_name, full_match, AST::Strikethrough)
|
|
229
|
-
when "u", "ins"
|
|
230
|
-
handle_paired_tag(tag_name, full_match, AST::Underline)
|
|
231
|
-
when "sup"
|
|
232
|
-
handle_paired_tag(tag_name, full_match, AST::Superscript)
|
|
233
|
-
when "sub"
|
|
234
|
-
handle_paired_tag(tag_name, full_match, AST::Subscript)
|
|
235
|
-
end
|
|
213
|
+
dispatch_html_tag(entry, tag_name, full_match)
|
|
236
214
|
end
|
|
237
215
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
216
|
+
# Dispatch an HTML-like tag based on its registry entry type.
|
|
217
|
+
def dispatch_html_tag(entry, tag_name, full_match)
|
|
218
|
+
case entry.type
|
|
219
|
+
when :raw
|
|
220
|
+
if entry.element_class.nil?
|
|
221
|
+
handle_nowiki_tag(full_match)
|
|
222
|
+
else
|
|
223
|
+
handle_paired_raw_tag(tag_name, full_match, entry.element_class)
|
|
224
|
+
end
|
|
225
|
+
when :formatting
|
|
226
|
+
handle_paired_tag(tag_name, full_match, entry.element_class)
|
|
227
|
+
when :self_closing
|
|
228
|
+
@pos += full_match.length
|
|
229
|
+
@parent << entry.element_class.new
|
|
230
|
+
end
|
|
242
231
|
end
|
|
243
232
|
|
|
244
233
|
# Advance position and buffer the match as literal text.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Markbridge
|
|
4
|
+
module Parsers
|
|
5
|
+
module MediaWiki
|
|
6
|
+
# Registry of inline HTML-like tag handlers for the MediaWiki parser.
|
|
7
|
+
#
|
|
8
|
+
# Supports three tag types:
|
|
9
|
+
# - :raw - content is preserved verbatim (e.g., <code>, <nowiki>)
|
|
10
|
+
# - :formatting - content is parsed for inline wiki markup (e.g., <s>, <u>)
|
|
11
|
+
# - :self_closing - no content, produces a leaf AST node (e.g., <br>)
|
|
12
|
+
#
|
|
13
|
+
# @example Default usage
|
|
14
|
+
# registry = InlineTagRegistry.default
|
|
15
|
+
# entry = registry["s"]
|
|
16
|
+
# entry.type # => :formatting
|
|
17
|
+
# entry.element_class # => AST::Strikethrough
|
|
18
|
+
#
|
|
19
|
+
# @example Custom registration
|
|
20
|
+
# registry = InlineTagRegistry.build_from_default do |r|
|
|
21
|
+
# r.register("mark", :formatting, AST::Bold)
|
|
22
|
+
# end
|
|
23
|
+
class InlineTagRegistry
|
|
24
|
+
Entry = Data.define(:type, :element_class)
|
|
25
|
+
|
|
26
|
+
def initialize
|
|
27
|
+
@entries = {}
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Register a handler for an inline HTML-like tag.
|
|
31
|
+
#
|
|
32
|
+
# @param tag_name [String] the tag name (case-insensitive)
|
|
33
|
+
# @param type [:raw, :formatting, :self_closing] how the tag content is handled
|
|
34
|
+
# @param element_class [Class] the AST node class to create
|
|
35
|
+
# @return [self]
|
|
36
|
+
def register(tag_name, type, element_class)
|
|
37
|
+
validate_type!(type)
|
|
38
|
+
@entries[tag_name.downcase] = Entry.new(type:, element_class:)
|
|
39
|
+
self
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Look up a tag entry by name.
|
|
43
|
+
#
|
|
44
|
+
# @param tag_name [String]
|
|
45
|
+
# @return [Entry, nil]
|
|
46
|
+
def [](tag_name)
|
|
47
|
+
@entries[tag_name.downcase]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Check if a tag name is registered.
|
|
51
|
+
#
|
|
52
|
+
# @param tag_name [String]
|
|
53
|
+
# @return [Boolean]
|
|
54
|
+
def known?(tag_name)
|
|
55
|
+
@entries.key?(tag_name.downcase)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Create the default registry with standard MediaWiki inline tags.
|
|
59
|
+
#
|
|
60
|
+
# @return [InlineTagRegistry]
|
|
61
|
+
def self.default
|
|
62
|
+
registry = new
|
|
63
|
+
|
|
64
|
+
# Raw tags -content preserved verbatim, not parsed for wiki markup
|
|
65
|
+
registry.register("nowiki", :raw, nil)
|
|
66
|
+
registry.register("code", :raw, AST::Code)
|
|
67
|
+
registry.register("pre", :raw, AST::Code)
|
|
68
|
+
|
|
69
|
+
# Formatting tags -content parsed for inline wiki markup
|
|
70
|
+
registry.register("s", :formatting, AST::Strikethrough)
|
|
71
|
+
registry.register("del", :formatting, AST::Strikethrough)
|
|
72
|
+
registry.register("u", :formatting, AST::Underline)
|
|
73
|
+
registry.register("ins", :formatting, AST::Underline)
|
|
74
|
+
registry.register("sup", :formatting, AST::Superscript)
|
|
75
|
+
registry.register("sub", :formatting, AST::Subscript)
|
|
76
|
+
|
|
77
|
+
# Self-closing tags -produce a leaf node, no content
|
|
78
|
+
registry.register("br", :self_closing, AST::LineBreak)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Build a registry from the default with optional customization.
|
|
82
|
+
#
|
|
83
|
+
# @yield [InlineTagRegistry] the registry to customize
|
|
84
|
+
# @return [InlineTagRegistry]
|
|
85
|
+
def self.build_from_default
|
|
86
|
+
registry = default
|
|
87
|
+
yield(registry) if block_given?
|
|
88
|
+
registry
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
VALID_TYPES = %i[raw formatting self_closing].freeze
|
|
94
|
+
|
|
95
|
+
def validate_type!(type)
|
|
96
|
+
return if VALID_TYPES.include?(type)
|
|
97
|
+
|
|
98
|
+
raise ArgumentError, "type must be one of #{VALID_TYPES}, got #{type.inspect}"
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|