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