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.
- data/.gitignore +10 -0
- data/.travis.yml +14 -0
- data/Gemfile +4 -0
- data/MIT-LICENSE +18 -0
- data/README.md +40 -0
- data/Rakefile +25 -0
- data/bin/html2haml +7 -0
- data/html2haml.gemspec +25 -0
- data/lib/html2haml.rb +8 -0
- data/lib/html2haml/exec.rb +260 -0
- data/lib/html2haml/html.rb +423 -0
- data/lib/html2haml/html/erb.rb +153 -0
- data/lib/html2haml/version.rb +3 -0
- data/test/erb_test.rb +477 -0
- data/test/html2haml_test.rb +323 -0
- data/test/test_helper.rb +22 -0
- metadata +180 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/MIT-LICENSE
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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
|
data/Rakefile
ADDED
@@ -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
|
data/bin/html2haml
ADDED
data/html2haml.gemspec
ADDED
@@ -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
|
data/lib/html2haml.rb
ADDED
@@ -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('&', '&'))
|
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
|