Module: Markbridge

Defined in:
lib/markbridge.rb,
lib/markbridge/ast.rb,
lib/markbridge/parse.rb,
lib/markbridge/ast/url.rb,
lib/markbridge/version.rb,
lib/markbridge/ast/bold.rb,
lib/markbridge/ast/code.rb,
lib/markbridge/ast/list.rb,
lib/markbridge/ast/node.rb,
lib/markbridge/ast/poll.rb,
lib/markbridge/ast/size.rb,
lib/markbridge/ast/text.rb,
lib/markbridge/ast/align.rb,
lib/markbridge/ast/color.rb,
lib/markbridge/ast/email.rb,
lib/markbridge/ast/event.rb,
lib/markbridge/ast/image.rb,
lib/markbridge/ast/quote.rb,
lib/markbridge/ast/table.rb,
lib/markbridge/ast/italic.rb,
lib/markbridge/ast/upload.rb,
lib/markbridge/conversion.rb,
lib/markbridge/gem_loader.rb,
lib/markbridge/normalizer.rb,
lib/markbridge/ast/details.rb,
lib/markbridge/ast/element.rb,
lib/markbridge/ast/heading.rb,
lib/markbridge/ast/mention.rb,
lib/markbridge/ast/spoiler.rb,
lib/markbridge/ast/document.rb,
lib/markbridge/parsers/html.rb,
lib/markbridge/ast/list_item.rb,
lib/markbridge/ast/paragraph.rb,
lib/markbridge/ast/subscript.rb,
lib/markbridge/ast/underline.rb,
lib/markbridge/ast/attachment.rb,
lib/markbridge/ast/line_break.rb,
lib/markbridge/parsers/bbcode.rb,
lib/markbridge/ast/superscript.rb,
lib/markbridge/ast/markdown_text.rb,
lib/markbridge/ast/strikethrough.rb,
lib/markbridge/normalizer/report.rb,
lib/markbridge/normalizer/walker.rb,
lib/markbridge/parsers/media_wiki.rb,
lib/markbridge/ast/horizontal_rule.rb,
lib/markbridge/normalizer/rule_set.rb,
lib/markbridge/parsers/html/parser.rb,
lib/markbridge/renderers/discourse.rb,
lib/markbridge/parsers/bbcode/parser.rb,
lib/markbridge/parsers/bbcode/scanner.rb,
lib/markbridge/parsers/text_formatter.rb,
lib/markbridge/renderers/discourse/tag.rb,
lib/markbridge/parsers/media_wiki/parser.rb,
lib/markbridge/normalizer/text_projection.rb,
lib/markbridge/parsers/bbcode/parser_state.rb,
lib/markbridge/parsers/bbcode/tokens/token.rb,
lib/markbridge/renderers/discourse/renderer.rb,
lib/markbridge/parsers/html/handler_registry.rb,
lib/markbridge/parsers/text_formatter/parser.rb,
lib/markbridge/parsers/bbcode/handler_registry.rb,
lib/markbridge/renderers/discourse/tag_library.rb,
lib/markbridge/parsers/bbcode/tokens/text_token.rb,
lib/markbridge/parsers/media_wiki/inline_parser.rb,
lib/markbridge/renderers/discourse/html_escaper.rb,
lib/markbridge/renderers/discourse/tags/url_tag.rb,
lib/markbridge/parsers/bbcode/raw_content_result.rb,
lib/markbridge/parsers/html/handlers/raw_handler.rb,
lib/markbridge/parsers/html/handlers/url_handler.rb,
lib/markbridge/renderers/discourse/postprocessor.rb,
lib/markbridge/renderers/discourse/tags/bold_tag.rb,
lib/markbridge/renderers/discourse/tags/code_tag.rb,
lib/markbridge/renderers/discourse/tags/list_tag.rb,
lib/markbridge/renderers/discourse/tags/poll_tag.rb,
lib/markbridge/renderers/discourse/tags/size_tag.rb,
lib/markbridge/parsers/bbcode/peekable_enumerator.rb,
lib/markbridge/parsers/html/handlers/base_handler.rb,
lib/markbridge/parsers/html/handlers/list_handler.rb,
lib/markbridge/parsers/html/handlers/span_handler.rb,
lib/markbridge/renderers/discourse/render_context.rb,
lib/markbridge/renderers/discourse/tags/align_tag.rb,
lib/markbridge/renderers/discourse/tags/color_tag.rb,
lib/markbridge/renderers/discourse/tags/email_tag.rb,
lib/markbridge/renderers/discourse/tags/event_tag.rb,
lib/markbridge/renderers/discourse/tags/image_tag.rb,
lib/markbridge/renderers/discourse/tags/quote_tag.rb,
lib/markbridge/renderers/discourse/tags/table_tag.rb,
lib/markbridge/parsers/bbcode/handlers/raw_handler.rb,
lib/markbridge/parsers/bbcode/handlers/url_handler.rb,
lib/markbridge/parsers/bbcode/tokens/tag_end_token.rb,
lib/markbridge/parsers/html/handlers/image_handler.rb,
lib/markbridge/parsers/html/handlers/quote_handler.rb,
lib/markbridge/parsers/html/handlers/table_handler.rb,
lib/markbridge/renderers/discourse/tags/italic_tag.rb,
lib/markbridge/renderers/discourse/tags/upload_tag.rb,
lib/markbridge/parsers/bbcode/handlers/base_handler.rb,
lib/markbridge/parsers/bbcode/handlers/code_handler.rb,
lib/markbridge/parsers/bbcode/handlers/list_handler.rb,
lib/markbridge/parsers/bbcode/handlers/size_handler.rb,
lib/markbridge/parsers/bbcode/raw_content_collector.rb,
lib/markbridge/parsers/html/handlers/simple_handler.rb,
lib/markbridge/renderers/discourse/identity_escaper.rb,
lib/markbridge/renderers/discourse/markdown_escaper.rb,
lib/markbridge/renderers/discourse/tags/details_tag.rb,
lib/markbridge/renderers/discourse/tags/heading_tag.rb,
lib/markbridge/renderers/discourse/tags/mention_tag.rb,
lib/markbridge/renderers/discourse/tags/spoiler_tag.rb,
lib/markbridge/parsers/bbcode/handlers/align_handler.rb,
lib/markbridge/parsers/bbcode/handlers/color_handler.rb,
lib/markbridge/parsers/bbcode/handlers/email_handler.rb,
lib/markbridge/parsers/bbcode/handlers/image_handler.rb,
lib/markbridge/parsers/bbcode/handlers/quote_handler.rb,
lib/markbridge/parsers/bbcode/handlers/table_handler.rb,
lib/markbridge/parsers/bbcode/tokens/tag_start_token.rb,
lib/markbridge/parsers/bbcode/closing_strategies/base.rb,
lib/markbridge/parsers/bbcode/handlers/simple_handler.rb,
lib/markbridge/parsers/media_wiki/inline_tag_registry.rb,
lib/markbridge/renderers/discourse/tags/list_item_tag.rb,
lib/markbridge/renderers/discourse/tags/paragraph_tag.rb,
lib/markbridge/renderers/discourse/tags/subscript_tag.rb,
lib/markbridge/renderers/discourse/tags/table_row_tag.rb,
lib/markbridge/renderers/discourse/tags/underline_tag.rb,
lib/markbridge/parsers/bbcode/handlers/spoiler_handler.rb,
lib/markbridge/parsers/html/handlers/list_item_handler.rb,
lib/markbridge/parsers/html/handlers/paragraph_handler.rb,
lib/markbridge/parsers/html/handlers/table_row_handler.rb,
lib/markbridge/parsers/text_formatter/handler_registry.rb,
lib/markbridge/renderers/discourse/rendering_interface.rb,
lib/markbridge/renderers/discourse/tags/attachment_tag.rb,
lib/markbridge/renderers/discourse/tags/line_break_tag.rb,
lib/markbridge/renderers/discourse/tags/table_cell_tag.rb,
lib/markbridge/parsers/bbcode/closing_strategies/strict.rb,
lib/markbridge/parsers/html/handlers/table_cell_handler.rb,
lib/markbridge/renderers/discourse/tags/superscript_tag.rb,
lib/markbridge/parsers/bbcode/handlers/list_item_handler.rb,
lib/markbridge/parsers/bbcode/handlers/table_row_handler.rb,
lib/markbridge/parsers/bbcode/handlers/attachment_handler.rb,
lib/markbridge/parsers/bbcode/handlers/table_cell_handler.rb,
lib/markbridge/parsers/html/handlers/self_closing_handler.rb,
lib/markbridge/renderers/discourse/tags/strikethrough_tag.rb,
lib/markbridge/parsers/text_formatter/handlers/url_handler.rb,
lib/markbridge/parsers/bbcode/closing_strategies/reordering.rb,
lib/markbridge/parsers/bbcode/handlers/self_closing_handler.rb,
lib/markbridge/parsers/text_formatter/handlers/base_handler.rb,
lib/markbridge/parsers/text_formatter/handlers/code_handler.rb,
lib/markbridge/parsers/text_formatter/handlers/list_handler.rb,
lib/markbridge/renderers/discourse/tags/horizontal_rule_tag.rb,
lib/markbridge/parsers/text_formatter/handlers/email_handler.rb,
lib/markbridge/parsers/text_formatter/handlers/image_handler.rb,
lib/markbridge/parsers/text_formatter/handlers/quote_handler.rb,
lib/markbridge/parsers/bbcode/errors/max_depth_exceeded_error.rb,
lib/markbridge/parsers/text_formatter/handlers/simple_handler.rb,
lib/markbridge/renderers/discourse/builders/list_item_builder.rb,
lib/markbridge/parsers/bbcode/closing_strategies/tag_reconciler.rb,
lib/markbridge/parsers/text_formatter/handlers/attribute_handler.rb,
lib/markbridge/parsers/text_formatter/handlers/attachment_handler.rb,
lib/markbridge/parsers/text_formatter/handlers/table_cell_handler.rb

