html2haml 1.0.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,10 @@
1
+ /.yardoc
2
+ /coverage
3
+ /doc
4
+ /pkg
5
+ *.rbc
6
+ .rbenv-version
7
+ Gemfile.lock
8
+ .rvmrc
9
+ .rbx
10
+ tmp
@@ -0,0 +1,14 @@
1
+ rvm:
2
+ - 1.8.7
3
+ - 1.9.3
4
+ - jruby-18mode
5
+ - rbx-18mode
6
+
7
+ gemfile:
8
+ - Gemfile
9
+
10
+ branches:
11
+ only:
12
+ - master
13
+
14
+ script: "bundle exec rake test"
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2006-2012 Hampton Catlin, Nathan Weizenbaum and Norman Clarke
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7
+ the Software, and to permit persons to whom the Software is furnished to do so,
8
+ subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
15
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,40 @@
1
+ # Html2haml
2
+
3
+ Converts HTML to Haml. This is in the process of being extracted from the Haml
4
+ gem.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ gem 'html2haml'
11
+
12
+ And then execute:
13
+
14
+ $ bundle
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install html2haml
19
+
20
+ ## Usage
21
+
22
+ See `html2haml --help`:
23
+
24
+ Usage: html2haml [options] [INPUT] [OUTPUT]
25
+
26
+ Description: Transforms an HTML file into corresponding Haml code.
27
+
28
+ Options:
29
+ -e, --erb Parse ERb tags.
30
+ --no-erb Don't parse ERb tags.
31
+ -r, --rhtml Deprecated; same as --erb.
32
+ --no-rhtml Deprecated; same as --no-erb.
33
+ -x, --xhtml Parse the input using the more strict XHTML parser.
34
+ --html-attributes Use HTML style attributes instead of Ruby hash style.
35
+ -E ex[:in] Specify the default external and internal character encodings.
36
+ -s, --stdin Read input from standard input instead of an input file
37
+ --trace Show a full traceback on error
38
+ --unix-newlines Use Unix-style newlines in written files.
39
+ -?, -h, --help Show this message
40
+ -v, --version Print version
@@ -0,0 +1,25 @@
1
+ require "rake/clean"
2
+ require "rake/testtask"
3
+ require "rubygems/package_task"
4
+
5
+ task :default => :test
6
+
7
+ CLEAN.replace %w(pkg doc coverage .yardoc)
8
+
9
+ Rake::TestTask.new do |t|
10
+ t.libs << 'lib' << 'test'
11
+ t.test_files = Dir["test/**/*_test.rb"]
12
+ t.verbose = true
13
+ end
14
+
15
+ task :set_coverage_env do
16
+ ENV["COVERAGE"] = "true"
17
+ end
18
+
19
+ desc "Run Simplecov (only works on 1.9)"
20
+ task :coverage => [:set_coverage_env, :test]
21
+
22
+ gemspec = File.expand_path("../html2haml.gemspec", __FILE__)
23
+ if File.exist? gemspec
24
+ Gem::PackageTask.new(eval(File.read(gemspec))) { |pkg| }
25
+ end
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.dirname(__FILE__) + '/../lib/html2haml'
4
+ require 'html2haml/exec'
5
+
6
+ opts = Html2haml::Exec::HTML2Haml.new(ARGV)
7
+ opts.parse!
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/html2haml/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Norman Clarke"]
6
+ gem.email = ["norman@njclarke.com"]
7
+ gem.description = %q{Converts HTML into Haml}
8
+ gem.summary = %q{Converts HTML into Haml}
9
+ gem.homepage = "http://haml.info"
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "html2haml"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Html2haml::VERSION
17
+
18
+ gem.add_dependency 'hpricot'
19
+ gem.add_dependency 'haml', '>= 3.2.0.beta.1'
20
+ gem.add_development_dependency 'erubis'
21
+ gem.add_development_dependency 'ruby_parser'
22
+ gem.add_development_dependency 'simplecov'
23
+ gem.add_development_dependency 'rake'
24
+ gem.add_development_dependency 'minitest'
25
+ end
@@ -0,0 +1,8 @@
1
+ require "html2haml/version"
2
+ require "haml/util"
3
+ require "haml/parser"
4
+ require "haml/error"
5
+ require "html2haml/html"
6
+
7
+ module Html2haml
8
+ end
@@ -0,0 +1,260 @@
1
+ require 'optparse'
2
+ require 'fileutils'
3
+ require 'rbconfig'
4
+
5
+ module Html2haml
6
+ # This module handles the various Haml executables (`haml` and `haml-convert`).
7
+ module Exec
8
+ # An abstract class that encapsulates the executable code for all three executables.
9
+ class Generic
10
+ # @param args [Array<String>] The command-line arguments
11
+ def initialize(args)
12
+ @args = args
13
+ @options = {:for_engine => {}}
14
+ end
15
+
16
+ # Parses the command-line arguments and runs the executable.
17
+ # Calls `Kernel#exit` at the end, so it never returns.
18
+ #
19
+ # @see #parse
20
+ def parse!
21
+ begin
22
+ parse
23
+ rescue Exception => e
24
+ raise e if @options[:trace] || e.is_a?(SystemExit)
25
+
26
+ $stderr.print "#{e.class}: " unless e.class == RuntimeError
27
+ $stderr.puts "#{e.message}"
28
+ $stderr.puts " Use --trace for backtrace."
29
+ exit 1
30
+ end
31
+ exit 0
32
+ end
33
+
34
+ # Parses the command-line arguments and runs the executable.
35
+ # This does not handle exceptions or exit the program.
36
+ #
37
+ # @see #parse!
38
+ def parse
39
+ @opts = OptionParser.new(&method(:set_opts))
40
+ @opts.parse!(@args)
41
+
42
+ process_result
43
+
44
+ @options
45
+ end
46
+
47
+ # @return [String] A description of the executable
48
+ def to_s
49
+ @opts.to_s
50
+ end
51
+
52
+ protected
53
+
54
+ # Finds the line of the source template
55
+ # on which an exception was raised.
56
+ #
57
+ # @param exception [Exception] The exception
58
+ # @return [String] The line number
59
+ def get_line(exception)
60
+ # SyntaxErrors have weird line reporting
61
+ # when there's trailing whitespace,
62
+ # which there is for Haml documents.
63
+ return (exception.message.scan(/:(\d+)/).first || ["??"]).first if exception.is_a?(::SyntaxError)
64
+ (exception.backtrace[0].scan(/:(\d+)/).first || ["??"]).first
65
+ end
66
+
67
+ # Tells optparse how to parse the arguments
68
+ # available for all executables.
69
+ #
70
+ # This is meant to be overridden by subclasses
71
+ # so they can add their own options.
72
+ #
73
+ # @param opts [OptionParser]
74
+ def set_opts(opts)
75
+ opts.on('-s', '--stdin', :NONE, 'Read input from standard input instead of an input file') do
76
+ @options[:input] = $stdin
77
+ end
78
+
79
+ opts.on('--trace', :NONE, 'Show a full traceback on error') do
80
+ @options[:trace] = true
81
+ end
82
+
83
+ opts.on('--unix-newlines', 'Use Unix-style newlines in written files.') do
84
+ # Note that this is the preferred way to check for Windows, since
85
+ # JRuby and Rubinius also run there.
86
+ if RbConfig::CONFIG['host_os'] =~ /mswin|windows|mingw/i
87
+ @options[:unix_newlines] = true
88
+ end
89
+ end
90
+
91
+ opts.on_tail("-?", "-h", "--help", "Show this message") do
92
+ puts opts
93
+ exit
94
+ end
95
+
96
+ opts.on_tail("-v", "--version", "Print version") do
97
+ puts("html2haml #{::Html2haml::VERSION}")
98
+ exit
99
+ end
100
+ end
101
+
102
+ # Processes the options set by the command-line arguments.
103
+ # In particular, sets `@options[:input]` and `@options[:output]`
104
+ # to appropriate IO streams.
105
+ #
106
+ # This is meant to be overridden by subclasses
107
+ # so they can run their respective programs.
108
+ def process_result
109
+ input, output = @options[:input], @options[:output]
110
+ args = @args.dup
111
+ input ||=
112
+ begin
113
+ filename = args.shift
114
+ @options[:filename] = filename
115
+ open_file(filename) || $stdin
116
+ end
117
+ output ||= open_file(args.shift, 'w') || $stdout
118
+
119
+ @options[:input], @options[:output] = input, output
120
+ end
121
+
122
+ COLORS = { :red => 31, :green => 32, :yellow => 33 }
123
+
124
+ # Prints a status message about performing the given action,
125
+ # colored using the given color (via terminal escapes) if possible.
126
+ #
127
+ # @param name [#to_s] A short name for the action being performed.
128
+ # Shouldn't be longer than 11 characters.
129
+ # @param color [Symbol] The name of the color to use for this action.
130
+ # Can be `:red`, `:green`, or `:yellow`.
131
+ def puts_action(name, color, arg)
132
+ return if @options[:for_engine][:quiet]
133
+ printf color(color, "%11s %s\n"), name, arg
134
+ end
135
+
136
+ # Same as `Kernel.puts`, but doesn't print anything if the `--quiet` option is set.
137
+ #
138
+ # @param args [Array] Passed on to `Kernel.puts`
139
+ def puts(*args)
140
+ return if @options[:for_engine][:quiet]
141
+ Kernel.puts(*args)
142
+ end
143
+
144
+ # Wraps the given string in terminal escapes
145
+ # causing it to have the given color.
146
+ # If terminal esapes aren't supported on this platform,
147
+ # just returns the string instead.
148
+ #
149
+ # @param color [Symbol] The name of the color to use.
150
+ # Can be `:red`, `:green`, or `:yellow`.
151
+ # @param str [String] The string to wrap in the given color.
152
+ # @return [String] The wrapped string.
153
+ def color(color, str)
154
+ raise "[BUG] Unrecognized color #{color}" unless COLORS[color]
155
+
156
+ # Almost any real Unix terminal will support color,
157
+ # so we just filter for Windows terms (which don't set TERM)
158
+ # and not-real terminals, which aren't ttys.
159
+ return str if ENV["TERM"].nil? || ENV["TERM"].empty? || !STDOUT.tty?
160
+ return "\e[#{COLORS[color]}m#{str}\e[0m"
161
+ end
162
+
163
+ private
164
+
165
+ def open_file(filename, flag = 'r')
166
+ return if filename.nil?
167
+ flag = 'wb' if @options[:unix_newlines] && flag == 'w'
168
+ File.open(filename, flag)
169
+ end
170
+
171
+ def handle_load_error(err)
172
+ dep = err.message[/^no such file to load -- (.*)/, 1]
173
+ raise err if @options[:trace] || dep.nil? || dep.empty?
174
+ $stderr.puts <<MESSAGE
175
+ Required dependency #{dep} not found!
176
+ Run "gem install #{dep}" to get it.
177
+ Use --trace for backtrace.
178
+ MESSAGE
179
+ exit 1
180
+ end
181
+ end
182
+
183
+ # The `html2haml` executable.
184
+ class HTML2Haml < Generic
185
+ # @param args [Array<String>] The command-line arguments
186
+ def initialize(args)
187
+ super
188
+ @module_opts = {}
189
+ end
190
+
191
+ # Tells optparse how to parse the arguments.
192
+ #
193
+ # @param opts [OptionParser]
194
+ def set_opts(opts)
195
+ opts.banner = <<END
196
+ Usage: html2haml [options] [INPUT] [OUTPUT]
197
+
198
+ Description: Transforms an HTML file into corresponding Haml code.
199
+
200
+ Options:
201
+ END
202
+
203
+ opts.on('-e', '--erb', 'Parse ERb tags.') do
204
+ @module_opts[:erb] = true
205
+ end
206
+
207
+ opts.on('--no-erb', "Don't parse ERb tags.") do
208
+ @options[:no_erb] = true
209
+ end
210
+
211
+ opts.on('-r', '--rhtml', 'Deprecated; same as --erb.') do
212
+ @module_opts[:erb] = true
213
+ end
214
+
215
+ opts.on('--no-rhtml', "Deprecated; same as --no-erb.") do
216
+ @options[:no_erb] = true
217
+ end
218
+
219
+ opts.on('-x', '--xhtml', 'Parse the input using the more strict XHTML parser.') do
220
+ @module_opts[:xhtml] = true
221
+ end
222
+
223
+ opts.on("--html-attributes", "Use HTML style attributes instead of Ruby hash style.") do
224
+ @module_opts[:html_style_attributes] = true
225
+ end
226
+
227
+ unless RUBY_VERSION < "1.9"
228
+ opts.on('-E ex[:in]', 'Specify the default external and internal character encodings.') do |encoding|
229
+ external, internal = encoding.split(':')
230
+ Encoding.default_external = external if external && !external.empty?
231
+ Encoding.default_internal = internal if internal && !internal.empty?
232
+ end
233
+ end
234
+
235
+ super
236
+ end
237
+
238
+ # Processes the options set by the command-line arguments,
239
+ # and runs the HTML compiler appropriately.
240
+ def process_result
241
+ super
242
+
243
+ require 'html2haml/html'
244
+
245
+ input = @options[:input]
246
+ output = @options[:output]
247
+
248
+ @module_opts[:erb] ||= input.respond_to?(:path) && input.path =~ /\.(rhtml|erb)$/
249
+ @module_opts[:erb] &&= @options[:no_erb] != false
250
+
251
+ output.write(::Haml::HTML.new(input, @module_opts).render)
252
+ rescue ::Haml::Error => e
253
+ raise "#{e.is_a?(::Haml::SyntaxError) ? "Syntax error" : "Error"} on line " +
254
+ "#{get_line e}: #{e.message}"
255
+ rescue LoadError => err
256
+ handle_load_error(err)
257
+ end
258
+ end
259
+ end
260
+ end
@@ -0,0 +1,423 @@
1
+ require 'rubygems'
2
+ require 'cgi'
3
+ require 'hpricot'
4
+ require 'html2haml/html/erb'
5
+
6
+ # Haml monkeypatches various Hpricot classes
7
+ # to add methods for conversion to Haml.
8
+ # @private
9
+ module Hpricot
10
+ # @see Hpricot
11
+ module Node
12
+ # Whether this node has already been converted to Haml.
13
+ # Only used for text nodes and elements.
14
+ #
15
+ # @return [Boolean]
16
+ attr_accessor :converted_to_haml
17
+
18
+ # Returns the Haml representation of the given node.
19
+ #
20
+ # @param tabs [Fixnum] The indentation level of the resulting Haml.
21
+ # @option options (see Haml::HTML#initialize)
22
+ def to_haml(tabs, options)
23
+ return "" if converted_to_haml || to_s.strip.empty?
24
+ text = uninterp(self.to_s)
25
+ node = next_node
26
+ while node.is_a?(::Hpricot::Elem) && node.name == "haml:loud"
27
+ node.converted_to_haml = true
28
+ text << '#{' <<
29
+ CGI.unescapeHTML(node.inner_text).gsub(/\n\s*/, ' ').strip << '}'
30
+
31
+ if node.next_node.is_a?(::Hpricot::Text)
32
+ node = node.next_node
33
+ text << uninterp(node.to_s)
34
+ node.converted_to_haml = true
35
+ end
36
+
37
+ node = node.next_node
38
+ end
39
+ return parse_text_with_interpolation(text, tabs)
40
+ end
41
+
42
+ private
43
+
44
+ def erb_to_interpolation(text, options)
45
+ return text unless options[:erb]
46
+ text = CGI.escapeHTML(uninterp(text))
47
+ %w[<haml:loud> </haml:loud>].each {|str| text.gsub!(CGI.escapeHTML(str), str)}
48
+ ::Hpricot::XML(text).children.inject("") do |str, elem|
49
+ if elem.is_a?(::Hpricot::Text)
50
+ str + CGI.unescapeHTML(elem.to_s)
51
+ else # <haml:loud> element
52
+ str + '#{' + CGI.unescapeHTML(elem.innerText.strip) + '}'
53
+ end
54
+ end
55
+ end
56
+
57
+ def tabulate(tabs)
58
+ ' ' * tabs
59
+ end
60
+
61
+ def uninterp(text)
62
+ text.gsub('#{', '\#{') #'
63
+ end
64
+
65
+ def attr_hash
66
+ attributes.to_hash
67
+ end
68
+
69
+ def parse_text(text, tabs)
70
+ parse_text_with_interpolation(uninterp(text), tabs)
71
+ end
72
+
73
+ def parse_text_with_interpolation(text, tabs)
74
+ text.strip!
75
+ return "" if text.empty?
76
+
77
+ text.split("\n").map do |line|
78
+ line.strip!
79
+ "#{tabulate(tabs)}#{'\\' if Haml::Parser::SPECIAL_CHARACTERS.include?(line[0])}#{line}\n"
80
+ end.join
81
+ end
82
+ end
83
+ end
84
+
85
+ # @private
86
+ HAML_TAGS = %w[haml:block haml:loud haml:silent]
87
+
88
+ HAML_TAGS.each do |t|
89
+ Hpricot::ElementContent[t] = {}
90
+ Hpricot::ElementContent.keys.each do |key|
91
+ Hpricot::ElementContent[t][key.hash] = true
92
+ end
93
+ end
94
+
95
+ Hpricot::ElementContent.keys.each do |k|
96
+ HAML_TAGS.each do |el|
97
+ val = Hpricot::ElementContent[k]
98
+ val[el.hash] = true if val.is_a?(Hash)
99
+ end
100
+ end
101
+
102
+ module Haml
103
+ # Converts HTML documents into Haml templates.
104
+ # Depends on [Hpricot](http://github.com/whymirror/hpricot) for HTML parsing.
105
+ # If ERB conversion is being used, also depends on
106
+ # [Erubis](http://www.kuwata-lab.com/erubis) to parse the ERB
107
+ # and [ruby_parser](http://parsetree.rubyforge.org/) to parse the Ruby code.
108
+ #
109
+ # Example usage:
110
+ #
111
+ # Haml::HTML.new("<a href='http://google.com'>Blat</a>").render
112
+ # #=> "%a{:href => 'http://google.com'} Blat"
113
+ class HTML
114
+ # @param template [String, Hpricot::Node] The HTML template to convert
115
+ # @option options :erb [Boolean] (false) Whether or not to parse
116
+ # ERB's `<%= %>` and `<% %>` into Haml's `=` and `-`
117
+ # @option options :xhtml [Boolean] (false) Whether or not to parse
118
+ # the HTML strictly as XHTML
119
+ def initialize(template, options = {})
120
+ @options = options
121
+
122
+ if template.is_a? Hpricot::Node
123
+ @template = template
124
+ else
125
+ if template.is_a? IO
126
+ template = template.read
127
+ end
128
+
129
+ template = Haml::Util.check_encoding(template) {|msg, line| raise Haml::Error.new(msg, line)}
130
+
131
+ if @options[:erb]
132
+ require 'html2haml/html/erb'
133
+ template = ERB.compile(template)
134
+ end
135
+
136
+ method = @options[:xhtml] ? Hpricot.method(:XML) : method(:Hpricot)
137
+ @template = method.call(template.gsub('&', '&amp;'))
138
+ end
139
+ end
140
+
141
+ # Processes the document and returns the result as a string
142
+ # containing the Haml template.
143
+ def render
144
+ @template.to_haml(0, @options)
145
+ end
146
+ alias_method :to_haml, :render
147
+
148
+ TEXT_REGEXP = /^(\s*).*$/
149
+
150
+ # @see Hpricot
151
+ # @private
152
+ class ::Hpricot::Doc
153
+ # @see Haml::HTML::Node#to_haml
154
+ def to_haml(tabs, options)
155
+ (children || []).inject('') {|s, c| s << c.to_haml(0, options)}
156
+ end
157
+ end
158
+
159
+ # @see Hpricot
160
+ # @private
161
+ class ::Hpricot::XMLDecl
162
+ # @see Haml::HTML::Node#to_haml
163
+ def to_haml(tabs, options)
164
+ "#{tabulate(tabs)}!!! XML\n"
165
+ end
166
+ end
167
+
168
+ # @see Hpricot
169
+ # @private
170
+ class ::Hpricot::CData
171
+ # @see Haml::HTML::Node#to_haml
172
+ def to_haml(tabs, options)
173
+ content = parse_text_with_interpolation(
174
+ erb_to_interpolation(self.content, options), tabs + 1)
175
+ "#{tabulate(tabs)}:cdata\n#{content}"
176
+ end
177
+ end
178
+
179
+ # @see Hpricot
180
+ # @private
181
+ class ::Hpricot::DocType
182
+ # @see Haml::HTML::Node#to_haml
183
+ def to_haml(tabs, options)
184
+ attrs = public_id.nil? ? ["", "", ""] :
185
+ public_id.scan(/DTD\s+([^\s]+)\s*([^\s]*)\s*([^\s]*)\s*\/\//)[0]
186
+ raise Haml::SyntaxError.new("Invalid doctype") if attrs == nil
187
+
188
+ type, version, strictness = attrs.map { |a| a.downcase }
189
+ if type == "html"
190
+ version = ""
191
+ strictness = "strict" if strictness == ""
192
+ end
193
+
194
+ if version == "1.0" || version.empty?
195
+ version = nil
196
+ end
197
+
198
+ if strictness == 'transitional' || strictness.empty?
199
+ strictness = nil
200
+ end
201
+
202
+ version = " #{version.capitalize}" if version
203
+ strictness = " #{strictness.capitalize}" if strictness
204
+
205
+ "#{tabulate(tabs)}!!!#{version}#{strictness}\n"
206
+ end
207
+ end
208
+
209
+ # @see Hpricot
210
+ # @private
211
+ class ::Hpricot::Comment
212
+ # @see Haml::HTML::Node#to_haml
213
+ def to_haml(tabs, options)
214
+ content = self.content
215
+ if content =~ /\A(\[[^\]]+\])>(.*)<!\[endif\]\z/m
216
+ condition = $1
217
+ content = $2
218
+ end
219
+
220
+ if content.include?("\n")
221
+ "#{tabulate(tabs)}/#{condition}\n#{parse_text(content, tabs + 1)}"
222
+ else
223
+ "#{tabulate(tabs)}/#{condition} #{content.strip}\n"
224
+ end
225
+ end
226
+ end
227
+
228
+ # @see Hpricot
229
+ # @private
230
+ class ::Hpricot::Elem
231
+ # @see Haml::HTML::Node#to_haml
232
+ def to_haml(tabs, options)
233
+ return "" if converted_to_haml
234
+ if name == "script" &&
235
+ (attr_hash['type'].nil? || attr_hash['type'] == "text/javascript") &&
236
+ (attr_hash.keys - ['type']).empty?
237
+ return to_haml_filter(:javascript, tabs, options)
238
+ elsif name == "style" &&
239
+ (attr_hash['type'].nil? || attr_hash['type'] == "text/css") &&
240
+ (attr_hash.keys - ['type']).empty?
241
+ return to_haml_filter(:css, tabs, options)
242
+ end
243
+
244
+ output = tabulate(tabs)
245
+ if options[:erb] && name[0...5] == 'haml:'
246
+ case name[5..-1]
247
+ when "loud"
248
+ lines = CGI.unescapeHTML(inner_text).split("\n").
249
+ map {|s| s.rstrip}.reject {|s| s.strip.empty?}
250
+ lines.first.gsub!(/^[ \t]*/, "= ")
251
+
252
+ if lines.size > 1 # Multiline script block
253
+ # Normalize the indentation so that the last line is the base
254
+ indent_str = lines.last[/^[ \t]*/]
255
+ indent_re = /^[ \t]{0,#{indent_str.count(" ") + 8 * indent_str.count("\t")}}/
256
+ lines.map! {|s| s.gsub!(indent_re, '')}
257
+
258
+ # Add an extra " " to make it indented relative to "= "
259
+ lines[1..-1].each {|s| s.gsub!(/^/, " ")}
260
+
261
+ # Add | at the end, properly aligned
262
+ length = lines.map {|s| s.size}.max + 1
263
+ lines.map! {|s| "%#{-length}s|" % s}
264
+
265
+ if next_sibling && next_sibling.is_a?(Hpricot::Elem) && next_sibling.name == "haml:loud" &&
266
+ next_sibling.inner_text.split("\n").reject {|s| s.strip.empty?}.size > 1
267
+ lines << "-#"
268
+ end
269
+ end
270
+ return lines.map {|s| output + s + "\n"}.join
271
+ when "silent"
272
+ return CGI.unescapeHTML(inner_text).split("\n").map do |line|
273
+ next "" if line.strip.empty?
274
+ "#{output}- #{line.strip}\n"
275
+ end.join
276
+ when "block"
277
+ return render_children("", tabs, options)
278
+ end
279
+ end
280
+
281
+ if self.next && self.next.text? && self.next.content =~ /\A[^\s]/
282
+ if self.previous.nil? || self.previous.text? &&
283
+ (self.previous.content =~ /[^\s]\Z/ ||
284
+ self.previous.content =~ /\A\s*\Z/ && self.previous.previous.nil?)
285
+ nuke_outer_whitespace = true
286
+ else
287
+ output << "= succeed #{self.next.content.slice!(/\A[^\s]+/).dump} do\n"
288
+ tabs += 1
289
+ output << tabulate(tabs)
290
+ end
291
+ end
292
+
293
+ output << "%#{name}" unless name == 'div' &&
294
+ (static_id?(options) ||
295
+ static_classname?(options) &&
296
+ attr_hash['class'].split(' ').any?(&method(:haml_css_attr?)))
297
+
298
+ if attr_hash
299
+ if static_id?(options)
300
+ output << "##{attr_hash['id']}"
301
+ remove_attribute('id')
302
+ end
303
+ if static_classname?(options)
304
+ leftover = attr_hash['class'].split(' ').reject do |c|
305
+ next unless haml_css_attr?(c)
306
+ output << ".#{c}"
307
+ end
308
+ remove_attribute('class')
309
+ set_attribute('class', leftover.join(' ')) unless leftover.empty?
310
+ end
311
+ output << haml_attributes(options) if attr_hash.length > 0
312
+ end
313
+
314
+ output << ">" if nuke_outer_whitespace
315
+ output << "/" if empty? && !etag
316
+
317
+ if children && children.size == 1
318
+ child = children.first
319
+ if child.is_a?(::Hpricot::Text)
320
+ if !child.to_s.include?("\n")
321
+ text = child.to_haml(tabs + 1, options)
322
+ return output + " " + text.lstrip.gsub(/^\\/, '') unless text.chomp.include?("\n")
323
+ return output + "\n" + text
324
+ elsif ["pre", "textarea"].include?(name) ||
325
+ (name == "code" && parent.is_a?(::Hpricot::Elem) && parent.name == "pre")
326
+ return output + "\n#{tabulate(tabs + 1)}:preserve\n" +
327
+ innerText.gsub(/^/, tabulate(tabs + 2))
328
+ end
329
+ elsif child.is_a?(::Hpricot::Elem) && child.name == "haml:loud"
330
+ return output + child.to_haml(tabs + 1, options).lstrip
331
+ end
332
+ end
333
+
334
+ render_children(output + "\n", tabs, options)
335
+ end
336
+
337
+ private
338
+
339
+ def render_children(so_far, tabs, options)
340
+ (self.children || []).inject(so_far) do |output, child|
341
+ output + child.to_haml(tabs + 1, options)
342
+ end
343
+ end
344
+
345
+ def dynamic_attributes
346
+ @dynamic_attributes ||= begin
347
+ Hash[attr_hash.map do |name, value|
348
+ next if value.empty?
349
+ full_match = nil
350
+ ruby_value = value.gsub(%r{<haml:loud>\s*(.+?)\s*</haml:loud>}) do
351
+ full_match = $`.empty? && $'.empty?
352
+ CGI.unescapeHTML(full_match ? $1: "\#{#{$1}}")
353
+ end
354
+ next if ruby_value == value
355
+ [name, full_match ? ruby_value : %("#{ruby_value}")]
356
+ end]
357
+ end
358
+ end
359
+
360
+ def to_haml_filter(filter, tabs, options)
361
+ content =
362
+ if children.first.is_a?(::Hpricot::CData)
363
+ children.first.content
364
+ else
365
+ CGI.unescapeHTML(self.innerText)
366
+ end
367
+
368
+ content = erb_to_interpolation(content, options)
369
+ content.gsub!(/\A\s*\n(\s*)/, '\1')
370
+ original_indent = content[/\A(\s*)/, 1]
371
+ if content.split("\n").all? {|l| l.strip.empty? || l =~ /^#{original_indent}/}
372
+ content.gsub!(/^#{original_indent}/, tabulate(tabs + 1))
373
+ end
374
+
375
+ "#{tabulate(tabs)}:#{filter}\n#{content}"
376
+ end
377
+
378
+ def static_attribute?(name, options)
379
+ attr_hash[name] && !dynamic_attribute?(name, options)
380
+ end
381
+
382
+ def dynamic_attribute?(name, options)
383
+ options[:erb] and dynamic_attributes.key?(name)
384
+ end
385
+
386
+ def static_id?(options)
387
+ static_attribute?('id', options) && haml_css_attr?(attr_hash['id'])
388
+ end
389
+
390
+ def static_classname?(options)
391
+ static_attribute?('class', options)
392
+ end
393
+
394
+ def haml_css_attr?(attr)
395
+ attr =~ /^[-:\w]+$/
396
+ end
397
+
398
+ # Returns a string representation of an attributes hash
399
+ # that's prettier than that produced by Hash#inspect
400
+ def haml_attributes(options)
401
+ attrs = attr_hash.sort.map do |name, value|
402
+ haml_attribute_pair(name, value, options)
403
+ end
404
+ if options[:html_style_attributes]
405
+ "(#{attrs.join(' ')})"
406
+ else
407
+ "{#{attrs.join(', ')}}"
408
+ end
409
+ end
410
+
411
+ # Returns the string representation of a single attribute key value pair
412
+ def haml_attribute_pair(name, value, options)
413
+ value = dynamic_attribute?(name, options) ? dynamic_attributes[name] : value.inspect
414
+ if options[:html_style_attributes]
415
+ "#{name}=#{value}"
416
+ else
417
+ name = name.index(/\W/) ? name.inspect : ":#{name}"
418
+ "#{name} => #{value}"
419
+ end
420
+ end
421
+ end
422
+ end
423
+ end