markbridge 0.1.0 → 0.1.1

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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/lib/markbridge/ast/table.rb +67 -0
  3. data/lib/markbridge/ast.rb +1 -0
  4. data/lib/markbridge/parsers/bbcode/handler_registry.rb +5 -0
  5. data/lib/markbridge/parsers/bbcode/handlers/image_handler.rb +13 -11
  6. data/lib/markbridge/parsers/bbcode/handlers/quote_handler.rb +40 -33
  7. data/lib/markbridge/parsers/bbcode/handlers/table_cell_handler.rb +26 -0
  8. data/lib/markbridge/parsers/bbcode/handlers/table_handler.rb +32 -0
  9. data/lib/markbridge/parsers/bbcode/handlers/table_row_handler.rb +35 -0
  10. data/lib/markbridge/parsers/bbcode/parser.rb +1 -1
  11. data/lib/markbridge/parsers/bbcode.rb +3 -0
  12. data/lib/markbridge/parsers/html/handler_registry.rb +5 -0
  13. data/lib/markbridge/parsers/html/handlers/base_handler.rb +0 -1
  14. data/lib/markbridge/parsers/html/handlers/table_cell_handler.rb +24 -0
  15. data/lib/markbridge/parsers/html/handlers/table_handler.rb +24 -0
  16. data/lib/markbridge/parsers/html/handlers/table_row_handler.rb +24 -0
  17. data/lib/markbridge/parsers/html/parser.rb +13 -2
  18. data/lib/markbridge/parsers/html.rb +3 -0
  19. data/lib/markbridge/parsers/media_wiki/inline_parser.rb +105 -130
  20. data/lib/markbridge/parsers/media_wiki/parser.rb +128 -0
  21. data/lib/markbridge/parsers/text_formatter/handler_registry.rb +6 -0
  22. data/lib/markbridge/parsers/text_formatter/handlers/table_cell_handler.rb +26 -0
  23. data/lib/markbridge/parsers/text_formatter.rb +1 -0
  24. data/lib/markbridge/processors/discourse_markdown/code_block_tracker.rb +96 -84
  25. data/lib/markbridge/processors/discourse_markdown/detectors/base.rb +12 -0
  26. data/lib/markbridge/processors/discourse_markdown/detectors/event.rb +0 -10
  27. data/lib/markbridge/processors/discourse_markdown/detectors/poll.rb +0 -10
  28. data/lib/markbridge/processors/discourse_markdown/scanner.rb +19 -16
  29. data/lib/markbridge/renderers/discourse/markdown_escaper.rb +237 -180
  30. data/lib/markbridge/renderers/discourse/renderer.rb +1 -0
  31. data/lib/markbridge/renderers/discourse/tags/align_tag.rb +1 -1
  32. data/lib/markbridge/renderers/discourse/tags/attachment_tag.rb +1 -1
  33. data/lib/markbridge/renderers/discourse/tags/code_tag.rb +2 -1
  34. data/lib/markbridge/renderers/discourse/tags/event_tag.rb +3 -5
  35. data/lib/markbridge/renderers/discourse/tags/image_tag.rb +1 -1
  36. data/lib/markbridge/renderers/discourse/tags/mention_tag.rb +1 -1
  37. data/lib/markbridge/renderers/discourse/tags/poll_tag.rb +3 -5
  38. data/lib/markbridge/renderers/discourse/tags/quote_tag.rb +15 -11
  39. data/lib/markbridge/renderers/discourse/tags/table_cell_tag.rb +18 -0
  40. data/lib/markbridge/renderers/discourse/tags/table_row_tag.rb +18 -0
  41. data/lib/markbridge/renderers/discourse/tags/table_tag.rb +124 -0
  42. data/lib/markbridge/renderers/discourse/tags/upload_tag.rb +1 -1
  43. data/lib/markbridge/renderers/discourse.rb +3 -0
  44. data/lib/markbridge/version.rb +1 -1
  45. data/lib/markbridge.rb +20 -55
  46. metadata +12 -1
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Markbridge
4
+ module Renderers
5
+ module Discourse
6
+ module Tags
7
+ # Tag for rendering table rows (passthrough - renders children only)
8
+ # The TableTag handles rows directly; this is a safety net for standalone rendering.
9
+ class TableRowTag < Tag
10
+ def render(element, interface)
11
+ child_context = interface.with_parent(element)
12
+ interface.render_children(element, context: child_context)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Markbridge
4
+ module Renderers
5
+ module Discourse
6
+ module Tags
7
+ # Tag for rendering tables as Markdown pipe tables with HTML fallback
8
+ class TableTag < Tag
9
+ def render(element, interface)
10
+ child_context = interface.with_parent(element)
11
+ rows_data = extract_rows(element, interface, child_context)
12
+
13
+ return "" if rows_data.empty?
14
+
15
+ if markdown_compatible?(rows_data, interface)
16
+ render_markdown(rows_data)
17
+ else
18
+ render_html(rows_data)
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ # Extract rendered cell data from each row
25
+ # @return [Array<Hash>] array of {cells: [{content:, header:}], ...}
26
+ def extract_rows(element, interface, child_context)
27
+ element.children.filter_map do |child|
28
+ next unless child.is_a?(AST::TableRow)
29
+
30
+ cells =
31
+ child.children.filter_map do |cell|
32
+ next unless cell.is_a?(AST::TableCell)
33
+
34
+ cell_context = child_context.with_parent(child)
35
+ content = interface.render_children(cell, context: cell_context).strip
36
+ { content:, header: cell.header? }
37
+ end
38
+
39
+ { cells: } unless cells.empty?
40
+ end
41
+ end
42
+
43
+ # Check if the table can be rendered as Markdown
44
+ def markdown_compatible?(rows_data, interface)
45
+ return false if rows_data.empty?
46
+ return false if interface.has_parent?(AST::Table)
47
+
48
+ cell_count = rows_data.first[:cells].length
49
+ rows_data.all? do |row|
50
+ row[:cells].length == cell_count &&
51
+ row[:cells].none? { |c| c[:content].include?("\n") }
52
+ end
53
+ end
54
+
55
+ # Render as Markdown pipe table
56
+ def render_markdown(rows_data)
57
+ header_idx = rows_data.index { |r| r[:cells].all? { |c| c[:header] } }
58
+ header_row = header_idx ? rows_data[header_idx] : rows_data.first
59
+ data_rows =
60
+ (
61
+ if header_idx
62
+ rows_data[0...header_idx] + rows_data[(header_idx + 1)..]
63
+ else
64
+ rows_data[1..]
65
+ end
66
+ )
67
+
68
+ col_count = header_row[:cells].length
69
+ lines = []
70
+ lines << format_row(header_row[:cells])
71
+ lines << "| #{(["---"] * col_count).join(" | ")} |"
72
+ data_rows.each { |row| lines << format_row(row[:cells]) }
73
+
74
+ "\n\n#{lines.join("\n")}\n\n"
75
+ end
76
+
77
+ # Format a single row as a Markdown pipe row
78
+ def format_row(cells)
79
+ # Pipe characters in cell content are already escaped by the markdown escaper
80
+ "| #{cells.map { |c| c[:content] }.join(" | ")} |"
81
+ end
82
+
83
+ # Render as HTML table
84
+ def render_html(rows_data)
85
+ has_header = rows_data.any? { |r| r[:cells].any? { |c| c[:header] } }
86
+ lines = ["<table>"]
87
+
88
+ if has_header
89
+ header_rows, body_rows = rows_data.partition { |r| r[:cells].all? { |c| c[:header] } }
90
+
91
+ unless header_rows.empty?
92
+ lines << "<thead>"
93
+ header_rows.each { |row| lines << html_row(row, force_header: true) }
94
+ lines << "</thead>"
95
+ end
96
+
97
+ unless body_rows.empty?
98
+ lines << "<tbody>"
99
+ body_rows.each { |row| lines << html_row(row) }
100
+ lines << "</tbody>"
101
+ end
102
+ else
103
+ rows_data.each { |row| lines << html_row(row) }
104
+ end
105
+
106
+ lines << "</table>"
107
+ "\n\n#{lines.join("\n")}\n\n"
108
+ end
109
+
110
+ # Render a single HTML table row
111
+ def html_row(row, force_header: false)
112
+ cells_html =
113
+ row[:cells].map do |cell|
114
+ tag = (cell[:header] || force_header) ? "th" : "td"
115
+ "<#{tag}>#{cell[:content]}</#{tag}>"
116
+ end
117
+
118
+ "<tr>#{cells_html.join}</tr>"
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -29,7 +29,7 @@ module Markbridge
29
29
  # end
