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: 219: add_special(/<!--(.*?)-->/, :COMMENT) 220: end
# File markup/simple_markup/inline.rb, line 238 238: def add_html(tag, name) 239: HTML_TAGS[tag.downcase] = Attribute.bitmap_for(name) 240: end
# File markup/simple_markup/inline.rb, line 242 242: def add_special(pattern, name) 243: SPECIAL[pattern] = Attribute.bitmap_for(name) 244: end
# File markup/simple_markup/inline.rb, line 222 222: def add_word_pair(start, stop, name) 223: raise "Word flags may not start '<'" if start[0] == ?< 224: bitmap = Attribute.bitmap_for(name) 225: if start == stop 226: MATCHING_WORD_PAIRS[start] = bitmap 227: else 228: pattern = Regexp.new("(" + Regexp.escape(start) + ")" + 229: # "([A-Za-z]+)" + 230: "(\\S+)" + 231: "(" + Regexp.escape(stop) +")") 232: WORD_PAIR_MAP[pattern] = bitmap 233: end 234: PROTECTABLE << start[0,1] 235: PROTECTABLE.uniq! 236: 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, $&.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 263 263: def display_attributes 264: puts 265: puts @str.tr(NULL, "!") 266: bit = 1 267: 16.times do |bno| 268: line = "" 269: @str.length.times do |i| 270: if (@attrs[i] & bit) == 0 271: line << " " 272: else 273: if bno.zero? 274: line << "S" 275: else 276: line << ("%d" % (bno+1)) 277: end 278: end 279: end 280: puts(line) unless line =~ /^ *$/ 281: bit <<= 1 282: end 283: end
# File markup/simple_markup/inline.rb, line 246 246: def flow(str) 247: @str = str 248: 249: puts("Before flow, str='#{@str.dump}'") if $DEBUG 250: mask_protected_sequences 251: 252: @attrs = AttrSpan.new(@str.length) 253: 254: puts("After protecting, str='#{@str.dump}'") if $DEBUG 255: convert_attrs(@str, @attrs) 256: convert_html(@str, @attrs) 257: convert_specials(str, @attrs) 258: unmask_protected_sequences 259: puts("After flow, str='#{@str.dump}'") if $DEBUG 260: return split_into_flow 261: 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 285 285: def split_into_flow 286: 287: display_attributes if $DEBUG 288: 289: res = [] 290: current_attr = 0 291: str = "" 292: 293: 294: str_len = @str.length 295: 296: # skip leading invisible text 297: i = 0 298: i += 1 while i < str_len and @str[i] == "\0" 299: start_pos = i 300: 301: # then scan the string, chunking it on attribute changes 302: while i < str_len 303: new_attr = @attrs[i] 304: if new_attr != current_attr 305: if i > start_pos 306: res << copy_string(start_pos, i) 307: start_pos = i 308: end 309: 310: res << change_attribute(current_attr, new_attr) 311: current_attr = new_attr 312: 313: if (current_attr & Attribute::SPECIAL) != 0 314: i += 1 while i < str_len and (@attrs[i] & Attribute::SPECIAL) != 0 315: res << Special.new(current_attr, copy_string(start_pos, i)) 316: start_pos = i 317: next 318: end 319: end 320: 321: # move on, skipping any invisible characters 322: begin 323: i += 1 324: end while i < str_len and @str[i] == "\0" 325: end 326: 327: # tidy up trailing text 328: if start_pos < str_len 329: res << copy_string(start_pos, str_len) 330: end 331: 332: # and reset to all attributes off 333: res << change_attribute(current_attr, 0) if current_attr != 0 334: 335: return res 336: end