rdoc-f95 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. data/History.txt +4 -0
  2. data/Manifest.txt +79 -0
  3. data/PostInstall.txt +7 -0
  4. data/README.rdoc +147 -0
  5. data/Rakefile +28 -0
  6. data/bin/rdoc-f95 +70 -0
  7. data/lib/rdoc-f95.rb +306 -0
  8. data/lib/rdoc-f95/code_objects.rb +776 -0
  9. data/lib/rdoc-f95/diagram.rb +342 -0
  10. data/lib/rdoc-f95/dot.rb +249 -0
  11. data/lib/rdoc-f95/generator.rb +1088 -0
  12. data/lib/rdoc-f95/generator/chm.rb +113 -0
  13. data/lib/rdoc-f95/generator/chm/chm.rb +98 -0
  14. data/lib/rdoc-f95/generator/html.rb +370 -0
  15. data/lib/rdoc-f95/generator/html/hefss.rb +414 -0
  16. data/lib/rdoc-f95/generator/html/html.rb +708 -0
  17. data/lib/rdoc-f95/generator/html/kilmer.rb +418 -0
  18. data/lib/rdoc-f95/generator/html/one_page_html.rb +121 -0
  19. data/lib/rdoc-f95/generator/ri.rb +229 -0
  20. data/lib/rdoc-f95/generator/xhtml.rb +106 -0
  21. data/lib/rdoc-f95/generator/xhtml/ctop.xsl +1318 -0
  22. data/lib/rdoc-f95/generator/xhtml/mathml.xsl +42 -0
  23. data/lib/rdoc-f95/generator/xhtml/pmathml.xsl +612 -0
  24. data/lib/rdoc-f95/generator/xhtml/pmathmlcss.xsl +872 -0
  25. data/lib/rdoc-f95/generator/xhtml/xhtml.rb +732 -0
  26. data/lib/rdoc-f95/generator/xml.rb +120 -0
  27. data/lib/rdoc-f95/generator/xml/rdf.rb +113 -0
  28. data/lib/rdoc-f95/generator/xml/xml.rb +111 -0
  29. data/lib/rdoc-f95/install.rb +166 -0
  30. data/lib/rdoc-f95/markup.rb +506 -0
  31. data/lib/rdoc-f95/markup/formatter.rb +14 -0
  32. data/lib/rdoc-f95/markup/fragments.rb +337 -0
  33. data/lib/rdoc-f95/markup/inline.rb +361 -0
  34. data/lib/rdoc-f95/markup/install.rb +57 -0
  35. data/lib/rdoc-f95/markup/lines.rb +152 -0
  36. data/lib/rdoc-f95/markup/mathml_wrapper.rb +91 -0
  37. data/lib/rdoc-f95/markup/preprocess.rb +71 -0
  38. data/lib/rdoc-f95/markup/sample/rdoc2latex.rb +16 -0
  39. data/lib/rdoc-f95/markup/sample/sample.rb +42 -0
  40. data/lib/rdoc-f95/markup/to_flow.rb +185 -0
  41. data/lib/rdoc-f95/markup/to_html.rb +357 -0
  42. data/lib/rdoc-f95/markup/to_html_crossref.rb +123 -0
  43. data/lib/rdoc-f95/markup/to_latex.rb +328 -0
  44. data/lib/rdoc-f95/markup/to_test.rb +50 -0
  45. data/lib/rdoc-f95/markup/to_xhtml_texparser.rb +234 -0
  46. data/lib/rdoc-f95/options.rb +745 -0
  47. data/lib/rdoc-f95/parsers/parse_c.rb +775 -0
  48. data/lib/rdoc-f95/parsers/parse_f95.rb +2499 -0
  49. data/lib/rdoc-f95/parsers/parse_rb.rb +2587 -0
  50. data/lib/rdoc-f95/parsers/parse_simple.rb +39 -0
  51. data/lib/rdoc-f95/parsers/parserfactory.rb +99 -0
  52. data/lib/rdoc-f95/ri.rb +2 -0
  53. data/lib/rdoc-f95/ri/cache.rb +188 -0
  54. data/lib/rdoc-f95/ri/descriptions.rb +147 -0
  55. data/lib/rdoc-f95/ri/display.rb +244 -0
  56. data/lib/rdoc-f95/ri/driver.rb +435 -0
  57. data/lib/rdoc-f95/ri/formatter.rb +603 -0
  58. data/lib/rdoc-f95/ri/paths.rb +105 -0
  59. data/lib/rdoc-f95/ri/reader.rb +106 -0
  60. data/lib/rdoc-f95/ri/util.rb +81 -0
  61. data/lib/rdoc-f95/ri/writer.rb +64 -0
  62. data/lib/rdoc-f95/stats.rb +23 -0
  63. data/lib/rdoc-f95/template.rb +64 -0
  64. data/lib/rdoc-f95/tokenstream.rb +33 -0
  65. data/lib/rdoc-f95/usage.rb +210 -0
  66. data/script/console +10 -0
  67. data/script/destroy +14 -0
  68. data/script/generate +14 -0
  69. data/test/test_helper.rb +3 -0
  70. data/test/test_rdoc-f95.rb +11 -0
  71. metadata +156 -0