30
30
  # end
31
31
  class UploadTag < Tag
32
- def render(element, interface)
32
+ def render(element, _interface)
33
33
  # Return raw Markdown if available, otherwise reconstruct
34
34
  return element.raw if element.raw
35
35
 
@@ -30,6 +30,9 @@ require_relative "discourse/tags/spoiler_tag"
30
30
  require_relative "discourse/tags/strikethrough_tag"
31
31
  require_relative "discourse/tags/subscript_tag"
32
32
  require_relative "discourse/tags/superscript_tag"
33
+ require_relative "discourse/tags/table_tag"
34
+ require_relative "discourse/tags/table_row_tag"
35
+ require_relative "discourse/tags/table_cell_tag"
33
36
  require_relative "discourse/tags/underline_tag"
34
37
  require_relative "discourse/tags/url_tag"
35
38
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Markbridge
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.1"
5
5
  end
data/lib/markbridge.rb CHANGED
@@ -16,13 +16,8 @@ module Markbridge
16
16
  # @param handlers [HandlerRegistry, nil] custom handler registry or use default
17
17
  # @return [AST::Document]
18
18
  def parse_bbcode(input, handlers: nil)
19
- raise ArgumentError, "input cannot be nil" if input.nil?
20
-
21
- input = input.to_s # Coerce to string
22
19
  handlers ||= default_handlers
