class RDoc::Markup::AttributeManager
Constants
- NULL
The NUL character
Attributes
The attributes enabled for this markup object.
A bits of exclusive maps
This maps delimiters that occur around words (such as bold or tt) where the start and end delimiters and the same. This lets us optimize the regexp
A \ in front of a character that would normally be processed turns off processing. We do this by turning < into <#{PROTECT}
And this maps _regexp handling_ sequences to a name. A regexp handling sequence is something like a WikiWord
And this is used when the delimiters aren’t the same. In this case the hash maps a pattern to the attribute character
Public Class Methods
Creates a new attribute manager that understands bold, emphasized and teletype text.
# File lib/rdoc/markup/attribute_manager.rb, line 80 def initialize @html_tags = {} @matching_word_pairs = {} @protectable = %w[<] @regexp_handlings = [] @word_pair_map = {} @exclusive_bitmap = 0 @attributes = RDoc::Markup::Attributes.new add_word_pair "*", "*", :BOLD, true add_word_pair "_", "_", :EM, true add_word_pair "+", "+", :TT, true add_html "em", :EM, true add_html "i", :EM, true add_html "b", :BOLD, true add_html "tt", :TT, true add_html "code", :TT, true end
Public Instance Methods
Adds a markup class with name for words surrounded by HTML tag tag. To process emphasis tags:
am.add_html 'em', :EM
# File lib/rdoc/markup/attribute_manager.rb, line 283 def add_html(tag, name, exclusive = false) bitmap = @attributes.bitmap_for name @html_tags[tag.downcase] = bitmap @exclusive_bitmap |= bitmap if exclusive end
Adds a regexp handling for pattern with name. A simple URL handler would be:
@am.add_regexp_handling(/((https?:)\S+\w)/, :HYPERLINK)
# File lib/rdoc/markup/attribute_manager.rb, line 295 def add_regexp_handling pattern, name, exclusive = false bitmap = @attributes.bitmap_for(name) @regexp_handlings << [pattern, bitmap] @exclusive_bitmap |= bitmap if exclusive end
Adds a markup class with name for words wrapped in the start and stop character. To make words wrapped with “*” bold:
am.add_word_pair '*', '*', :BOLD
# File lib/rdoc/markup/attribute_manager.rb, line 258 def add_word_pair(start, stop, name, exclusive = false) raise ArgumentError, "Word flags may not start with '<'" if start[0,1] == '<' bitmap = @attributes.bitmap_for name if start == stop then @matching_word_pairs[start] = bitmap else pattern = /(#{Regexp.escape start})(\S+)(#{Regexp.escape stop})/ @word_pair_map[pattern] = bitmap end @protectable << start[0,1] @protectable.uniq! @exclusive_bitmap |= bitmap if exclusive end
Return an attribute object with the given turn_on and turn_off bits set
# File lib/rdoc/markup/attribute_manager.rb, line 103 def attribute(turn_on, turn_off) RDoc::Markup::AttrChanger.new turn_on, turn_off end
Changes the current attribute from current to new
# File lib/rdoc/markup/attribute_manager.rb, line 110 def change_attribute current, new diff = current ^ new attribute(new & diff, current & diff) end
Used by the tests to change attributes by name from current_set to new_set
# File lib/rdoc/markup/attribute_manager.rb, line 119 def changed_attribute_by_name current_set, new_set current = new = 0 current_set.each do |name| current |= @attributes.bitmap_for(name) end new_set.each do |name| new |= @attributes.bitmap_for(name) end change_attribute(current, new) end
Map attributes like textto the sequence 001002<char>001003<char>, where <char> is a per-attribute specific character
# File lib/rdoc/markup/attribute_manager.rb, line 153 def convert_attrs(str, attrs, exclusive = false) convert_attrs_matching_word_pairs(str, attrs, exclusive) convert_attrs_word_pair_map(str, attrs, exclusive) end
# File lib/rdoc/markup/attribute_manager.rb, line 158 def convert_attrs_matching_word_pairs(str, attrs, exclusive) # first do matching ones tags = @matching_word_pairs.select { |start, bitmap| exclusive == exclusive?(bitmap) }.keys return if tags.empty? tags = "[#{tags.join("")}](?!#{PROTECT_ATTR})" all_tags = "[#{@matching_word_pairs.keys.join("")}](?!#{PROTECT_ATTR})" re = /(?:^|\W|#{all_tags})\K(#{tags})(\1*[#\\]?[\w:#{PROTECT_ATTR}.\/\[\]-]+?\S?)\1(?!\1)(?=#{all_tags}|\W|$)/ 1 while str.gsub!(re) { |orig| a, w = (m = $~).values_at(1, 2) attr = @matching_word_pairs[a] if attrs.set_attrs(m.begin(2), w.length, attr) a = NULL * a.length else a = NON_PRINTING_START + a + NON_PRINTING_END end a + w + a } str.delete!(NON_PRINTING_START + NON_PRINTING_END) end
# File lib/rdoc/markup/attribute_manager.rb, line 182 def convert_attrs_word_pair_map(str, attrs, exclusive) # then non-matching unless @word_pair_map.empty? then @word_pair_map.each do |regexp, attr| next unless exclusive == exclusive?(attr) 1 while str.gsub!(regexp) { |orig| w = (m = ($~))[2] updated = attrs.set_attrs(m.begin(2), w.length, attr) if updated NULL * m.match_length(1) + w + NULL * m.match_length(3) else orig end } end end end
Converts HTML tags to RDoc attributes
# File lib/rdoc/markup/attribute_manager.rb, line 203 def convert_html(str, attrs, exclusive = false) tags = @html_tags.select { |start, bitmap| exclusive == exclusive?(bitmap) }.keys.join '|' 1 while str.gsub!(/<(#{tags})>(.*?)<\/\1>/i) { |orig| attr = @html_tags[$1.downcase] html_length = $~.match_length(1) + 2 # "<>".length seq = NULL * html_length attrs.set_attrs($~.begin(2), $~.match_length(2), attr) seq + $2 + seq + NULL } end
Converts regexp handling sequences to RDoc attributes
# File lib/rdoc/markup/attribute_manager.rb, line 220 def convert_regexp_handlings str, attrs, exclusive = false @regexp_handlings.each do |regexp, attribute| next unless exclusive == exclusive?(attribute) str.scan(regexp) do capture = $~.size == 1 ? 0 : 1 s, e = $~.offset capture attrs.set_attrs s, e - s, attribute | @attributes.regexp_handling end end end
Copies start_pos to end_pos from the current string
# File lib/rdoc/markup/attribute_manager.rb, line 135 def copy_string(start_pos, end_pos) res = @str[start_pos...end_pos] res.gsub!(/\000/, '') res end
Debug method that prints a string along with its attributes
# File lib/rdoc/markup/attribute_manager.rb, line 326 def display_attributes puts puts @str.tr(NULL, "!") bit = 1 16.times do |bno| line = "" @str.length.times do |i| if (@attrs[i] & bit) == 0 line << " " else if bno.zero? line << "S" else line << ("%d" % (bno+1)) end end end puts(line) unless line =~ /^ *$/ bit <<= 1 end end
# File lib/rdoc/markup/attribute_manager.rb, line 141 def exclusive?(attr) (attr & @exclusive_bitmap) != 0 end
Processes str converting attributes, HTML and regexp handlings
# File lib/rdoc/markup/attribute_manager.rb, line 304 def flow str @str = str.dup mask_protected_sequences @attrs = RDoc::Markup::AttrSpan.new @str.length, @exclusive_bitmap convert_attrs @str, @attrs, true convert_html @str, @attrs, true convert_regexp_handlings @str, @attrs, true convert_attrs @str, @attrs convert_html @str, @attrs convert_regexp_handlings @str, @attrs unmask_protected_sequences split_into_flow end
Escapes regexp handling sequences of text to prevent conversion to RDoc
# File lib/rdoc/markup/attribute_manager.rb, line 236 def mask_protected_sequences # protect __send__, __FILE__, etc. @str.gsub!(/__([a-z]+)__/i, "_#{PROTECT_ATTR}_#{PROTECT_ATTR}\\1_#{PROTECT_ATTR}_#{PROTECT_ATTR}") @str.gsub!(/(\A|[^\\])\\([#{Regexp.escape @protectable.join}])/m, "\\1\\2#{PROTECT_ATTR}") @str.gsub!(/\\(\\[#{Regexp.escape @protectable.join}])/m, "\\1") end
Splits the string into chunks by attribute change
# File lib/rdoc/markup/attribute_manager.rb, line 351 def split_into_flow res = [] current_attr = 0 str_len = @str.length # skip leading invisible text i = 0 i += 1 while i < str_len and @str[i].chr == "\0" start_pos = i # then scan the string, chunking it on attribute changes while i < str_len new_attr = @attrs[i] if new_attr != current_attr if i > start_pos res << copy_string(start_pos, i) start_pos = i end res << change_attribute(current_attr, new_attr) current_attr = new_attr if (current_attr & @attributes.regexp_handling) != 0 then i += 1 while i < str_len and (@attrs[i] & @attributes.regexp_handling) != 0 res << RDoc::Markup::RegexpHandling.new(current_attr, copy_string(start_pos, i)) start_pos = i next end end # move on, skipping any invisible characters begin i += 1 end while i < str_len and @str[i].chr == "\0" end # tidy up trailing text if start_pos < str_len res << copy_string(start_pos, str_len) end # and reset to all attributes off res << change_attribute(current_attr, 0) if current_attr != 0 res end
Unescapes regexp handling sequences of text
# File lib/rdoc/markup/attribute_manager.rb, line 248 def unmask_protected_sequences @str.gsub!(/(.)#{PROTECT_ATTR}/, "\\1\000") end