Class SM::AttributeManager
In: markup/simple_markup/inline.rb
Parent: Object

Methods

Constants

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}

Public Class methods

[Source]

     # 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

Public Instance methods

[Source]

     # 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

[Source]

     # File markup/simple_markup/inline.rb, line 240
240:     def add_special(pattern, name)
241:       SPECIAL[pattern] = Attribute.bitmap_for(name)
242:     end

[Source]

     # 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

Return an attribute object with the given turn_on and turn_off bits set

[Source]

     # File markup/simple_markup/inline.rb, line 122
122:     def attribute(turn_on, turn_off)
123:       AttrChanger.new(turn_on, turn_off)
124:     end

[Source]

     # 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

[Source]

     # 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

[Source]

     # 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

[Source]

     # 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

[Source]

     # 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

[Source]

     # 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

[Source]

     # 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

[Source]

     # 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

[Source]

     # 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

[Source]

     # 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

[Source]

     # File markup/simple_markup/inline.rb, line 204
204:     def unmask_protected_sequences
205:       @str.gsub!(/(.)#{PROTECT_ATTR}/, "\\1\000")
206:     end

[Validate]