annotate vendor/plugins/coderay-0.9.2/lib/coderay/encoders/html.rb @ 877:e97cef3bd5d0 bug_70

Close obsolete branch bug_70
author Chris Cannam
date Wed, 30 Mar 2011 10:48:32 +0100
parents 513646585e45
children
rev   line source
Chris@0 1 require 'set'
Chris@0 2
Chris@0 3 module CodeRay
Chris@0 4 module Encoders
Chris@0 5
Chris@0 6 # = HTML Encoder
Chris@0 7 #
Chris@0 8 # This is CodeRay's most important highlighter:
Chris@0 9 # It provides save, fast XHTML generation and CSS support.
Chris@0 10 #
Chris@0 11 # == Usage
Chris@0 12 #
Chris@0 13 # require 'coderay'
Chris@0 14 # puts CodeRay.scan('Some /code/', :ruby).html #-> a HTML page
Chris@0 15 # puts CodeRay.scan('Some /code/', :ruby).html(:wrap => :span)
Chris@0 16 # #-> <span class="CodeRay"><span class="co">Some</span> /code/</span>
Chris@0 17 # puts CodeRay.scan('Some /code/', :ruby).span #-> the same
Chris@0 18 #
Chris@0 19 # puts CodeRay.scan('Some code', :ruby).html(
Chris@0 20 # :wrap => nil,
Chris@0 21 # :line_numbers => :inline,
Chris@0 22 # :css => :style
Chris@0 23 # )
Chris@0 24 # #-> <span class="no">1</span> <span style="color:#036; font-weight:bold;">Some</span> code
Chris@0 25 #
Chris@0 26 # == Options
Chris@0 27 #
Chris@0 28 # === :tab_width
Chris@0 29 # Convert \t characters to +n+ spaces (a number.)
Chris@0 30 # Default: 8
Chris@0 31 #
Chris@0 32 # === :css
Chris@0 33 # How to include the styles; can be :class or :style.
Chris@0 34 #
Chris@0 35 # Default: :class
Chris@0 36 #
Chris@0 37 # === :wrap
Chris@0 38 # Wrap in :page, :div, :span or nil.
Chris@0 39 #
Chris@0 40 # You can also use Encoders::Div and Encoders::Span.
Chris@0 41 #
Chris@0 42 # Default: nil
Chris@0 43 #
Chris@0 44 # === :title
Chris@0 45 #
Chris@0 46 # The title of the HTML page (works only when :wrap is set to :page.)
Chris@0 47 #
Chris@0 48 # Default: 'CodeRay output'
Chris@0 49 #
Chris@0 50 # === :line_numbers
Chris@0 51 # Include line numbers in :table, :inline, :list or nil (no line numbers)
Chris@0 52 #
Chris@0 53 # Default: nil
Chris@0 54 #
Chris@0 55 # === :line_number_start
Chris@0 56 # Where to start with line number counting.
Chris@0 57 #
Chris@0 58 # Default: 1
Chris@0 59 #
Chris@0 60 # === :bold_every
Chris@0 61 # Make every +n+-th number appear bold.
Chris@0 62 #
Chris@0 63 # Default: 10
Chris@0 64 #
Chris@0 65 # === :highlight_lines
Chris@0 66 #
Chris@0 67 # Highlights certain line numbers.
Chris@0 68 # Can be any Enumerable, typically just an Array or Range, of numbers.
Chris@0 69 #
Chris@0 70 # Bolding is deactivated when :highlight_lines is set. It only makes sense
Chris@0 71 # in combination with :line_numbers.
Chris@0 72 #
Chris@0 73 # Default: nil
Chris@0 74 #
Chris@0 75 # === :hint
Chris@0 76 # Include some information into the output using the title attribute.
Chris@0 77 # Can be :info (show token type on mouse-over), :info_long (with full path)
Chris@0 78 # or :debug (via inspect).
Chris@0 79 #
Chris@0 80 # Default: false
Chris@0 81 class HTML < Encoder
Chris@0 82
Chris@0 83 include Streamable
Chris@0 84 register_for :html
Chris@0 85
Chris@0 86 FILE_EXTENSION = 'html'
Chris@0 87
Chris@0 88 DEFAULT_OPTIONS = {
Chris@0 89 :tab_width => 8,
Chris@0 90
Chris@0 91 :css => :class,
Chris@0 92
Chris@0 93 :style => :cycnus,
Chris@0 94 :wrap => nil,
Chris@0 95 :title => 'CodeRay output',
Chris@0 96
Chris@0 97 :line_numbers => nil,
Chris@0 98 :line_number_start => 1,
Chris@0 99 :bold_every => 10,
Chris@0 100 :highlight_lines => nil,
Chris@0 101
Chris@0 102 :hint => false,
Chris@0 103 }
Chris@0 104
Chris@0 105 helper :output, :css
Chris@0 106
Chris@0 107 attr_reader :css
Chris@0 108
Chris@0 109 protected
Chris@0 110
Chris@0 111 HTML_ESCAPE = { #:nodoc:
Chris@0 112 '&' => '&amp;',
Chris@0 113 '"' => '&quot;',
Chris@0 114 '>' => '&gt;',
Chris@0 115 '<' => '&lt;',
Chris@0 116 }
Chris@0 117
Chris@0 118 # This was to prevent illegal HTML.
Chris@0 119 # Strange chars should still be avoided in codes.
Chris@0 120 evil_chars = Array(0x00...0x20) - [?\n, ?\t, ?\s]
Chris@0 121 evil_chars.each { |i| HTML_ESCAPE[i.chr] = ' ' }
Chris@0 122 #ansi_chars = Array(0x7f..0xff)
Chris@0 123 #ansi_chars.each { |i| HTML_ESCAPE[i.chr] = '&#%d;' % i }
Chris@0 124 # \x9 (\t) and \xA (\n) not included
Chris@0 125 #HTML_ESCAPE_PATTERN = /[\t&"><\0-\x8\xB-\x1f\x7f-\xff]/
Chris@0 126 HTML_ESCAPE_PATTERN = /[\t"&><\0-\x8\xB-\x1f]/
Chris@0 127
Chris@0 128 TOKEN_KIND_TO_INFO = Hash.new { |h, kind|
Chris@0 129 h[kind] =
Chris@0 130 case kind
Chris@0 131 when :pre_constant
Chris@0 132 'Predefined constant'
Chris@0 133 else
Chris@0 134 kind.to_s.gsub(/_/, ' ').gsub(/\b\w/) { $&.capitalize }
Chris@0 135 end
Chris@0 136 }
Chris@0 137
Chris@0 138 TRANSPARENT_TOKEN_KINDS = [
Chris@0 139 :delimiter, :modifier, :content, :escape, :inline_delimiter,
Chris@0 140 ].to_set
Chris@0 141
Chris@0 142 # Generate a hint about the given +classes+ in a +hint+ style.
Chris@0 143 #
Chris@0 144 # +hint+ may be :info, :info_long or :debug.
Chris@0 145 def self.token_path_to_hint hint, classes
Chris@0 146 title =
Chris@0 147 case hint
Chris@0 148 when :info
Chris@0 149 TOKEN_KIND_TO_INFO[classes.first]
Chris@0 150 when :info_long
Chris@0 151 classes.reverse.map { |kind| TOKEN_KIND_TO_INFO[kind] }.join('/')
Chris@0 152 when :debug
Chris@0 153 classes.inspect
Chris@0 154 end
Chris@0 155 title ? " title=\"#{title}\"" : ''
Chris@0 156 end
Chris@0 157
Chris@0 158 def setup options
Chris@0 159 super
Chris@0 160
Chris@0 161 @HTML_ESCAPE = HTML_ESCAPE.dup
Chris@0 162 @HTML_ESCAPE["\t"] = ' ' * options[:tab_width]
Chris@0 163
Chris@0 164 @opened = [nil]
Chris@0 165 @css = CSS.new options[:style]
Chris@0 166
Chris@0 167 hint = options[:hint]
Chris@0 168 if hint and not [:debug, :info, :info_long].include? hint
Chris@0 169 raise ArgumentError, "Unknown value %p for :hint; \
Chris@0 170 expected :info, :debug, false, or nil." % hint
Chris@0 171 end
Chris@0 172
Chris@0 173 case options[:css]
Chris@0 174
Chris@0 175 when :class
Chris@0 176 @css_style = Hash.new do |h, k|
Chris@0 177 c = CodeRay::Tokens::ClassOfKind[k.first]
Chris@0 178 if c == :NO_HIGHLIGHT and not hint
Chris@0 179 h[k.dup] = false
Chris@0 180 else
Chris@0 181 title = if hint
Chris@0 182 HTML.token_path_to_hint(hint, k[1..-1] << k.first)
Chris@0 183 else
Chris@0 184 ''
Chris@0 185 end
Chris@0 186 if c == :NO_HIGHLIGHT
Chris@0 187 h[k.dup] = '<span%s>' % [title]
Chris@0 188 else
Chris@0 189 h[k.dup] = '<span%s class="%s">' % [title, c]
Chris@0 190 end
Chris@0 191 end
Chris@0 192 end
Chris@0 193
Chris@0 194 when :style
Chris@0 195 @css_style = Hash.new do |h, k|
Chris@0 196 if k.is_a? ::Array
Chris@0 197 styles = k.dup
Chris@0 198 else
Chris@0 199 styles = [k]
Chris@0 200 end
Chris@0 201 type = styles.first
Chris@0 202 classes = styles.map { |c| Tokens::ClassOfKind[c] }
Chris@0 203 if classes.first == :NO_HIGHLIGHT and not hint
Chris@0 204 h[k] = false
Chris@0 205 else
Chris@0 206 styles.shift if TRANSPARENT_TOKEN_KINDS.include? styles.first
Chris@0 207 title = HTML.token_path_to_hint hint, styles
Chris@0 208 style = @css[*classes]
Chris@0 209 h[k] =
Chris@0 210 if style
Chris@0 211 '<span%s style="%s">' % [title, style]
Chris@0 212 else
Chris@0 213 false
Chris@0 214 end
Chris@0 215 end
Chris@0 216 end
Chris@0 217
Chris@0 218 else
Chris@0 219 raise ArgumentError, "Unknown value %p for :css." % options[:css]
Chris@0 220
Chris@0 221 end
Chris@0 222 end
Chris@0 223
Chris@0 224 def finish options
Chris@0 225 not_needed = @opened.shift
Chris@0 226 @out << '</span>' * @opened.size
Chris@0 227 unless @opened.empty?
Chris@0 228 warn '%d tokens still open: %p' % [@opened.size, @opened]
Chris@0 229 end
Chris@0 230
Chris@0 231 @out.extend Output
Chris@0 232 @out.css = @css
Chris@0 233 @out.numerize! options[:line_numbers], options
Chris@0 234 @out.wrap! options[:wrap]
Chris@0 235 @out.apply_title! options[:title]
Chris@0 236
Chris@0 237 super
Chris@0 238 end
Chris@0 239
Chris@0 240 def token text, type = :plain
Chris@0 241 case text
Chris@0 242
Chris@0 243 when nil
Chris@0 244 # raise 'Token with nil as text was given: %p' % [[text, type]]
Chris@0 245
Chris@0 246 when String
Chris@0 247 if text =~ /#{HTML_ESCAPE_PATTERN}/o
Chris@0 248 text = text.gsub(/#{HTML_ESCAPE_PATTERN}/o) { |m| @HTML_ESCAPE[m] }
Chris@0 249 end
Chris@0 250 @opened[0] = type
Chris@0 251 if text != "\n" && style = @css_style[@opened]
Chris@0 252 @out << style << text << '</span>'
Chris@0 253 else
Chris@0 254 @out << text
Chris@0 255 end
Chris@0 256
Chris@0 257
Chris@0 258 # token groups, eg. strings
Chris@0 259 when :open
Chris@0 260 @opened[0] = type
Chris@0 261 @out << (@css_style[@opened] || '<span>')
Chris@0 262 @opened << type
Chris@0 263 when :close
Chris@0 264 if @opened.empty?
Chris@0 265 # nothing to close
Chris@0 266 else
Chris@0 267 if $CODERAY_DEBUG and (@opened.size == 1 or @opened.last != type)
Chris@0 268 raise 'Malformed token stream: Trying to close a token (%p) \
Chris@0 269 that is not open. Open are: %p.' % [type, @opened[1..-1]]
Chris@0 270 end
Chris@0 271 @out << '</span>'
Chris@0 272 @opened.pop
Chris@0 273 end
Chris@0 274
Chris@0 275 # whole lines to be highlighted, eg. a deleted line in a diff
Chris@0 276 when :begin_line
Chris@0 277 @opened[0] = type
Chris@0 278 if style = @css_style[@opened]
Chris@0 279 @out << style.sub('<span', '<div')
Chris@0 280 else
Chris@0 281 @out << '<div>'
Chris@0 282 end
Chris@0 283 @opened << type
Chris@0 284 when :end_line
Chris@0 285 if @opened.empty?
Chris@0 286 # nothing to close
Chris@0 287 else
Chris@0 288 if $CODERAY_DEBUG and (@opened.size == 1 or @opened.last != type)
Chris@0 289 raise 'Malformed token stream: Trying to close a line (%p) \
Chris@0 290 that is not open. Open are: %p.' % [type, @opened[1..-1]]
Chris@0 291 end
Chris@0 292 @out << '</div>'
Chris@0 293 @opened.pop
Chris@0 294 end
Chris@0 295
Chris@0 296 else
Chris@0 297 raise 'unknown token kind: %p' % [text]
Chris@0 298
Chris@0 299 end
Chris@0 300 end
Chris@0 301
Chris@0 302 end
Chris@0 303
Chris@0 304 end
Chris@0 305 end