23
-
24
- parser = Parsers::BBCode::Parser.new(handlers:)
25
- parser.parse(input)
20
+ parse_with(Parsers::BBCode::Parser, input, handlers:)
26
21
  end
27
22
 
28
23
  # Convert BBCode to Discourse Markdown
@@ -31,17 +26,8 @@ module Markbridge
31
26
  # @param tag_library [TagLibrary, nil] custom tag library or use default
32
27
  # @return [String] Markdown output
33
28
  def bbcode_to_markdown(input, handlers: nil, tag_library: nil)
34
- raise ArgumentError, "input cannot be nil" if input.nil?
35
-
36
- handlers ||= default_handlers
37
- tag_library ||= default_tag_library
38
-
39
29
  ast = parse_bbcode(input, handlers:)
40
- renderer = build_renderer(tag_library:)
41
-
42
- # Clean up output
43
- result = renderer.render(ast)
44
- cleanup_markdown(result)
30
+ render_to_markdown(ast, tag_library:)
45
31
  end
46
32
 
47
33
  # Parse HTML to AST
@@ -49,13 +35,8 @@ module Markbridge
49
35
  # @param handlers [HandlerRegistry, nil] custom handler registry or use default
50
36
  # @return [AST::Document]
51
37
  def parse_html(input, handlers: nil)
52
- raise ArgumentError, "input cannot be nil" if input.nil?
53
-
54
- input = input.to_s # Coerce to string
55
38
  handlers ||= default_html_handlers
56
-
57
- parser = Parsers::HTML::Parser.new(handlers:)
58
- parser.parse(input)
39
+ parse_with(Parsers::HTML::Parser, input, handlers:)
59
40
  end
60
41
 
61
42
  # Convert HTML to Discourse Markdown
@@ -64,17 +45,8 @@ module Markbridge
64
45
  # @param tag_library [TagLibrary, nil] custom tag library or use default
65
46
  # @return [String] Markdown output
66
47
  def html_to_markdown(input, handlers: nil, tag_library: nil)
67
- raise ArgumentError, "input cannot be nil" if input.nil?
68
-
69
- handlers ||= default_html_handlers
70
- tag_library ||= default_tag_library
71
-
72
48
  ast = parse_html(input, handlers:)
73
- renderer = build_renderer(tag_library:)
74
-
75
- # Clean up output
76
- result = renderer.render(ast)
77
- cleanup_markdown(result)
49
+ render_to_markdown(ast, tag_library:)
78
50
  end
79
51
 
80
52
  # Parse s9e/TextFormatter XML to AST
@@ -82,13 +54,8 @@ module Markbridge
82
54
  # @param handlers [Parsers::TextFormatter::HandlerRegistry, nil] custom handler registry or use default
83
55
  # @return [AST::Document]
84
56
  def parse_text_formatter_xml(input, handlers: nil)
85
- raise ArgumentError, "input cannot be nil" if input.nil?
86
-
87
- input = input.to_s
88
57
  handlers ||= default_text_formatter_handlers
89
-
90
- parser = Parsers::TextFormatter::Parser.new(handlers:)
91
- parser.parse(input)
58
+ parse_with(Parsers::TextFormatter::Parser, input, handlers:)
92
59
  end
93
60
 
94
61
  # Convert s9e/TextFormatter XML to Discourse Markdown
@@ -97,16 +64,8 @@ module Markbridge
97
64
  # @param tag_library [TagLibrary, nil] custom tag library or use default
98
65
  # @return [String] Markdown output
99
66
  def text_formatter_xml_to_markdown(input, handlers: nil, tag_library: nil)
100
- raise ArgumentError, "input cannot be nil" if input.nil?
101
-
102
- handlers ||= default_text_formatter_handlers
103
- tag_library ||= default_tag_library
104
-
105
67
  ast = parse_text_formatter_xml(input, handlers:)
106
- renderer = build_renderer(tag_library:)
107
-
108
- result = renderer.render(ast)
109
- cleanup_markdown(result)
68
+ render_to_markdown(ast, tag_library:)
110
69
  end
111
70
 
112
71
  # Parse MediaWiki wikitext to AST
@@ -125,15 +84,8 @@ module Markbridge
125
84
  # @param tag_library [TagLibrary, nil] custom tag library or use default
126
85
  # @return [String] Markdown output