Defined Under Namespace

Modules: AST, GemLoader, Parsers, Renderers Classes: Conversion, Normalizer, Parse

Constant Summary collapse

VERSION =
"0.3.1"

Class Method Summary collapse

Class Method Details

.bbcode_to_markdown(input, handlers: nil, renderer: nil, raise_on_error: true, normalize: true) {|ast| ... } ⇒ Conversion

Convert BBCode to Discourse Markdown.

If a block is given, it is called with the parsed AST between parse and render — the caller can append/remove/replace nodes before rendering. Mutations to the yielded AST persist in Markbridge::Conversion#ast.

Parameters:

  • input (String)

    BBCode source

  • handlers (Parsers::BBCode::HandlerRegistry, nil) (defaults to: nil)

    custom handlers

  • renderer (Renderers::Discourse::Renderer, nil) (defaults to: nil)

    custom renderer (build with discourse_renderer); defaults to a fresh default Renderer

  • raise_on_error (Boolean) (defaults to: true)

    when true (default), let render-time exceptions propagate; when false, swallow them, return a Conversion with an empty markdown string, and surface the exceptions via Markbridge::Conversion#errors.

  • normalize (Boolean, Normalizer) (defaults to: true)

    apply target-format nesting rules between the yield hook and render. true (default) uses the shared default normalizer; a Normalizer is used as-is; false skips normalization. See Normalizer.

