html2haml 1.0.0.beta.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.
@@ -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