127
86
  def mediawiki_to_markdown(input, tag_library: nil)
128
- raise ArgumentError, "input cannot be nil" if input.nil?
129
-
130
- tag_library ||= default_tag_library
131
-
132
87
  ast = parse_mediawiki(input)
133
- renderer = build_renderer(tag_library:)
134
-
135
- result = renderer.render(ast)
136
- cleanup_markdown(result)
88
+ render_to_markdown(ast, tag_library:)
137
89
  end
138
90
 
139
91
  # Get default handler registry
@@ -183,6 +135,19 @@ module Markbridge
183
135
 
184
136
  private
185
137
 
138
+ def parse_with(parser_class, input, handlers:)
139
+ raise ArgumentError, "input cannot be nil" if input.nil?
140
+
141
+ parser = parser_class.new(handlers:)
142
+ parser.parse(input.to_s)
143
+ end
144
+
145
+ def render_to_markdown(ast, tag_library: nil)
146
+ tag_library ||= default_tag_library
147
+ renderer = build_renderer(tag_library:)
148
+ cleanup_markdown(renderer.render(ast))
149
+ end
150
+
186
151
  def build_renderer(tag_library:)
187
152
  escaper =
188
153
  Renderers::Discourse::MarkdownEscaper.new(
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: markbridge
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Discourse Team
@@ -47,6 +47,7 @@ files:
47
47
  - lib/markbridge/ast/strikethrough.rb
48
48
  - lib/markbridge/ast/subscript.rb
49
49
  - lib/markbridge/ast/superscript.rb
50
+ - lib/markbridge/ast/table.rb
50
51
  - lib/markbridge/ast/text.rb
51
52
  - lib/markbridge/ast/underline.rb
52
53
  - lib/markbridge/ast/upload.rb
@@ -75,6 +76,9 @@ files:
75
76
  - lib/markbridge/parsers/bbcode/handlers/simple_handler.rb
76
77
  - lib/markbridge/parsers/bbcode/handlers/size_handler.rb
77
78
  - lib/markbridge/parsers/bbcode/handlers/spoiler_handler.rb
79
+ - lib/markbridge/parsers/bbcode/handlers/table_cell_handler.rb
80
+ - lib/markbridge/parsers/bbcode/handlers/table_handler.rb
81
+ - lib/markbridge/parsers/bbcode/handlers/table_row_handler.rb
78
82
  - lib/markbridge/parsers/bbcode/handlers/url_handler.rb
79
83
  - lib/markbridge/parsers/bbcode/parser.rb
80
84
  - lib/markbridge/parsers/bbcode/parser_state.rb
@@ -96,6 +100,9 @@ files:
96
100
  - lib/markbridge/parsers/html/handlers/quote_handler.rb
97
101
  - lib/markbridge/parsers/html/handlers/raw_handler.rb
98
102
  - lib/markbridge/parsers/html/handlers/simple_handler.rb
103
+ - lib/markbridge/parsers/html/handlers/table_cell_handler.rb
104
+ - lib/markbridge/parsers/html/handlers/table_handler.rb
105
+ - lib/markbridge/parsers/html/handlers/table_row_handler.rb
99
106
  - lib/markbridge/parsers/html/handlers/url_handler.rb
100
107
  - lib/markbridge/parsers/html/parser.rb
101
108
  - lib/markbridge/parsers/media_wiki.rb
@@ -112,6 +119,7 @@ files:
112
119
  - lib/markbridge/parsers/text_formatter/handlers/list_handler.rb
113
120
  - lib/markbridge/parsers/text_formatter/handlers/quote_handler.rb
114
121
  - lib/markbridge/parsers/text_formatter/handlers/simple_handler.rb
122
+ - lib/markbridge/parsers/text_formatter/handlers/table_cell_handler.rb
115
123
  - lib/markbridge/parsers/text_formatter/handlers/url_handler.rb
116
124
  - lib/markbridge/parsers/text_formatter/parser.rb
117
125
  - lib/markbridge/processors.rb
@@ -154,6 +162,9 @@ files:
154
162
  - lib/markbridge/renderers/discourse/tags/strikethrough_tag.rb
155
163
  - lib/markbridge/renderers/discourse/tags/subscript_tag.rb
156
164
  - lib/markbridge/renderers/discourse/tags/superscript_tag.rb
165
+ - lib/markbridge/renderers/discourse/tags/table_cell_tag.rb
166
+ - lib/markbridge/renderers/discourse/tags/table_row_tag.rb
167
+ - lib/markbridge/renderers/discourse/tags/table_tag.rb
157
168
  - lib/markbridge/renderers/discourse/tags/underline_tag.rb
158
169
  - lib/markbridge/renderers/discourse/tags/upload_tag.rb
159
170
  - lib/markbridge/renderers/discourse/tags/url_tag.rb