Yield Parameters:

Returns:



53
54
55
56
57
58
59
60
61
62
63
# File 'lib/markbridge.rb', line 53

def bbcode_to_markdown(
  input,
  handlers: nil,
  renderer: nil,
  raise_on_error: true,
  normalize: true
)
  parse = parse_bbcode(input, handlers:)
  yield(parse.ast) if block_given?
  build_conversion(parse, renderer:, raise_on_error:, normalize:)
end

.convert(input, format:, **kwargs) {|ast| ... } ⇒ Conversion

Convert input in the given format. Thin dispatcher over the four *_to_markdown methods; useful when the format is data- driven (e.g. iterating posts whose :format column varies). An optional block is forwarded to the dispatched method.

Parameters:

  • input (String, Nokogiri::XML::Node)

    source content; the HTML and TextFormatter dispatch targets also accept pre-parsed Nokogiri trees.

  • format (Symbol)

    one of :bbcode, :html, :text_formatter_xml, :mediawiki

  • kwargs (Hash)

    forwarded to the underlying convenience method (e.g. handlers:, renderer:, raise_on_error:).

Yield Parameters:

Returns:



205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'lib/markbridge.rb', line 205

def convert(input, format:, **kwargs, &block)
  case format
  when :bbcode
    bbcode_to_markdown(input, **kwargs, &block)
  when :html
    html_to_markdown(input, **kwargs, &block)
  when :text_formatter_xml
    text_formatter_xml_to_markdown(input, **kwargs, &block)
  when :mediawiki
    mediawiki_to_markdown(input, **kwargs, &block)
  else
    raise ArgumentError,
          "unknown format #{format.inspect} " \
            "(expected :bbcode, :html, :text_formatter_xml, or :mediawiki)"
  end
