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: 
219:       add_special(/<!--(.*?)-->/, :COMMENT)
220:     end

Public Instance methods

[Source]

     # 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

[Source]

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

[Source]

     # 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

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, $&.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 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

[Source]

     # 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

[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 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

[Source]

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

[Validate]