Class: Markbridge::Parsers::TextFormatter::HandlerRegistry

Inherits:
Object
  • Object
show all
Defined in:
lib/markbridge/parsers/text_formatter/handler_registry.rb

Overview

Registry of s9e/TextFormatter XML element handlers

Manages mappings between XML element names and handler objects. Handlers must respond to #process(element:, parent:) and return the created element if children should be processed, or nil otherwise.

Examples:

Using default mappings

registry = HandlerRegistry.default
element = registry.process_element(xml_element, parent)

Customizing mappings with handler objects

registry = HandlerRegistry.build_from_default do |r|
  r.register("CUSTOM", MyCustomHandler.new)
  r.register("B", SimpleHandler.new(AST::Bold))  # Override default
end

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeHandlerRegistry

Returns a new instance of HandlerRegistry.



54
55
56
# File 'lib/markbridge/parsers/text_formatter/handler_registry.rb', line 54

def initialize
  @mappings = {}
end

Class Method Details

.build_from_default {|HandlerRegistry| ... } ⇒ HandlerRegistry

Build from default mappings with custom additions

Yields:

Returns:



31
32
33
# File 'lib/markbridge/parsers/text_formatter/handler_registry.rb', line 31

def self.build_from_default
  default.tap { |registry| yield registry if block_given? }
end

.defaultHandlerRegistry

Create a new registry with default mappings

Returns:



24
25
26
# File 'lib/markbridge/parsers/text_formatter/handler_registry.rb', line 24

def self.default
  new.tap(&:register_defaults)
end

.shared_defaultHandlerRegistry

Shared, deep-frozen default registry for the no-customization fast path. Built once per process; Parser falls back to it when no handlers: are given, skipping the full registry construction on every parse. Handlers are stateless after construction, so sharing is safe across parsers and threads.

Returns:



42
43
44
# File 'lib/markbridge/parsers/text_formatter/handler_registry.rb', line 42

def self.shared_default
  @shared_default ||= default.freeze
end

Instance Method Details

#[](element_name) ⇒ #process?

Look up the handler for an element name (case-insensitive).

Parameters:

  • element_name (String)

Returns:

  • (#process, nil)


68
69
70
71
72
73
# File 'lib/markbridge/parsers/text_formatter/handler_registry.rb', line 68

def [](element_name)
  # Keys are always upcased and s9e/TextFormatter stores its tag
  # names in uppercase, so the fetch hits directly on the hot
  # path; the block normalizes only unusual lookups.
  @mappings.fetch(element_name) { @mappings[element_name.upcase] }
end

#freezeObject

Freeze the registry together with its internal Hash so that registration on a shared instance fails loudly instead of silently mutating state visible to every parser.



49
50
51
52
# File 'lib/markbridge/parsers/text_formatter/handler_registry.rb', line 49

def freeze
  @mappings.freeze
  super
end

#has_handler?(element_name) ⇒ Boolean

Check if a handler is registered for an element

Parameters:

  • element_name (String)

    XML element name

Returns:

  • (Boolean)

    true if handler is registered



93
94
95
# File 'lib/markbridge/parsers/text_formatter/handler_registry.rb', line 93

def has_handler?(element_name)
  @mappings.key?(element_name.upcase)
end

#overlay(element_names) {|previous| ... } ⇒ self

Replace the handler bound to one or more element names by yielding the previously-bound handler (which may be nil) and registering whatever the block returns.

Parameters:

  • element_names (String, Array<String>)

Yield Parameters:

  • previous (#process, nil)

Returns:

  • (self)


82
83
84
85
86
87
88
# File 'lib/markbridge/parsers/text_formatter/handler_registry.rb', line 82

def overlay(element_names)
  Array(element_names).each do |name|
    previous = self[name]
    register(name, yield(previous))
  end
  self
end

#process_element(element, parent, processor) ⇒ AST::Element?

Process an XML element using the registered handler

Parameters:

  • element (Nokogiri::XML::Element)
  • parent (AST::Element)

    parent node to add children to

  • processor (Parser)

    the parser, exposed to handlers so they can call back into process_children for nested content

Returns:

  • (AST::Element, nil)

    the created element if children should be processed, nil otherwise



103
104
105
106
# File 'lib/markbridge/parsers/text_formatter/handler_registry.rb', line 103

def process_element(element, parent, processor)
  handler = self[element.name]
  handler&.process(element:, parent:, processor:)
end

#register(element_name, handler) ⇒ Object

Register a handler for an element

Parameters:

  • element_name (String)

    XML element name (case-insensitive)

  • handler (#process)

    Handler object responding to process(element:, parent:)



61
62
63
# File 'lib/markbridge/parsers/text_formatter/handler_registry.rb', line 61

def register(element_name, handler)
  @mappings[element_name.upcase] = handler
end

#register_defaultsObject

Register all default s9e/TextFormatter element mappings



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/markbridge/parsers/text_formatter/handler_registry.rb', line 109

def register_defaults
  # Simple formatting elements
  register("B", Handlers::SimpleHandler.new(AST::Bold))
  register("I", Handlers::SimpleHandler.new(AST::Italic))
  register("U", Handlers::SimpleHandler.new(AST::Underline))
  register("S", Handlers::SimpleHandler.new(AST::Strikethrough))

  # Complex elements with attributes
  register("URL", Handlers::UrlHandler.new)
  register("EMAIL", Handlers::EmailHandler.new)
  register("CODE", Handlers::CodeHandler.new)
  register("QUOTE", Handlers::QuoteHandler.new)
  register("IMG", Handlers::ImageHandler.new)
  register("LIST", Handlers::ListHandler.new)
  register("COLOR", Handlers::AttributeHandler.new(AST::Color, attribute: :color))
  register("SIZE", Handlers::AttributeHandler.new(AST::Size, attribute: :size))
  register(
    "ALIGN",
    Handlers::AttributeHandler.new(AST::Align, attribute: :align, param: :alignment),
  )
  register("SPOILER", Handlers::AttributeHandler.new(AST::Spoiler, attribute: :title))
  register("ATTACHMENT", Handlers::AttachmentHandler.new)
  register("ATTACH", Handlers::AttachmentHandler.new)

  # List item (supports both LI and * for compatibility)
  register("LI", Handlers::SimpleHandler.new(AST::ListItem))
  register("*", Handlers::SimpleHandler.new(AST::ListItem))

  # Paragraphs
  register("P", Handlers::SimpleHandler.new(AST::Paragraph))

  # Table elements
  register("TABLE", Handlers::SimpleHandler.new(AST::Table))
  register("TR", Handlers::SimpleHandler.new(AST::TableRow))
  register("TD", Handlers::TableCellHandler.new)
  register("TH", Handlers::TableCellHandler.new)
end