end

.discourse_renderer(tags: nil, tag_library: nil, unregister: nil, escaper: nil, escape: true, escape_hard_line_breaks: false, allow: nil, postprocessor: nil, strip_trailing_invisibles: false) ⇒ Renderers::Discourse::Renderer

Build a configured Discourse Markbridge::Renderers::Discourse::Renderer for use with the renderer: kwarg on the *_to_markdown convenience methods.

Parameters:

  • tags (Hash{Class => Tag, nil}, nil) (defaults to: nil)

    mappings to merge on top of the default library; nil values unregister the class.

  • tag_library (Renderers::Discourse::TagLibrary, nil) (defaults to: nil)

    base library to start from. Defaults to a fresh TagLibrary.default. When supplied, it is +dup+'d before any tags: / unregister: mutation, so the caller's library is left untouched.

  • unregister (Array<Class>, nil) (defaults to: nil)

    AST classes to drop from the library so they fall through to render_children.

  • escaper (#escape, nil) (defaults to: nil)

    when given, used as-is; escape:, escape_hard_line_breaks:, and allow: are then ignored.

  • escape (Boolean) (defaults to: true)

    when false, the renderer is built with Markbridge::Renderers::Discourse::IdentityEscaper (no Markdown escaping). Mutually exclusive with escape_hard_line_breaks: / allow:.

  • escape_hard_line_breaks (Boolean) (defaults to: false)

    forwarded to a fresh MarkdownEscaper when no explicit escaper: is given.

  • allow (Symbol, Array<Symbol>, nil) (defaults to: nil)

    block-level constructs to pass through unescaped (e.g. :lists, :bullet_list, :ordered_list, :atx_heading, :block_quote); forwarded to a fresh MarkdownEscaper.

  • postprocessor (Renderers::Discourse::Postprocessor, nil) (defaults to: nil)

    when given, used as-is; strip_trailing_invisibles: is then ignored.

  • strip_trailing_invisibles (Boolean) (defaults to: false)

    forwarded to a fresh Markbridge::Renderers::Discourse::Postprocessor when no explicit postprocessor: is given. Strips NBSP and zero-width format characters from the end of each line.

Returns:



299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
# File 'lib/markbridge.rb', line 299

def discourse_renderer(
  tags: nil,
  tag_library: nil,
  unregister: nil,
  escaper: nil,
  escape: true,
  escape_hard_line_breaks: false,
  allow: nil,
  postprocessor: nil,
  strip_trailing_invisibles: false
)
  # Dup the caller's library before mutating so successive
  # +discourse_renderer+ calls against the same +tag_library:+ don't
  # see each other's overrides. +TagLibrary.default+ already returns
  # a fresh instance, so the dup is only needed in the explicit
  # +tag_library:+ branch.
  library = tag_library ? tag_library.dup : Renderers::Discourse::TagLibrary.default
  library.merge!(tags) if tags
  Array(unregister).each { |klass| library.unregister(klass) }

  escaper ||= build_escaper(escape:, escape_hard_line_breaks:, allow:)
  postprocessor ||= Renderers::Discourse::Postprocessor.new(strip_trailing_invisibles:)

  Renderers::Discourse::Renderer.new(tag_library: library, escaper:, postprocessor:)
end

.html_to_markdown(input, handlers: nil, renderer: nil, raise_on_error: true, normalize: true) {|ast| ... } ⇒ Conversion

Convert HTML to Discourse Markdown.

If a block is given, it is called with the parsed AST between parse and render — the caller can append/remove/replace nodes before rendering. Mutations to the yielded AST persist in Markbridge::Conversion#ast.

Parameters:

Yield Parameters:

Returns:



101
102
103
104
105
# File 'lib/markbridge.rb', line 101

def html_to_markdown(input, handlers: nil, renderer: nil, raise_on_error: true, normalize: true)
  parse = parse_html(input, handlers:)
  yield(parse.ast) if block_given?
  build_conversion(parse, renderer:, raise_on_error:, normalize:)
end

.mediawiki_to_markdown(input, handlers: nil, renderer: nil, raise_on_error: true, normalize: true) {|ast| ... } ⇒ Conversion

Convert MediaWiki wikitext to Discourse Markdown.

If a block is given, it is called with the parsed AST between parse and render — the caller can append/remove/replace nodes before rendering. Mutations to the yielded AST persist in Markbridge::Conversion#ast.

Parameters:

Yield Parameters:

Returns:



179
180
181
182
183
184
185
186
187
188
189
# File 'lib/markbridge.rb', line 179

def mediawiki_to_markdown(
  input,
  handlers: nil,
  renderer: nil,
  raise_on_error: true,
  normalize: true
)
  parse = parse_mediawiki(input, handlers:)
  yield(parse.ast) if block_given?
  build_conversion(parse, renderer:, raise_on_error:, normalize:)
end

.parse_bbcode(input, handlers: nil) ⇒ Parse

Parse BBCode to AST.

Parameters:

Returns:

Raises:

  • (ArgumentError)


18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/markbridge.rb', line 18

def parse_bbcode(input, handlers: nil)
  raise ArgumentError, "input cannot be nil" if input.nil?

  parser = Parsers::BBCode::Parser.new(handlers:)
  ast = parser.parse(input.to_s)

  Parse.new(
    ast:,
    format: :bbcode,
    unknown_tags: parser.unknown_tags,
    diagnostics: bbcode_diagnostics(parser),
  )
end

.parse_html(input, handlers: nil) ⇒ Parse

Parse HTML to AST.

Parameters:

  • input (String, Nokogiri::XML::Node)

    HTML source or pre-parsed Nokogiri tree (e.g. the DocumentFragment returned by Nokogiri::HTML.fragment). Passing a pre-parsed tree lets callers run their own Nokogiri-driven pre-processing without forcing Markbridge to re-parse the same bytes.

  • handlers (Parsers::HTML::HandlerRegistry, nil) (defaults to: nil)

    custom handlers

Returns:

Raises:

  • (ArgumentError)


74
75
76
77
78
79
80
81
# File 'lib/markbridge.rb', line 74

def parse_html(input, handlers: nil)
  raise ArgumentError, "input cannot be nil" if input.nil?

  parser = Parsers::HTML::Parser.new(handlers:)
  ast = parser.parse(input)

  Parse.new(ast:, format: :html, unknown_tags: parser.unknown_tags, diagnostics: {})
end

.parse_mediawiki(input, handlers: nil) ⇒ Parse

Parse MediaWiki wikitext to AST.

Parameters:

Returns:

Raises:

  • (ArgumentError)


156
157
158
159
160
161
162
163
# File 'lib/markbridge.rb', line 156

def parse_mediawiki(input, handlers: nil)
  raise ArgumentError, "input cannot be nil" if input.nil?

  parser = Parsers::MediaWiki::Parser.new(handlers:)
  ast = parser.parse(input.to_s)

  Parse.new(ast:, format: :mediawiki, unknown_tags: parser.unknown_tags, diagnostics: {})
end

.parse_text_formatter_xml(input, handlers: nil) ⇒ Parse

Parse s9e/TextFormatter XML to AST.

Parameters:

  • input (String, Nokogiri::XML::Node)

    XML source or pre-parsed Nokogiri tree. A Nokogiri::XML::Document is unwrapped via #root; any other node is treated as the root.

  • handlers (Parsers::TextFormatter::HandlerRegistry, nil) (defaults to: nil)

    custom handlers

Returns:

Raises:

  • (ArgumentError)


114
115
116
117
118
119
120
121
122
# File 'lib/markbridge.rb', line 114

def parse_text_formatter_xml(input, handlers: nil)
  raise ArgumentError, "input cannot be nil" if input.nil?

  parser = Parsers::TextFormatter::Parser.new(handlers:)
  ast = parser.parse(input)
  unknown_tags = parser.unknown_tags

  Parse.new(ast:, format: :text_formatter_xml, unknown_tags:, diagnostics: {})
end

.render(parse_or_ast, format: :discourse, renderer: nil, raise_on_error: true, normalize: true) ⇒ Conversion

Render a Parse or a bare AST node to Discourse Markdown. Useful when the caller has mutated the AST between parse and render (e.g. appending attachments not present in the source), or built an AST programmatically.

When given a Parse, the returned Conversion carries the parser's unknown_tags, diagnostics, and source format forward. When given an AST node, those fields default to empty and format is nil — there was no source document, so there is no source format to report. A bare node that isn't already a Markbridge::AST::Document is wrapped in one, so Markbridge::Conversion#ast is always a Document (and tree helpers like each_descendant are always available on it).

Parameters:

  • parse_or_ast (Parse, AST::Node)
  • format (Symbol) (defaults to: :discourse)

    :discourse (only renderer currently shipped)

  • renderer (Renderers::Discourse::Renderer, nil) (defaults to: nil)
  • raise_on_error (Boolean) (defaults to: true)
  • normalize (Boolean, Normalizer) (defaults to: true)

    see bbcode_to_markdown. Normalization is idempotent, so re-rendering an already-normalized Parse is a no-op.

Returns:

Raises:

  • (ArgumentError)


244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
# File 'lib/markbridge.rb', line 244

def render(
  parse_or_ast,
  format: :discourse,
  renderer: nil,
  raise_on_error: true,
  normalize: true
)
  raise ArgumentError, "unknown render format #{format.inspect}" unless format == :discourse

  parse =
    case parse_or_ast
    when Parse
      parse_or_ast
    when AST::Document
      Parse.new(ast: parse_or_ast, format: nil, unknown_tags: {}, diagnostics: {})
    when AST::Node
      document = AST::Document.new([parse_or_ast])
      Parse.new(ast: document, format: nil, unknown_tags: {}, diagnostics: {})
    else
      raise ArgumentError, "expected Parse or AST::Node, got #{parse_or_ast.class}"
    end

  build_conversion(parse, renderer:, raise_on_error:, normalize:)
end

.text_formatter_xml_to_markdown(input, handlers: nil, renderer: nil, raise_on_error: true, normalize: true) {|ast| ... } ⇒ Conversion

Convert s9e/TextFormatter XML to Discourse Markdown.

If a block is given, it is called with the parsed AST between parse and render — the caller can append/remove/replace nodes before rendering. Mutations to the yielded AST persist in Markbridge::Conversion#ast.

Parameters:

Yield Parameters:

Returns:



139
140
141
142
143
144
145
146
147
148
149
# File 'lib/markbridge.rb', line 139

def text_formatter_xml_to_markdown(
  input,
  handlers: nil,
  renderer: nil,
  raise_on_error: true,
  normalize: true
)
  parse = parse_text_formatter_xml(input, handlers:)
  yield(parse.ast) if block_given?
  build_conversion(parse, renderer:, raise_on_error:, normalize:)
end