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/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/parsers/media_wiki.rb,
lib/markbridge/ast/horizontal_rule.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/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, Parse

Constant Summary collapse

VERSION =
"0.2.1"

Class Method Summary collapse

Class Method Details

.bbcode_to_markdown(input, handlers: nil, renderer: nil, raise_on_error: 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:

Yield Parameters:

Returns:



48
49
50
51
52
# File 'lib/markbridge.rb', line 48

def bbcode_to_markdown(input, handlers: nil, renderer: nil, raise_on_error: true)
  parse = parse_bbcode(input, handlers:)
  yield(parse.ast) if block_given?
  build_conversion(parse, renderer:, raise_on_error:)
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:



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

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:



264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
# File 'lib/markbridge.rb', line 264

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) {|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:



89
90
91
92
93
# File 'lib/markbridge.rb', line 89

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

.mediawiki_to_markdown(input, handlers: nil, renderer: nil, raise_on_error: 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:



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

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

.parse_bbcode(input, handlers: nil) ⇒ Parse

Parse BBCode to AST.

Parameters:

Returns:

Raises:

  • (ArgumentError)


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

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)


63
64
65
66
67
68
69
70
# File 'lib/markbridge.rb', line 63

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)


137
138
139
140
141
142
143
144
# File 'lib/markbridge.rb', line 137

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)


102
103
104
105
106
107
108
109
110
# File 'lib/markbridge.rb', line 102

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) ⇒ 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)

Returns:

Raises:

  • (ArgumentError)


215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/markbridge.rb', line 215

def render(parse_or_ast, format: :discourse, renderer: nil, raise_on_error: 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:)
end

.text_formatter_xml_to_markdown(input, handlers: nil, renderer: nil, raise_on_error: 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:



126
127
128
129
130
# File 'lib/markbridge.rb', line 126

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