@@ -0,0 +1,14 @@
1
+ require 'rdoc-f95/markup'
2
+
3
+ class RDocF95::Markup::Formatter
4
+
5
+ def initialize
6
+ @markup = RDocF95::Markup.new
7
+ end
8
+
9
+ def convert(content, block_exceptions=nil)
10
+ @markup.convert content, self, block_exceptions
11
+ end
12
+
13
+ end
14
+
@@ -0,0 +1,337 @@
1
+ require 'rdoc-f95/markup'
2
+ require 'rdoc-f95/markup/lines'
3
+
4
+ class RDocF95::Markup
5
+
6
+ ##
7
+ # A Fragment is a chunk of text, subclassed as a paragraph, a list
8
+ # entry, or verbatim text.
9
+
10
+ class Fragment
11
+ attr_reader :level, :param, :txt
12
+ attr_accessor :type
13
+
14
+ ######
15
+ # This is a simple factory system that lets us associate fragement
16
+ # types (a string) with a subclass of fragment
17
+
18
+ TYPE_MAP = {}
19
+
20
+ def self.type_name(name)
21
+ TYPE_MAP[name] = self
22
+ end
23
+
24
+ def self.for(line)
25
+ klass = TYPE_MAP[line.type] ||
26
+ raise("Unknown line type: '#{line.type.inspect}:' '#{line.text}'")
27
+ return klass.new(line.level, line.param, line.flag, line.text)
28
+ end
29
+
30
+ def initialize(level, param, type, txt)
31
+ @level = level
32
+ @param = param
33
+ @type = type
34
+ @txt = ""
35
+ add_text(txt) if txt
36
+ end
37
+
38
+ def add_text(txt)
39
+ @txt << " " if @txt.length > 0
40
+ @txt << txt.tr_s("\n ", " ").strip
41
+ end
42
+
43
+ def to_s
44
+ "L#@level: #{self.class.name.split('::')[-1]}\n#@txt"
45
+ end
46
+
47
+ end
48
+
49
+ ##
50
+ # A paragraph is a fragment which gets wrapped to fit. We remove all
51
+ # newlines when we're created, and have them put back on output.
52
+
53
+ class Paragraph < Fragment
54
+ type_name :PARAGRAPH
55
+ end
56
+
57
+ class BlankLine < Paragraph
58
+ type_name :BLANK
59
+ end
60
+
61
+ class Heading < Paragraph
62
+ type_name :HEADING
63
+
64
+ def head_level
65
+ @param.to_i
66
+ end
67
+ end
68
+
69
+ ##
70
+ # A List is a fragment with some kind of label
71
+
72
+ class ListBase < Paragraph
73
+ LIST_TYPES = [
74
+ :BULLET,
75
+ :NUMBER,
76
+ :UPPERALPHA,
77
+ :LOWERALPHA,
78
+ :LABELED,
79
+ :NOTE,
80
+ ]
81
+ end
82
+
83
+ class ListItem < ListBase
84
+ type_name :LIST
85
+
86
+ def to_s
87
+ text = if [:NOTE, :LABELED].include? type then
88
+ "#{@param}: #{@txt}"
89
+ else
90
+ @txt
91
+ end
92
+
93
+ "L#@level: #{type} #{self.class.name.split('::')[-1]}\n#{text}"
94
+ end
95
+
96
+ end
97
+
98
+ class ListStart < ListBase
99
+ def initialize(level, param, type)
100
+ super(level, param, type, nil)
101
+ end
102
+ end
103
+
104
+ class ListEnd < ListBase
105
+ def initialize(level, type)
106
+ super(level, "", type, nil)
107
+ end
108
+ end
109
+
110
+ ##
111
+ # Verbatim code contains lines that don't get wrapped.
112
+
113
+ class Verbatim < Fragment
114
+ type_name :VERBATIM
115
+
116
+ def add_text(txt)
117
+ @txt << txt.chomp << "\n"
118
+ end
119
+
120
+ end
121
+
122
+ ##
123
+ # A horizontal rule
124
+
125
+ class Rule < Fragment
126
+ type_name :RULE
127
+ end
128
+
129
+ ##
130
+ # Collect groups of lines together. Each group will end up containing a flow
131
+ # of text.
132
+
133
+ class LineCollection
134
+
135
+ def initialize
136
+ @fragments = []
137
+ end
138
+
139
+ def add(fragment)
140
+ @fragments << fragment
141
+ end
142
+
143
+ def each(&b)
144
+ @fragments.each(&b)
145
+ end
146
+
147
+ def to_a # :nodoc:
148
+ @fragments.map {|fragment| fragment.to_s}
149
+ end
150
+
151
+ ##
152
+ # Factory for different fragment types
153
+
154
+ def fragment_for(*args)
155
+ Fragment.for(*args)
156
+ end
157
+
158
+ ##
159
+ # Tidy up at the end
160
+
161
+ def normalize
162
+ change_verbatim_blank_lines
163
+ add_list_start_and_ends
164
+ add_list_breaks
165
+ tidy_blank_lines
166
+ end
167
+
168
+ def to_s
169
+ @fragments.join("\n----\n")
170
+ end
171
+
172
+ def accept(am, visitor)
173
+ visitor.start_accepting
174
+
175
+ @fragments.each do |fragment|
176
+ case fragment
177
+ when Verbatim
178
+ visitor.accept_verbatim(am, fragment)
179
+ when Rule
180
+ visitor.accept_rule(am, fragment)
181
+ when ListStart
182
+ visitor.accept_list_start(am, fragment)
183
+ when ListEnd
184
+ visitor.accept_list_end(am, fragment)
185
+ when ListItem
186
+ visitor.accept_list_item(am, fragment)
187
+ when BlankLine
188
+ visitor.accept_blank_line(am, fragment)
189
+ when Heading
190
+ visitor.accept_heading(am, fragment)
191
+ when Paragraph
192
+ visitor.accept_paragraph(am, fragment)
193
+ end
194
+ end
195
+
196
+ visitor.end_accepting
197
+ end
198
+
199
+ private
200
+
201
+ # If you have:
202
+ #
203
+ # normal paragraph text.
204
+ #
205
+ # this is code
206
+ #
207
+ # and more code
208
+ #
209
+ # You'll end up with the fragments Paragraph, BlankLine, Verbatim,
210
+ # BlankLine, Verbatim, BlankLine, etc.
211
+ #
212
+ # The BlankLine in the middle of the verbatim chunk needs to be changed to
213
+ # a real verbatim newline, and the two verbatim blocks merged
214
+
215
+ def change_verbatim_blank_lines
216
+ frag_block = nil
217
+ blank_count = 0
218
+ @fragments.each_with_index do |frag, i|
219
+ if frag_block.nil?
220
+ frag_block = frag if Verbatim === frag
221
+ else
222
+ case frag
223
+ when Verbatim
224
+ blank_count.times { frag_block.add_text("\n") }
225
+ blank_count = 0
226
+ frag_block.add_text(frag.txt)
227
+ @fragments[i] = nil # remove out current fragment
228
+ when BlankLine
229
+ if frag_block
230
+ blank_count += 1
231
+ @fragments[i] = nil
232
+ end
233
+ else
234
+ frag_block = nil
235
+ blank_count = 0
236
+ end
237
+ end
238
+ end
239
+ @fragments.compact!
240
+ end
241
+
242
+ ##
243
+ # List nesting is implicit given the level of indentation. Make it
244
+ # explicit, just to make life a tad easier for the output processors
245
+
246
+ def add_list_start_and_ends
247
+ level = 0
248
+ res = []
249
+ type_stack = []
250
+
251
+ @fragments.each do |fragment|
252
+ # $stderr.puts "#{level} : #{fragment.class.name} : #{fragment.level}"
253
+ new_level = fragment.level
254
+ while (level < new_level)
255
+ level += 1
256
+ type = fragment.type
257
+ res << ListStart.new(level, fragment.param, type) if type
258
+ type_stack.push type
259
+ # $stderr.puts "Start: #{level}"
260
+ end
261
+
262
+ while level > new_level
263
+ type = type_stack.pop
264
+ res << ListEnd.new(level, type) if type
265
+ level -= 1
266
+ # $stderr.puts "End: #{level}, #{type}"
267
+ end
268
+
269
+ res << fragment
270
+ level = fragment.level
271
+ end
272
+ level.downto(1) do |i|
273
+ type = type_stack.pop
274
+ res << ListEnd.new(i, type) if type
275
+ end
276
+
277
+ @fragments = res
278
+ end
279
+
280
+ ##
281
+ # Inserts start/ends between list entries at the same level that have
282
+ # different element types
283
+
284
+ def add_list_breaks
285
+ res = @fragments
286
+
287
+ @fragments = []
288
+ list_stack = []
289
+
290
+ res.each do |fragment|
291
+ case fragment
292
+ when ListStart
293
+ list_stack.push fragment
294
+ when ListEnd
295
+ start = list_stack.pop
296
+ fragment.type = start.type
297
+ when ListItem
298
+ l = list_stack.last
299
+ if fragment.type != l.type
300
+ @fragments << ListEnd.new(l.level, l.type)
301
+ start = ListStart.new(l.level, fragment.param, fragment.type)
302
+ @fragments << start
303
+ list_stack.pop
304
+ list_stack.push start
305
+ end
306
+ else
307
+ ;
308
+ end
309
+ @fragments << fragment
310
+ end
311
+ end
312
+
313
+ ##
314
+ # Tidy up the blank lines:
315
+ # * change Blank/ListEnd into ListEnd/Blank
316
+ # * remove blank lines at the front
317
+
318
+ def tidy_blank_lines
319
+ (@fragments.size - 1).times do |i|
320
+ if BlankLine === @fragments[i] and ListEnd === @fragments[i+1] then
321
+ @fragments[i], @fragments[i+1] = @fragments[i+1], @fragments[i]
322
+ end
323
+ end
324
+
325
+ # remove leading blanks
326
+ @fragments.each_with_index do |f, i|
327
+ break unless f.kind_of? BlankLine
328
+ @fragments[i] = nil
329
+ end
330
+
331
+ @fragments.compact!
332
+ end
333
+
334
+ end
335
+
336
+ end
337
+
@@ -0,0 +1,361 @@
1
+ require 'rdoc-f95/markup'
2
+
3
+ class RDocF95::Markup
4
+
5
+ ##
6
+ # We manage a set of attributes. Each attribute has a symbol name and a bit
7
+ # value.
8
+
9
+ class Attribute
10
+ SPECIAL = 1
11
+
12
+ @@name_to_bitmap = { :_SPECIAL_ => SPECIAL }
13
+ @@next_bitmap = 2
14
+
15
+ def self.bitmap_for(name)
16
+ bitmap = @@name_to_bitmap[name]
17
+ unless bitmap then
18
+ bitmap = @@next_bitmap
19
+ @@next_bitmap <<= 1
20
+ @@name_to_bitmap[name] = bitmap
21
+ end
22
+ bitmap
23
+ end
24
+
25
+ def self.as_string(bitmap)
26
+ return "none" if bitmap.zero?
27
+ res = []
28
+ @@name_to_bitmap.each do |name, bit|
29
+ res << name if (bitmap & bit) != 0
30
+ end
31
+ res.join(",")
32
+ end
33
+
34
+ def self.each_name_of(bitmap)
35
+ @@name_to_bitmap.each do |name, bit|
36
+ next if bit == SPECIAL
37
+ yield name.to_s if (bitmap & bit) != 0
38
+ end
39
+ end
40
+ end
41
+
42
+ ##
43
+ # An AttrChanger records a change in attributes. It contains a bitmap of the
44
+ # attributes to turn on, and a bitmap of those to turn off.
45
+
46
+ AttrChanger = Struct.new(:turn_on, :turn_off)
47
+
48
+ class AttrChanger
49
+ def to_s
50
+ "Attr: +#{Attribute.as_string(@turn_on)}/-#{Attribute.as_string(@turn_on)}"
51
+ end
52
+ end
53
+
54
+ ##
55
+ # An array of attributes which parallels the characters in a string.
56
+
57
+ class AttrSpan
58
+ def initialize(length)
59
+ @attrs = Array.new(length, 0)
60
+ end
61
+
62
+ def set_attrs(start, length, bits)
63
+ for i in start ... (start+length)
64
+ @attrs[i] |= bits
65
+ end
66
+ end
67
+
68
+ def [](n)
69
+ @attrs[n]
70
+ end
71
+ end
72
+
73
+ ##
74
+ # Hold details of a special sequence
75
+
76
+ class Special
77
+ attr_reader :type
78
+ attr_accessor :text
79
+
80
+ def initialize(type, text)
81
+ @type, @text = type, text
82
+ end
83
+
84
+ def ==(o)
85
+ self.text == o.text && self.type == o.type
86
+ end
87
+
88
+ def inspect
89
+ "#<RDocF95::Markup::Special:0x%x @type=%p, name=%p @text=%p>" % [
90
+ object_id, @type, RDocF95::Markup::Attribute.as_string(type), text.dump]
91
+ end
92
+
93
+ def to_s
94
+ "Special: type=#{type}, name=#{RDocF95::Markup::Attribute.as_string type}, text=#{text.dump}"
95
+ end
96
+
97
+ end
98
+
99
+ class AttributeManager
100
+
101
+ NULL = "\000".freeze
102
+
103
+ ##
104
+ # We work by substituting non-printing characters in to the text. For now
105
+ # I'm assuming that I can substitute a character in the range 0..8 for a 7
106
+ # bit character without damaging the encoded string, but this might be
107
+ # optimistic
108
+
109
+ A_PROTECT = 004
110
+ PROTECT_ATTR = A_PROTECT.chr
111
+
112
+ ##
113
+ # This maps delimiters that occur around words (such as *bold* or +tt+)
114
+ # where the start and end delimiters and the same. This lets us optimize
115
+ # the regexp
116
+
117
+ MATCHING_WORD_PAIRS = {}
118
+
119
+ ##
120
+ # And this is used when the delimiters aren't the same. In this case the
121
+ # hash maps a pattern to the attribute character
122
+
123
+ WORD_PAIR_MAP = {}
124
+
125
+ ##
126
+ # This maps HTML tags to the corresponding attribute char
127
+
128
+ HTML_TAGS = {}
129
+
130
+ ##
131
+ # And this maps _special_ sequences to a name. A special sequence is
132
+ # something like a WikiWord
133
+
134
+ SPECIAL = {}
135
+
136
+ ##
137
+ # Return an attribute object with the given turn_on and turn_off bits set
138
+
139
+ def attribute(turn_on, turn_off)
140
+ AttrChanger.new(turn_on, turn_off)
141
+ end
142
+
143
+ def change_attribute(current, new)
144
+ diff = current ^ new
145
+ attribute(new & diff, current & diff)
146
+ end
147
+
148
+ def changed_attribute_by_name(current_set, new_set)
149
+ current = new = 0
150
+ current_set.each {|name| current |= Attribute.bitmap_for(name) }
151
+ new_set.each {|name| new |= Attribute.bitmap_for(name) }
152
+ change_attribute(current, new)
153
+ end
154
+
155
+ def copy_string(start_pos, end_pos)
156
+ res = @str[start_pos...end_pos]
157
+ res.gsub!(/\000/, '')
158
+ res
159
+ end
160
+
161
+ ##
162
+ # Map attributes like <b>text</b>to the sequence
163
+ # \001\002<char>\001\003<char>, where <char> is a per-attribute specific
164
+ # character
165
+
166
+ def convert_attrs(str, attrs)
167
+ # first do matching ones
168
+ tags = MATCHING_WORD_PAIRS.keys.join("")
169
+
170
+ re = /(^|\W)([#{tags}])([#\\]?[\w.\/]+?\S?)\2(\W|$)/
171
+
172
+ 1 while str.gsub!(re) do
173
+ attr = MATCHING_WORD_PAIRS[$2]
174
+ attrs.set_attrs($`.length + $1.length + $2.length, $3.length, attr)
175
+ $1 + NULL * $2.length + $3 + NULL * $2.length + $4
176
+ end
177
+
178
+ # then non-matching
179
+ unless WORD_PAIR_MAP.empty? then
180
+ WORD_PAIR_MAP.each do |regexp, attr|
181
+ str.gsub!(regexp) {
182
+ attrs.set_attrs($`.length + $1.length, $2.length, attr)
183
+ NULL * $1.length + $2 + NULL * $3.length
184
+ }
185
+ end
186
+ end
187
+ end
188
+
189
+ def convert_html(str, attrs)
190
+ tags = HTML_TAGS.keys.join '|'
191
+
192
+ 1 while str.gsub!(/<(#{tags})>(.*?)<\/\1>/i) {
193
+ attr = HTML_TAGS[$1.downcase]
194
+ html_length = $1.length + 2
195
+ seq = NULL * html_length
196
+ attrs.set_attrs($`.length + html_length, $2.length, attr)
197
+ seq + $2 + seq + NULL
198
+ }
199
+ end
200
+
201
+ def convert_specials(str, attrs)
202
+ unless SPECIAL.empty?
203
+ SPECIAL.each do |regexp, attr|
204
+ str.scan(regexp) do
205
+ attrs.set_attrs($`.length, $&.length, attr | Attribute::SPECIAL)
206
+ end
207
+ end
208
+ end
209
+ end
210
+
211
+ ##
212
+ # A \ in front of a character that would normally be processed turns off
213
+ # processing. We do this by turning \< into <#{PROTECT}
214
+
215
+ PROTECTABLE = %w[<\\]
216
+
217
+ def mask_protected_sequences
218
+ protect_pattern = Regexp.new("\\\\([#{Regexp.escape(PROTECTABLE.join(''))}])")
219
+ @str.gsub!(protect_pattern, "\\1#{PROTECT_ATTR}")
220
+ end
221
+
222
+ def unmask_protected_sequences
223
+ @str.gsub!(/(.)#{PROTECT_ATTR}/, "\\1\000")
224
+ end
225
+
226
+ def initialize
227
+ add_word_pair("*", "*", :BOLD)
228
+ add_word_pair("_", "_", :EM)
229
+ add_word_pair("+", "+", :TT)
230
+
231
+ add_html("em", :EM)
232
+ add_html("i", :EM)
233
+ add_html("b", :BOLD)
234
+ add_html("tt", :TT)
235
+ add_html("code", :TT)
236
+
237
+ add_special(/<!--(.*?)-->/, :COMMENT)
238
+ end
239
+
240
+ def add_word_pair(start, stop, name)
241
+ raise "Word flags may not start '<'" if start[0] == ?<
242
+ bitmap = Attribute.bitmap_for(name)
243
+ if start == stop
244
+ MATCHING_WORD_PAIRS[start] = bitmap
245
+ else
246
+ pattern = Regexp.new("(" + Regexp.escape(start) + ")" +
247
+ # "([A-Za-z]+)" +
248
+ "(\\S+)" +
249
+ "(" + Regexp.escape(stop) +")")
250
+ WORD_PAIR_MAP[pattern] = bitmap
251
+ end
252
+ PROTECTABLE << start[0,1]
253
+ PROTECTABLE.uniq!
254
+ end
255
+
256
+ def add_html(tag, name)
257
+ HTML_TAGS[tag.downcase] = Attribute.bitmap_for(name)
258
+ end
259
+
260
+ def add_special(pattern, name)
261
+ SPECIAL[pattern] = Attribute.bitmap_for(name)
262
+ end
263
+
264
+ def flow(str)
265
+ @str = str
266
+
267
+ puts("Before flow, str='#{@str.dump}'") if $DEBUG_RDOC
268
+ mask_protected_sequences
269
+
270
+ @attrs = AttrSpan.new(@str.length)
271
+
272
+ puts("After protecting, str='#{@str.dump}'") if $DEBUG_RDOC
273
+
274
+ convert_attrs(@str, @attrs)
275
+ convert_html(@str, @attrs)
276
+ convert_specials(str, @attrs)
277
+
278
+ unmask_protected_sequences
279
+
280
+ puts("After flow, str='#{@str.dump}'") if $DEBUG_RDOC
281
+
282
+ return split_into_flow
283
+ end
284
+
285
+ def display_attributes
286
+ puts
287
+ puts @str.tr(NULL, "!")
288
+ bit = 1
289
+ 16.times do |bno|
290
+ line = ""
291
+ @str.length.times do |i|
292
+ if (@attrs[i] & bit) == 0
293
+ line << " "
294
+ else
295
+ if bno.zero?
296
+ line << "S"
297
+ else
298
+ line << ("%d" % (bno+1))
299
+ end
300
+ end
301
+ end
302
+ puts(line) unless line =~ /^ *$/
303
+ bit <<= 1
304
+ end
305
+ end
306
+
307
+ def split_into_flow
308
+ display_attributes if $DEBUG_RDOC
309
+
310
+ res = []
311
+ current_attr = 0
312
+ str = ""
313
+
314
+ str_len = @str.length
315
+
316
+ # skip leading invisible text
317
+ i = 0
318
+ i += 1 while i < str_len and @str[i] == "\0"
319
+ start_pos = i
320
+
321
+ # then scan the string, chunking it on attribute changes
322
+ while i < str_len
323
+ new_attr = @attrs[i]
324
+ if new_attr != current_attr
325
+ if i > start_pos
326
+ res << copy_string(start_pos, i)
327
+ start_pos = i
328
+ end
329
+
330
+ res << change_attribute(current_attr, new_attr)
331
+ current_attr = new_attr
332
+
333
+ if (current_attr & Attribute::SPECIAL) != 0
334
+ i += 1 while i < str_len and (@attrs[i] & Attribute::SPECIAL) != 0
335
+ res << Special.new(current_attr, copy_string(start_pos, i))
336
+ start_pos = i
337
+ next
338
+ end
339
+ end
340
+
341
+ # move on, skipping any invisible characters
342
+ begin
343
+ i += 1
344
+ end while i < str_len and @str[i] == "\0"
345
+ end
346
+
347
+ # tidy up trailing text
348
+ if start_pos < str_len
349
+ res << copy_string(start_pos, str_len)
350
+ end
351
+
352
+ # and reset to all attributes off
353
+ res << change_attribute(current_attr, 0) if current_attr != 0
354
+
355
+ return res
356
+ end
357
+
358
+ end
359
+
360
+ end
361
+