1## 2# Base class for RDoc markup formatters 3# 4# Formatters are a visitor that converts an RDoc::Markup tree (from a comment) 5# into some kind of output. RDoc ships with formatters for converting back to 6# rdoc, ANSI text, HTML, a Table of Contents and other formats. 7# 8# If you'd like to write your own Formatter use 9# RDoc::Markup::FormatterTestCase. If you're writing a text-output formatter 10# use RDoc::Markup::TextFormatterTestCase which provides extra test cases. 11 12class RDoc::Markup::Formatter 13 14 ## 15 # Tag for inline markup containing a +bit+ for the bitmask and the +on+ and 16 # +off+ triggers. 17 18 InlineTag = Struct.new(:bit, :on, :off) 19 20 ## 21 # Converts a target url to one that is relative to a given path 22 23 def self.gen_relative_url path, target 24 from = File.dirname path 25 to, to_file = File.split target 26 27 from = from.split "/" 28 to = to.split "/" 29 30 from.delete '.' 31 to.delete '.' 32 33 while from.size > 0 and to.size > 0 and from[0] == to[0] do 34 from.shift 35 to.shift 36 end 37 38 from.fill ".." 39 from.concat to 40 from << to_file 41 File.join(*from) 42 end 43 44 ## 45 # Creates a new Formatter 46 47 def initialize options, markup = nil 48 @options = options 49 50 @markup = markup || RDoc::Markup.new 51 @am = @markup.attribute_manager 52 @am.add_special(/<br>/, :HARD_BREAK) 53 54 @attributes = @am.attributes 55 56 @attr_tags = [] 57 58 @in_tt = 0 59 @tt_bit = @attributes.bitmap_for :TT 60 61 @hard_break = '' 62 @from_path = '.' 63 end 64 65 ## 66 # Adds +document+ to the output 67 68 def accept_document document 69 document.parts.each do |item| 70 case item 71 when RDoc::Markup::Document then # HACK 72 accept_document item 73 else 74 item.accept self 75 end 76 end 77 end 78 79 ## 80 # Adds a special for links of the form rdoc-...: 81 82 def add_special_RDOCLINK 83 @markup.add_special(/rdoc-[a-z]+:\S+/, :RDOCLINK) 84 end 85 86 ## 87 # Adds a special for links of the form {<text>}[<url>] and <word>[<url>] 88 89 def add_special_TIDYLINK 90 @markup.add_special(/(?: 91 \{.*?\} | # multi-word label 92 \b[^\s{}]+? # single-word label 93 ) 94 95 \[\S+?\] # link target 96 /x, :TIDYLINK) 97 end 98 99 ## 100 # Add a new set of tags for an attribute. We allow separate start and end 101 # tags for flexibility 102 103 def add_tag(name, start, stop) 104 attr = @attributes.bitmap_for name 105 @attr_tags << InlineTag.new(attr, start, stop) 106 end 107 108 ## 109 # Allows +tag+ to be decorated with additional information. 110 111 def annotate(tag) 112 tag 113 end 114 115 ## 116 # Marks up +content+ 117 118 def convert content 119 @markup.convert content, self 120 end 121 122 ## 123 # Converts flow items +flow+ 124 125 def convert_flow(flow) 126 res = [] 127 128 flow.each do |item| 129 case item 130 when String then 131 res << convert_string(item) 132 when RDoc::Markup::AttrChanger then 133 off_tags res, item 134 on_tags res, item 135 when RDoc::Markup::Special then 136 res << convert_special(item) 137 else 138 raise "Unknown flow element: #{item.inspect}" 139 end 140 end 141 142 res.join 143 end 144 145 ## 146 # Converts added specials. See RDoc::Markup#add_special 147 148 def convert_special special 149 return special.text if in_tt? 150 151 handled = false 152 153 @attributes.each_name_of special.type do |name| 154 method_name = "handle_special_#{name}" 155 156 if respond_to? method_name then 157 special.text = send method_name, special 158 handled = true 159 end 160 end 161 162 unless handled then 163 special_name = @attributes.as_string special.type 164 165 raise RDoc::Error, "Unhandled special #{special_name}: #{special}" 166 end 167 168 special.text 169 end 170 171 ## 172 # Converts a string to be fancier if desired 173 174 def convert_string string 175 string 176 end 177 178 ## 179 # Use ignore in your subclass to ignore the content of a node. 180 # 181 # ## 182 # # We don't support raw nodes in ToNoRaw 183 # 184 # alias accept_raw ignore 185 186 def ignore *node 187 end 188 189 ## 190 # Are we currently inside tt tags? 191 192 def in_tt? 193 @in_tt > 0 194 end 195 196 ## 197 # Turns on tags for +item+ on +res+ 198 199 def on_tags res, item 200 attr_mask = item.turn_on 201 return if attr_mask.zero? 202 203 @attr_tags.each do |tag| 204 if attr_mask & tag.bit != 0 then 205 res << annotate(tag.on) 206 @in_tt += 1 if tt? tag 207 end 208 end 209 end 210 211 ## 212 # Turns off tags for +item+ on +res+ 213 214 def off_tags res, item 215 attr_mask = item.turn_off 216 return if attr_mask.zero? 217 218 @attr_tags.reverse_each do |tag| 219 if attr_mask & tag.bit != 0 then 220 @in_tt -= 1 if tt? tag 221 res << annotate(tag.off) 222 end 223 end 224 end 225 226 ## 227 # Extracts and a scheme, url and an anchor id from +url+ and returns them. 228 229 def parse_url url 230 case url 231 when /^rdoc-label:([^:]*)(?::(.*))?/ then 232 scheme = 'link' 233 path = "##{$1}" 234 id = " id=\"#{$2}\"" if $2 235 when /([A-Za-z]+):(.*)/ then 236 scheme = $1.downcase 237 path = $2 238 when /^#/ then 239 else 240 scheme = 'http' 241 path = url 242 url = "http://#{url}" 243 end 244 245 if scheme == 'link' then 246 url = if path[0, 1] == '#' then # is this meaningful? 247 path 248 else 249 self.class.gen_relative_url @from_path, path 250 end 251 end 252 253 [scheme, url, id] 254 end 255 256 ## 257 # Is +tag+ a tt tag? 258 259 def tt? tag 260 tag.bit == @tt_bit 261 end 262 263end 264 265