Class | SM::AttributeManager |
In: |
markup/simple_markup/inline.rb
|
Parent: | Object |
NULL | = | "\000".freeze | ||
A_PROTECT | = | 004 | We work by substituting non-printing characters in to the text. For now I‘m assuming that I can substitute a character in the range 0..8 for a 7 bit character without damaging the encoded string, but this might be optimistic | |
PROTECT_ATTR | = | A_PROTECT.chr | ||
MATCHING_WORD_PAIRS | = | {} | 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 | |
WORD_PAIR_MAP | = | {} | And this is used when the delimiters aren‘t the same. In this case the hash maps a pattern to the attribute character | |
HTML_TAGS | = | {} | This maps HTML tags to the corresponding attribute char | |
SPECIAL | = | {} | And this maps special sequences to a name. A special sequence is something like a WikiWord | |
PROTECTABLE | = | [ "<" << "\\" ] | A \ in front of a character that would normally be processed turns off processing. We do this by turning < into <#{PROTECT} |
# File markup/simple_markup/inline.rb, line 208 208: def initialize 209: add_word_pair("*", "*", :BOLD) 210: add_word_pair("_", "_", :EM) 211: add_word_pair("+", "+", :TT) 212: 213: add_html("em", :EM) 214: add_html("i", :EM) 215: add_html("b", :BOLD) 216: add_html("tt", :TT) 217: add_html("code", :TT) 218: end
# File markup/simple_markup/inline.rb, line 236 236: def add_html(tag, name) 237: HTML_TAGS[tag.downcase] = Attribute.bitmap_for(name) 238: end
# File markup/simple_markup/inline.rb, line 240 240: def add_special(pattern, name) 241: SPECIAL[pattern] = Attribute.bitmap_for(name) 242: end
# File markup/simple_markup/inline.rb, line 220 220: def add_word_pair(start, stop, name) 221: raise "Word flags may not start '<'" if start[0] == ?< 222: bitmap = Attribute.bitmap_for(name) 223: if start == stop 224: MATCHING_WORD_PAIRS[start] = bitmap 225: else 226: pattern = Regexp.new("(" + Regexp.escape(start) + ")" + 227: # "([A-Za-z]+)" + 228: "(\\S+)" + 229: "(" + Regexp.escape(stop) +")") 230: WORD_PAIR_MAP[pattern] = bitmap 231: end 232: PROTECTABLE << start[0,1] 233: PROTECTABLE.uniq! 234: end
# File markup/simple_markup/inline.rb, line 127 127: def change_attribute(current, new) 128: diff = current ^ new 129: attribute(new & diff, current & diff) 130: end
# File markup/simple_markup/inline.rb, line 132 132: def changed_attribute_by_name(current_set, new_set) 133: current = new = 0 134: current_set.each {|name| current |= Attribute.bitmap_for(name) } 135: new_set.each {|name| new |= Attribute.bitmap_for(name) } 136: change_attribute(current, new) 137: end
Map attributes like textto the sequence \001\002<char>\001\003<char>, where <char> is a per-attribute specific character
# File markup/simple_markup/inline.rb, line 148 148: def convert_attrs(str, attrs) 149: # first do matching ones 150: tags = MATCHING_WORD_PAIRS.keys.join("") 151: re = "(^|\\W)([#{tags}])([A-Za-z_]+?)\\2(\\W|\$)" 152: # re = "(^|\\W)([#{tags}])(\\S+?)\\2(\\W|\$)" 153: 1 while str.gsub!(Regexp.new(re)) { 154: attr = MATCHING_WORD_PAIRS[$2]; 155: attrs.set_attrs($`.length + $1.length + $2.length, $3.length, attr) 156: $1 + NULL*$2.length + $3 + NULL*$2.length + $4 157: } 158: 159: # then non-matching 160: unless WORD_PAIR_MAP.empty? 161: WORD_PAIR_MAP.each do |regexp, attr| 162: str.gsub!(regexp) { 163: attrs.set_attrs($`.length + $1.length, $2.length, attr) 164: NULL*$1.length + $2 + NULL*$3.length 165: } 166: end 167: end 168: end
# File markup/simple_markup/inline.rb, line 170 170: def convert_html(str, attrs) 171: tags = HTML_TAGS.keys.join("|") 172: re = "<(#{tags})>(.*?)</\\1>" 173: 1 while str.gsub!(Regexp.new(re, Regexp::IGNORECASE)) { 174: attr = HTML_TAGS[$1.downcase] 175: html_length = $1.length + 2 176: seq = NULL * html_length 177: attrs.set_attrs($`.length + html_length, $2.length, attr) 178: seq + $2 + seq + NULL 179: } 180: end
# File markup/simple_markup/inline.rb, line 182 182: def convert_specials(str, attrs) 183: unless SPECIAL.empty? 184: SPECIAL.each do |regexp, attr| 185: str.scan(regexp) do 186: attrs.set_attrs($`.length, $1.length, attr | Attribute::SPECIAL) 187: end 188: end 189: end 190: end
# File markup/simple_markup/inline.rb, line 139 139: def copy_string(start_pos, end_pos) 140: res = @str[start_pos...end_pos] 141: res.gsub!(/\000/, '') 142: res 143: end
# File markup/simple_markup/inline.rb, line 261 261: def display_attributes 262: puts 263: puts @str.tr(NULL, "!") 264: bit = 1 265: 16.times do |bno| 266: line = "" 267: @str.length.times do |i| 268: if (@attrs[i] & bit) == 0 269: line << " " 270: else 271: if bno.zero? 272: line << "S" 273: else 274: line << ("%d" % (bno+1)) 275: end 276: end 277: end 278: puts(line) unless line =~ /^ *$/ 279: bit <<= 1 280: end 281: end
# File markup/simple_markup/inline.rb, line 244 244: def flow(str) 245: @str = str 246: 247: puts("Before flow, str='#{@str.dump}'") if $DEBUG 248: mask_protected_sequences 249: 250: @attrs = AttrSpan.new(@str.length) 251: 252: puts("After protecting, str='#{@str.dump}'") if $DEBUG 253: convert_attrs(@str, @attrs) 254: convert_html(@str, @attrs) 255: convert_specials(str, @attrs) 256: unmask_protected_sequences 257: puts("After flow, str='#{@str.dump}'") if $DEBUG 258: return split_into_flow 259: end
# File markup/simple_markup/inline.rb, line 199 199: def mask_protected_sequences 200: protect_pattern = Regexp.new("\\\\([#{Regexp.escape(PROTECTABLE.join(''))}])") 201: @str.gsub!(protect_pattern, "\\1#{PROTECT_ATTR}") 202: end
# File markup/simple_markup/inline.rb, line 283 283: def split_into_flow 284: 285: display_attributes if $DEBUG 286: 287: res = [] 288: current_attr = 0 289: str = "" 290: 291: 292: str_len = @str.length 293: 294: # skip leading invisible text 295: i = 0 296: i += 1 while i < str_len and @str[i].zero? 297: start_pos = i 298: 299: # then scan the string, chunking it on attribute changes 300: while i < str_len 301: new_attr = @attrs[i] 302: if new_attr != current_attr 303: if i > start_pos 304: res << copy_string(start_pos, i) 305: start_pos = i 306: end 307: 308: res << change_attribute(current_attr, new_attr) 309: current_attr = new_attr 310: 311: if (current_attr & Attribute::SPECIAL) != 0 312: i += 1 while i < str_len and (@attrs[i] & Attribute::SPECIAL) != 0 313: res << Special.new(current_attr, copy_string(start_pos, i)) 314: start_pos = i 315: next 316: end 317: end 318: 319: # move on, skipping any invisible characters 320: begin 321: i += 1 322: end while i < str_len and @str[i].zero? 323: end 324: 325: # tidy up trailing text 326: if start_pos < str_len 327: res << copy_string(start_pos, str_len) 328: end 329: 330: # and reset to all attributes off 331: res << change_attribute(current_attr, 0) if current_attr != 0 332: 333: return res 334: end