Merge tag 'upstream/2.0.0'
Upstream version 2.0.0
Jérémy Bobbio
8 years ago
0 | language: ruby | |
0 | 1 | rvm: |
1 | - 1.8.7 | |
2 | - 2.1.0 | |
3 | - 2.0.0 | |
2 | 4 | - 1.9.3 |
3 | - jruby-18mode | |
4 | - rbx-18mode | |
5 | - rbx-2 | |
6 | - jruby-19mode | |
5 | 7 | |
6 | 8 | gemfile: |
7 | 9 | - Gemfile |
0 | 0 | # HTML2Haml Changelog |
1 | ||
2 | ## 2.0.0 | |
3 | ||
4 | * Switch to Nokogiri for XML parsing. | |
5 | (thanks to [Stefan Natchev](https://github.com/snatchev) and [Norman | |
6 | Clarke](https://github.com/norman)) | |
7 | ||
8 | * Add Ruby 2.0 support. | |
9 | (thanks to [Yasuharu Ozaki](https://github.com/yasuoza)) | |
10 | ||
11 | * Add option to use Ruby 1.9-style attributes when possible. | |
12 | (thanks to [Yoshinori Kawasaki](https://github.com/luvtechno) and | |
13 | [Alexander Egorov](https://github.com/qatsi)) | |
14 | ||
15 | * Updated dependency versions. | |
16 | ||
17 | * Removed some deprecated configuration flags. | |
18 | ||
19 | * Move the internal HTML class from the Haml namespace into the Html2haml | |
20 | namespace. | |
1 | 21 | |
2 | 22 | ## 1.0.1 |
3 | 23 |
0 | Copyright (c) 2006-2013 Hampton Catlin, Nathan Weizenbaum and Norman Clarke | |
0 | Copyright (c) 2006-2014 Hampton Catlin, Natalie Weizenbaum and Norman Clarke | |
1 | 1 | |
2 | 2 | Permission is hereby granted, free of charge, to any person obtaining a copy of |
3 | 3 | this software and associated documentation files (the "Software"), to deal in |
0 | 0 | # Html2haml |
1 | 1 | |
2 | Converts HTML to Haml. | |
2 | [![Build Status](https://travis-ci.org/haml/html2haml.svg?branch=master)](https://travis-ci.org/haml/html2haml) | |
3 | [![Code Climate](https://codeclimate.com/github/haml/html2haml.svg)](https://codeclimate.com/github/haml/html2haml) | |
4 | [![Gem Version](https://badge.fury.io/rb/html2haml.svg)](https://rubygems.org/gems/html2haml) | |
5 | ||
6 | Html2haml, not surprisingly, converts HTML to Haml. It works on HTML with | |
7 | embedded ERB tags as well as plain old HTML. | |
3 | 8 | |
4 | 9 | ## Installation |
5 | 10 | |
17 | 22 | |
18 | 23 | ## Usage |
19 | 24 | |
25 | ||
26 | ### To convert a project from .erb to .haml | |
27 | ||
28 | If your system has `sed` and `xargs` available and none of your .erb file names | |
29 | have whitespace in them, you can convert all your templates like so: | |
30 | ||
31 | find . -name \*.erb -print | sed 'p;s/.erb$/.haml/' | xargs -n2 html2haml | |
32 | ||
33 | If some of your file names have whitespace or you need finer-grained control | |
34 | over the process, you can convert your files using `gsed` or multi-line script | |
35 | techniques discussed [here](http://stackoverflow.com/questions/17576814/). | |
36 | ||
37 | ||
38 | ### Documentation | |
39 | ||
40 | #### About version 2.0 | |
41 | ||
42 | Html2haml 2.0 differs from 1.x primarily in that it uses Nokgiri as its HTML | |
43 | parser rather than Hpricot. At the current time however, there are some | |
44 | problems running Html2haml 2.0 on JRuby due to differences in the way the Java | |
45 | version of Nokogiri parses HTML. If you are using JRuby you may wish to run | |
46 | HTML2Haml on MRI or use a 1.x version until these problems have been resolved. | |
47 | ||
48 | #### Options | |
49 | ||
50 | Here are the options currently available to Html2haml: | |
51 | ||
20 | 52 | See `html2haml --help`: |
21 | 53 | |
22 | 54 | Usage: html2haml [options] [INPUT] [OUTPUT] |
26 | 58 | Options: |
27 | 59 | -e, --erb Parse ERb tags. |
28 | 60 | --no-erb Don't parse ERb tags. |
29 | -r, --rhtml Deprecated; same as --erb. | |
30 | --no-rhtml Deprecated; same as --no-erb. | |
31 | -x, --xhtml Parse the input using the more strict XHTML parser. | |
32 | 61 | --html-attributes Use HTML style attributes instead of Ruby hash style. |
62 | --ruby19-attributes Use Ruby 1.9-style attributes when possible. | |
33 | 63 | -E ex[:in] Specify the default external and internal character encodings. |
34 | 64 | -s, --stdin Read input from standard input instead of an input file |
35 | 65 | --trace Show a full traceback on error |
39 | 69 | |
40 | 70 | ## License |
41 | 71 | |
42 | Copyright (c) 2006-2013 Hampton Catlin, Nathan Weizenbaum and Norman Clarke | |
72 | Copyright (c) 2006-2014 Hampton Catlin, Natalie Weizenbaum and Norman Clarke | |
43 | 73 | |
44 | 74 | Permission is hereby granted, free of charge, to any person obtaining a copy of |
45 | 75 | this software and associated documentation files (the "Software"), to deal in |
7 | 7 | |
8 | 8 | Rake::TestTask.new do |t| |
9 | 9 | t.libs << 'lib' << 'test' |
10 | t.test_files = Dir["test/**/*_test.rb"] | |
10 | if RUBY_PLATFORM == 'java' | |
11 | t.test_files = FileList["test/**/*_test.rb"] | |
12 | else | |
13 | t.test_files = FileList["test/**/*_test.rb"].exclude(/jruby/) | |
14 | end | |
11 | 15 | t.verbose = true |
12 | 16 | end |
13 | 17 | |
15 | 19 | ENV["COVERAGE"] = "true" |
16 | 20 | end |
17 | 21 | |
18 | desc "Run Simplecov (only works on 1.9)" | |
22 | desc "Run Simplecov" | |
19 | 23 | task :coverage => [:set_coverage_env, :test] |
20 | 24 | |
21 | 25 | gemspec = File.expand_path("../html2haml.gemspec", __FILE__) |
1 | 1 | require File.expand_path('../lib/html2haml/version', __FILE__) |
2 | 2 | |
3 | 3 | Gem::Specification.new do |gem| |
4 | gem.authors = ["Norman Clarke"] | |
5 | gem.email = ["norman@njclarke.com"] | |
4 | gem.authors = ["Norman Clarke", "Stefan Natchev"] | |
5 | gem.email = ["norman@njclarke.com", "stefan.natchev@gmail.com"] | |
6 | 6 | gem.description = %q{Converts HTML into Haml} |
7 | 7 | gem.summary = %q{Converts HTML into Haml} |
8 | 8 | gem.homepage = "http://haml.info" |
14 | 14 | gem.require_paths = ["lib"] |
15 | 15 | gem.version = Html2haml::VERSION |
16 | 16 | |
17 | gem.add_dependency 'hpricot', '~> 0.8.6' | |
17 | gem.required_ruby_version = '>= 1.9.2' | |
18 | ||
19 | gem.add_dependency 'nokogiri', '~> 1.6.0' | |
18 | 20 | gem.add_dependency 'erubis', '~> 2.7.0' |
19 | gem.add_dependency 'ruby_parser', '~> 3.1.1' | |
20 | gem.add_dependency 'haml', '>= 4.0.0.rc.1' | |
21 | gem.add_dependency 'ruby_parser', '~> 3.5' | |
22 | gem.add_dependency 'haml', '~> 4.0.0' | |
21 | 23 | gem.add_development_dependency 'simplecov', '~> 0.7.1' |
22 | 24 | gem.add_development_dependency 'minitest', '~> 4.4.0' |
23 | 25 | gem.add_development_dependency 'rake' |
2 | 2 | require 'rbconfig' |
3 | 3 | |
4 | 4 | module Html2haml |
5 | # This module handles the various Haml executables (`haml` and `haml-convert`). | |
5 | # This module handles the Html2haml executable. | |
6 | 6 | module Exec |
7 | # An abstract class that encapsulates the executable code for all three executables. | |
7 | # An abstract class that encapsulates the executable code for the Html2haml executable. | |
8 | # It's split into a base class and a subclass for historic reasons: this previously | |
9 | # was used by all the executables in the Haml project, before Html2haml was moved | |
10 | # into its own gem. | |
8 | 11 | class Generic |
9 | 12 | # @param args [Array<String>] The command-line arguments |
10 | 13 | def initialize(args) |
207 | 210 | @options[:no_erb] = true |
208 | 211 | end |
209 | 212 | |
210 | opts.on('-r', '--rhtml', 'Deprecated; same as --erb.') do | |
211 | @module_opts[:erb] = true | |
212 | end | |
213 | ||
214 | opts.on('--no-rhtml', "Deprecated; same as --no-erb.") do | |
215 | @options[:no_erb] = true | |
216 | end | |
217 | ||
218 | opts.on('-x', '--xhtml', 'Parse the input using the more strict XHTML parser.') do | |
219 | @module_opts[:xhtml] = true | |
220 | end | |
221 | ||
222 | 213 | opts.on("--html-attributes", "Use HTML style attributes instead of Ruby hash style.") do |
223 | 214 | @module_opts[:html_style_attributes] = true |
224 | 215 | end |
225 | 216 | |
226 | unless RUBY_VERSION < "1.9" | |
227 | opts.on('-E ex[:in]', 'Specify the default external and internal character encodings.') do |encoding| | |
228 | external, internal = encoding.split(':') | |
229 | Encoding.default_external = external if external && !external.empty? | |
230 | Encoding.default_internal = internal if internal && !internal.empty? | |
231 | end | |
217 | opts.on("--ruby19-attributes", "Use Ruby 1.9-style attributes when possible.") do | |
218 | @module_opts[:ruby19_style_attributes] = true | |
219 | end | |
220 | ||
221 | opts.on('-E ex[:in]', 'Specify the default external and internal character encodings.') do |encoding| | |
222 | external, internal = encoding.split(':') | |
223 | Encoding.default_external = external if external && !external.empty? | |
224 | Encoding.default_internal = internal if internal && !internal.empty? | |
232 | 225 | end |
233 | 226 | |
234 | 227 | super |
247 | 240 | @module_opts[:erb] ||= input.respond_to?(:path) && input.path =~ /\.(rhtml|erb)$/ |
248 | 241 | @module_opts[:erb] &&= @options[:no_erb] != false |
249 | 242 | |
250 | output.write(::Haml::HTML.new(input, @module_opts).render) | |
243 | output.write(HTML.new(input, @module_opts).render) | |
251 | 244 | rescue ::Haml::Error => e |
252 | 245 | raise "#{e.is_a?(::Haml::SyntaxError) ? "Syntax error" : "Error"} on line " + |
253 | 246 | "#{get_line e}: #{e.message}" |
1 | 1 | require 'erubis' |
2 | 2 | require 'ruby_parser' |
3 | 3 | |
4 | module Haml | |
4 | module Html2haml | |
5 | 5 | class HTML |
6 | 6 | # A class for converting ERB code into a format that's easier |
7 | # for the {Haml::HTML} Hpricot-based parser to understand. | |
7 | # for the {Html2haml::HTML} Nokogiri-based parser to understand. | |
8 | 8 | # |
9 | 9 | # Uses [Erubis](http://www.kuwata-lab.com/erubis)'s extensible parsing powers |
10 | 10 | # to parse the ERB in a reliable way, |
12 | 12 | # to figure out whether a given chunk of Ruby code starts a block or not. |
13 | 13 | # |
14 | 14 | # The ERB tags are converted to HTML tags in the following way. |
15 | # `<% ... %>` is converted into `<haml:silent> ... </haml:silent>`. | |
16 | # `<%= ... %>` is converted into `<haml:loud> ... </haml:loud>`. | |
15 | # `<% ... %>` is converted into `<haml_silent> ... </haml_silent>`. | |
16 | # `<%= ... %>` is converted into `<haml_loud> ... </haml_loud>`. | |
17 | # `<%== ... %>` is converted into `<haml_loud raw=raw> ... </haml_loud>`. | |
17 | 18 | # Finally, if either of these opens a Ruby block, |
18 | # `<haml:block> ... </haml:block>` will wrap the entire contents of the block - | |
19 | # `<haml_block> ... </haml_block>` will wrap the entire contents of the block - | |
19 | 20 | # that is, everything that should be indented beneath the previous silent or loud tag. |
20 | 21 | class ERB < Erubis::Basic::Engine |
21 | # Compiles an ERB template into a HTML document containing `haml:` tags. | |
22 | # Compiles an ERB template into a HTML document containing `haml_*` tags. | |
22 | 23 | # |
23 | 24 | # @param template [String] The ERB template |
24 | 25 | # @return [String] The output document |
25 | # @see Haml::HTML::ERB | |
26 | # @see Html2haml::HTML::ERB | |
26 | 27 | def self.compile(template) |
27 | 28 | new(template).src |
28 | end | |
29 | ||
30 | # `html2haml` doesn't support HTML-escaped expressions. | |
31 | def escaped_expr(code) | |
32 | raise Haml::Error.new("html2haml doesn't support escaped expressions.") | |
33 | 29 | end |
34 | 30 | |
35 | 31 | # The ERB-to-Hamlized-HTML conversion has no preamble. |
47 | 43 | end |
48 | 44 | |
49 | 45 | # Concatenates a silent Ruby statement onto the source buffer. |
50 | # This uses the `<haml:silent>` tag, | |
51 | # and may close and/or open a Ruby block with the `<haml:block>` tag. | |
46 | # This uses the `<haml_silent>` tag, | |
47 | # and may close and/or open a Ruby block with the `<haml_block>` tag. | |
52 | 48 | # |
53 | 49 | # In particular, a block is closed if this statement is some form of `end`, |
54 | 50 | # opened if it's a block opener like `do`, `if`, or `begin`, |
58 | 54 | # @param src [String] The source buffer |
59 | 55 | # @param code [String] The Ruby statement to add to the buffer |
60 | 56 | def add_stmt(src, code) |
61 | src << '</haml:block>' if block_closer?(code) || mid_block?(code) | |
62 | src << '<haml:silent>' << h(code) << '</haml:silent>' unless code.strip == "end" | |
63 | src << '<haml:block>' if block_opener?(code) || mid_block?(code) | |
57 | src << '</haml_block>' if block_closer?(code) || mid_block?(code) | |
58 | src << '<haml_silent>' << h(code) << '</haml_silent>' unless code.strip == "end" | |
59 | src << '<haml_block>' if block_opener?(code) || mid_block?(code) | |
64 | 60 | end |
65 | 61 | |
66 | 62 | # Concatenates a Ruby expression that's printed to the document |
72 | 68 | # @param src [String] The source buffer |
73 | 69 | # @param code [String] The Ruby expression to add to the buffer |
74 | 70 | def add_expr_literal(src, code) |
75 | src << '<haml:loud>' << h(code) << '</haml:loud>' | |
76 | src << '<haml:block>' if block_opener?(code) | |
71 | src << '<haml_loud>' << h(code) << '</haml_loud>' | |
72 | src << '<haml_block>' if block_opener?(code) | |
73 | end | |
74 | ||
75 | def add_expr_escaped(src, code) | |
76 | src << "<haml_loud raw=raw>" << h(code) << "</haml_loud>" | |
77 | 77 | end |
78 | 78 | |
79 | 79 | # `html2haml` doesn't support debugging expressions. |
108 | 108 | # @param code [String] Ruby code to check |
109 | 109 | # @return [Boolean] |
110 | 110 | def has_code?(code) |
111 | code != "\n" | |
111 | return false if code == "\n" | |
112 | return false if valid_ruby?(code) == nil | |
113 | true | |
112 | 114 | end |
113 | 115 | |
114 | 116 | # Checks if a string of Ruby code opens a block. |
0 | 0 | require 'cgi' |
1 | require 'hpricot' | |
1 | require 'nokogiri' | |
2 | 2 | require 'html2haml/html/erb' |
3 | 3 | |
4 | # Haml monkeypatches various Hpricot classes | |
4 | # Haml monkeypatches various Nokogiri classes | |
5 | 5 | # to add methods for conversion to Haml. |
6 | 6 | # @private |
7 | module Hpricot | |
8 | # @see Hpricot | |
9 | module Node | |
10 | # Whether this node has already been converted to Haml. | |
11 | # Only used for text nodes and elements. | |
12 | # | |
13 | # @return [Boolean] | |
14 | attr_accessor :converted_to_haml | |
15 | ||
16 | # Returns the Haml representation of the given node. | |
17 | # | |
18 | # @param tabs [Fixnum] The indentation level of the resulting Haml. | |
19 | # @option options (see Haml::HTML#initialize) | |
20 | def to_haml(tabs, options) | |
21 | return "" if converted_to_haml || to_s.strip.empty? | |
22 | text = uninterp(self.to_s) | |
23 | node = next_node | |
24 | while node.is_a?(::Hpricot::Elem) && node.name == "haml:loud" | |
25 | node.converted_to_haml = true | |
26 | text << '#{' << | |
27 | CGI.unescapeHTML(node.inner_text).gsub(/\n\s*/, ' ').strip << '}' | |
28 | ||
29 | if node.next_node.is_a?(::Hpricot::Text) | |
30 | node = node.next_node | |
31 | text << uninterp(node.to_s) | |
7 | module Nokogiri | |
8 | ||
9 | module XML | |
10 | # @see Nokogiri | |
11 | class 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 Html2haml::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 | ||
26 | #ending in a newline stops the inline nodes | |
27 | if text.end_with?("\n") | |
28 | parse_text_with_interpolation(text, tabs) | |
29 | else | |
30 | text << process_inline_nodes(next_sibling) | |
31 | parse_text_with_interpolation(text, tabs) | |
32 | end | |
33 | end | |
34 | ||
35 | private | |
36 | ||
37 | def erb_to_interpolation(text, options) | |
38 | return text unless options[:erb] | |
39 | text = CGI.escapeHTML(uninterp(text)) | |
40 | %w[<haml_loud> </haml_loud>].each {|str| text.gsub!(CGI.escapeHTML(str), str)} | |
41 | ::Nokogiri::XML.fragment(text).children.inject("") do |str, elem| | |
42 | if elem.is_a?(::Nokogiri::XML::Text) | |
43 | str + CGI.unescapeHTML(elem.to_s) | |
44 | else # <haml_loud> element | |
45 | str + '#{' + CGI.unescapeHTML(elem.inner_text.strip) + '}' | |
46 | end | |
47 | end | |
48 | end | |
49 | ||
50 | def tabulate(tabs) | |
51 | ' ' * tabs | |
52 | end | |
53 | ||
54 | def uninterp(text) | |
55 | text.gsub('#{', '\#{') #' | |
56 | end | |
57 | ||
58 | def attr_hash | |
59 | Hash[attributes.map {|k, v| [k.to_s, v.to_s]}] | |
60 | end | |
61 | ||
62 | def parse_text(text, tabs) | |
63 | parse_text_with_interpolation(uninterp(text), tabs) | |
64 | end | |
65 | ||
66 | def parse_text_with_interpolation(text, tabs) | |
67 | text.strip! | |
68 | return "" if text.empty? | |
69 | ||
70 | text.split("\n").map do |line| | |
71 | line.strip! | |
72 | "#{tabulate(tabs)}#{'\\' if Haml::Parser::SPECIAL_CHARACTERS.include?(line[0])}#{line}\n" | |
73 | end.join | |
74 | end | |
75 | ||
76 | def process_inline_nodes(node) | |
77 | text = "" | |
78 | while node.is_a?(::Nokogiri::XML::Element) && node.name == "haml_loud" | |
32 | 79 | node.converted_to_haml = true |
33 | end | |
34 | ||
35 | node = node.next_node | |
36 | end | |
37 | return parse_text_with_interpolation(text, tabs) | |
38 | end | |
39 | ||
40 | private | |
41 | ||
42 | def erb_to_interpolation(text, options) | |
43 | return text unless options[:erb] | |
44 | text = CGI.escapeHTML(uninterp(text)) | |
45 | %w[<haml:loud> </haml:loud>].each {|str| text.gsub!(CGI.escapeHTML(str), str)} | |
46 | ::Hpricot::XML(text).children.inject("") do |str, elem| | |
47 | if elem.is_a?(::Hpricot::Text) | |
48 | str + CGI.unescapeHTML(elem.to_s) | |
49 | else # <haml:loud> element | |
50 | str + '#{' + CGI.unescapeHTML(elem.innerText.strip) + '}' | |
51 | end | |
52 | end | |
53 | end | |
54 | ||
55 | def tabulate(tabs) | |
56 | ' ' * tabs | |
57 | end | |
58 | ||
59 | def uninterp(text) | |
60 | text.gsub('#{', '\#{') #' | |
61 | end | |
62 | ||
63 | def attr_hash | |
64 | attributes.to_hash | |
65 | end | |
66 | ||
67 | def parse_text(text, tabs) | |
68 | parse_text_with_interpolation(uninterp(text), tabs) | |
69 | end | |
70 | ||
71 | def parse_text_with_interpolation(text, tabs) | |
72 | text.strip! | |
73 | return "" if text.empty? | |
74 | ||
75 | text.split("\n").map do |line| | |
76 | line.strip! | |
77 | "#{tabulate(tabs)}#{'\\' if Haml::Parser::SPECIAL_CHARACTERS.include?(line[0])}#{line}\n" | |
78 | end.join | |
80 | text << '#{' << | |
81 | CGI.unescapeHTML(node.inner_text).gsub(/\n\s*/, ' ').strip << '}' | |
82 | ||
83 | if node.next_sibling.is_a?(::Nokogiri::XML::Text) | |
84 | node = node.next_sibling | |
85 | text << uninterp(node.to_s) | |
86 | node.converted_to_haml = true | |
87 | end | |
88 | ||
89 | node = node.next_sibling | |
90 | end | |
91 | text | |
92 | end | |
79 | 93 | end |
80 | 94 | end |
81 | 95 | end |
82 | 96 | |
83 | 97 | # @private |
84 | HAML_TAGS = %w[haml:block haml:loud haml:silent] | |
85 | ||
86 | HAML_TAGS.each do |t| | |
87 | Hpricot::ElementContent[t] = {} | |
88 | Hpricot::ElementContent.keys.each do |key| | |
89 | Hpricot::ElementContent[t][key.hash] = true | |
90 | end | |
91 | end | |
92 | ||
93 | Hpricot::ElementContent.keys.each do |k| | |
94 | HAML_TAGS.each do |el| | |
95 | val = Hpricot::ElementContent[k] | |
96 | val[el.hash] = true if val.is_a?(Hash) | |
97 | end | |
98 | end | |
99 | ||
100 | module Haml | |
98 | HAML_TAGS = %w[haml_block haml_loud haml_silent] | |
99 | # | |
100 | # HAML_TAGS.each do |t| | |
101 | # Nokogiri::XML::ElementContent[t] = {} | |
102 | # Nokogiri::XML::ElementContent.keys.each do |key| | |
103 | # Nokogiri::XML::ElementContent[t][key.hash] = true | |
104 | # end | |
105 | # end | |
106 | # | |
107 | # Nokogiri::XML::ElementContent.keys.each do |k| | |
108 | # HAML_TAGS.each do |el| | |
109 | # val = Nokogiri::XML::ElementContent[k] | |
110 | # val[el.hash] = true if val.is_a?(Hash) | |
111 | # end | |
112 | # end | |
113 | ||
114 | module Html2haml | |
101 | 115 | # Converts HTML documents into Haml templates. |
102 | # Depends on [Hpricot](http://github.com/whymirror/hpricot) for HTML parsing. | |
103 | # If ERB conversion is being used, also depends on | |
116 | # Depends on [Nokogiri](http://nokogiri.org/) for HTML parsing. | |
117 | # If ERB conversion is being used, also depends on | |
104 | 118 | # [Erubis](http://www.kuwata-lab.com/erubis) to parse the ERB |
105 | 119 | # and [ruby_parser](http://parsetree.rubyforge.org/) to parse the Ruby code. |
106 | 120 | # |
107 | 121 | # Example usage: |
108 | 122 | # |
109 | # Haml::HTML.new("<a href='http://google.com'>Blat</a>").render | |
123 | # HTML.new("<a href='http://google.com'>Blat</a>").render | |
110 | 124 | # #=> "%a{:href => 'http://google.com'} Blat" |
111 | 125 | class HTML |
112 | # @param template [String, Hpricot::Node] The HTML template to convert | |
126 | # @param template [String, Nokogiri::Node] The HTML template to convert | |
113 | 127 | # @option options :erb [Boolean] (false) Whether or not to parse |
114 | 128 | # ERB's `<%= %>` and `<% %>` into Haml's `=` and `-` |
115 | 129 | # @option options :xhtml [Boolean] (false) Whether or not to parse |
117 | 131 | def initialize(template, options = {}) |
118 | 132 | @options = options |
119 | 133 | |
120 | if template.is_a? Hpricot::Node | |
134 | if template.is_a? Nokogiri::XML::Node | |
121 | 135 | @template = template |
122 | 136 | else |
123 | 137 | if template.is_a? IO |
131 | 145 | template = ERB.compile(template) |
132 | 146 | end |
133 | 147 | |
134 | method = @options[:xhtml] ? Hpricot.method(:XML) : method(:Hpricot) | |
135 | @template = method.call(template.gsub('&', '&')) | |
136 | end | |
148 | @template = detect_proper_parser(template) | |
149 | end | |
150 | end | |
151 | ||
152 | def detect_proper_parser(template) | |
153 | if template =~ /^\s*<!DOCTYPE|<html/i | |
154 | return Nokogiri.HTML(template) | |
155 | end | |
156 | ||
157 | if template =~ /^\s*<head|<body/i | |
158 | return Nokogiri.HTML(template).at('/html').children | |
159 | end | |
160 | ||
161 | parsed = Nokogiri::HTML::DocumentFragment.parse(template) | |
162 | ||
163 | #detect missplaced head or body tag | |
164 | #XML_HTML_STRUCURE_ERROR : 800 | |
165 | if parsed.errors.any? {|e| e.code == 800 } | |
166 | return Nokogiri.HTML(template).at('/html').children | |
167 | end | |
168 | ||
169 | #in order to support CDATA in HTML (which is invalid) try using the XML parser | |
170 | # we can detect this when libxml returns error code XML_ERR_NAME_REQUIRED : 68 | |
171 | if parsed.errors.any? {|e| e.code == 68 } || template =~ /CDATA/ | |
172 | return Nokogiri::XML.fragment(template) | |
173 | end | |
174 | ||
175 | parsed | |
137 | 176 | end |
138 | 177 | |
139 | 178 | # Processes the document and returns the result as a string |
145 | 184 | |
146 | 185 | TEXT_REGEXP = /^(\s*).*$/ |
147 | 186 | |
148 | # @see Hpricot | |
187 | ||
188 | # @see Nokogiri | |
149 | 189 | # @private |
150 | class ::Hpricot::Doc | |
151 | # @see Haml::HTML::Node#to_haml | |
190 | class ::Nokogiri::XML::Document | |
191 | # @see Html2haml::HTML::Node#to_haml | |
152 | 192 | def to_haml(tabs, options) |
153 | 193 | (children || []).inject('') {|s, c| s << c.to_haml(0, options)} |
154 | 194 | end |
155 | 195 | end |
156 | 196 | |
157 | # @see Hpricot | |
197 | class ::Nokogiri::XML::DocumentFragment | |
198 | # @see Html2haml::HTML::Node#to_haml | |
199 | def to_haml(tabs, options) | |
200 | (children || []).inject('') {|s, c| s << c.to_haml(0, options)} | |
201 | end | |
202 | end | |
203 | ||
204 | class ::Nokogiri::XML::NodeSet | |
205 | # @see Html2haml::HTML::Node#to_haml | |
206 | def to_haml(tabs, options) | |
207 | self.inject('') {|s, c| s << c.to_haml(tabs, options)} | |
208 | end | |
209 | end | |
210 | ||
211 | # @see Nokogiri | |
158 | 212 | # @private |
159 | class ::Hpricot::XMLDecl | |
160 | # @see Haml::HTML::Node#to_haml | |
213 | class ::Nokogiri::XML::ProcessingInstruction | |
214 | # @see Html2haml::HTML::Node#to_haml | |
161 | 215 | def to_haml(tabs, options) |
162 | 216 | "#{tabulate(tabs)}!!! XML\n" |
163 | 217 | end |
164 | 218 | end |
165 | 219 | |
166 | # @see Hpricot | |
220 | # @see Nokogiri | |
167 | 221 | # @private |
168 | class ::Hpricot::CData | |
169 | # @see Haml::HTML::Node#to_haml | |
222 | class ::Nokogiri::XML::CDATA | |
223 | # @see Html2haml::HTML::Node#to_haml | |
170 | 224 | def to_haml(tabs, options) |
171 | 225 | content = parse_text_with_interpolation( |
172 | 226 | erb_to_interpolation(self.content, options), tabs + 1) |
173 | 227 | "#{tabulate(tabs)}:cdata\n#{content}" |
174 | 228 | end |
175 | end | |
176 | ||
177 | # @see Hpricot | |
229 | ||
230 | # removes the start and stop markers for cdata | |
231 | def content_without_cdata_tokens | |
232 | content. | |
233 | gsub(/^\s*<!\[CDATA\[\n/,""). | |
234 | gsub(/^\s*\]\]>\n/, "") | |
235 | end | |
236 | end | |
237 | ||
238 | # @see Nokogiri | |
178 | 239 | # @private |
179 | class ::Hpricot::DocType | |
180 | # @see Haml::HTML::Node#to_haml | |
181 | def to_haml(tabs, options) | |
182 | attrs = public_id.nil? ? ["", "", ""] : | |
183 | public_id.scan(/DTD\s+([^\s]+)\s*([^\s]*)\s*([^\s]*)\s*\/\//)[0] | |
240 | class ::Nokogiri::XML::DTD | |
241 | # @see Html2haml::HTML::Node#to_haml | |
242 | def to_haml(tabs, options) | |
243 | attrs = external_id.nil? ? ["", "", ""] : | |
244 | external_id.scan(/DTD\s+([^\s]+)\s*([^\s]*)\s*([^\s]*)\s*\/\//)[0] | |
184 | 245 | raise Haml::SyntaxError.new("Invalid doctype") if attrs == nil |
185 | 246 | |
186 | 247 | type, version, strictness = attrs.map { |a| a.downcase } |
204 | 265 | end |
205 | 266 | end |
206 | 267 | |
207 | # @see Hpricot | |
268 | # @see Nokogiri | |
208 | 269 | # @private |
209 | class ::Hpricot::Comment | |
210 | # @see Haml::HTML::Node#to_haml | |
270 | class ::Nokogiri::XML::Comment | |
271 | # @see Html2haml::HTML::Node#to_haml | |
211 | 272 | def to_haml(tabs, options) |
212 | 273 | content = self.content |
213 | 274 | if content =~ /\A(\[[^\]]+\])>(.*)<!\[endif\]\z/m |
223 | 284 | end |
224 | 285 | end |
225 | 286 | |
226 | # @see Hpricot | |
287 | # @see Nokogiri | |
227 | 288 | # @private |
228 | class ::Hpricot::Elem | |
229 | # @see Haml::HTML::Node#to_haml | |
289 | class ::Nokogiri::XML::Element | |
290 | # @see Html2haml::HTML::Node#to_haml | |
230 | 291 | def to_haml(tabs, options) |
231 | 292 | return "" if converted_to_haml |
293 | ||
232 | 294 | if name == "script" && |
233 | (attr_hash['type'].nil? || attr_hash['type'] == "text/javascript") && | |
295 | (attr_hash['type'].nil? || attr_hash['type'].to_s == "text/javascript") && | |
234 | 296 | (attr_hash.keys - ['type']).empty? |
235 | 297 | return to_haml_filter(:javascript, tabs, options) |
236 | 298 | elsif name == "style" && |
237 | (attr_hash['type'].nil? || attr_hash['type'] == "text/css") && | |
299 | (attr_hash['type'].nil? || attr_hash['type'].to_s == "text/css") && | |
238 | 300 | (attr_hash.keys - ['type']).empty? |
239 | 301 | return to_haml_filter(:css, tabs, options) |
240 | 302 | end |
241 | 303 | |
242 | 304 | output = tabulate(tabs) |
243 | if options[:erb] && name[0...5] == 'haml:' | |
244 | case name[5..-1] | |
245 | when "loud" | |
305 | if options[:erb] && HAML_TAGS.include?(name) | |
306 | case name | |
307 | when "haml_loud" | |
246 | 308 | lines = CGI.unescapeHTML(inner_text).split("\n"). |
247 | 309 | map {|s| s.rstrip}.reject {|s| s.strip.empty?} |
248 | lines.first.gsub!(/^[ \t]*/, "= ") | |
310 | ||
311 | if attribute("raw") | |
312 | lines.first.gsub!(/^[ \t]*/, "!= ") | |
313 | else | |
314 | lines.first.gsub!(/^[ \t]*/, "= ") | |
315 | end | |
249 | 316 | |
250 | 317 | if lines.size > 1 # Multiline script block |
251 | 318 | # Normalize the indentation so that the last line is the base |
260 | 327 | length = lines.map {|s| s.size}.max + 1 |
261 | 328 | lines.map! {|s| "%#{-length}s|" % s} |
262 | 329 | |
263 | if next_sibling && next_sibling.is_a?(Hpricot::Elem) && next_sibling.name == "haml:loud" && | |
330 | if next_sibling && next_sibling.is_a?(Nokogiri::XML::Element) && next_sibling.name == "haml_loud" && | |
264 | 331 | next_sibling.inner_text.split("\n").reject {|s| s.strip.empty?}.size > 1 |
265 | 332 | lines << "-#" |
266 | 333 | end |
267 | 334 | end |
268 | 335 | return lines.map {|s| output + s + "\n"}.join |
269 | when "silent" | |
336 | when "haml_silent" | |
270 | 337 | return CGI.unescapeHTML(inner_text).split("\n").map do |line| |
271 | 338 | next "" if line.strip.empty? |
272 | 339 | "#{output}- #{line.strip}\n" |
273 | 340 | end.join |
274 | when "block" | |
341 | when "haml_block" | |
275 | 342 | return render_children("", tabs, options) |
276 | 343 | end |
277 | 344 | end |
285 | 352 | output << "= succeed #{self.next.content.slice!(/\A[^\s]+/).dump} do\n" |
286 | 353 | tabs += 1 |
287 | 354 | output << tabulate(tabs) |
288 | end | |
289 | end | |
290 | ||
291 | output << "%#{name}" unless name == 'div' && | |
355 | #empty the text node since it was inserted into the block | |
356 | self.next.content = "" | |
357 | end | |
358 | end | |
359 | ||
360 | output << "%#{name}" unless name.to_s == 'div' && | |
292 | 361 | (static_id?(options) || |
293 | 362 | static_classname?(options) && |
294 | attr_hash['class'].split(' ').any?(&method(:haml_css_attr?))) | |
363 | attr_hash['class'].to_s.split(' ').any?(&method(:haml_css_attr?))) | |
295 | 364 | |
296 | 365 | if attr_hash |
366 | ||
297 | 367 | if static_id?(options) |
298 | output << "##{attr_hash['id']}" | |
368 | output << "##{attr_hash['id'].to_s}" | |
299 | 369 | remove_attribute('id') |
300 | 370 | end |
301 | 371 | if static_classname?(options) |
302 | leftover = attr_hash['class'].split(' ').reject do |c| | |
372 | leftover = attr_hash['class'].to_s.split(' ').reject do |c| | |
303 | 373 | next unless haml_css_attr?(c) |
304 | 374 | output << ".#{c}" |
305 | 375 | end |
310 | 380 | end |
311 | 381 | |
312 | 382 | output << ">" if nuke_outer_whitespace |
313 | output << "/" if empty? && !etag | |
383 | output << "/" if to_xhtml.end_with?("/>") | |
314 | 384 | |
315 | 385 | if children && children.size == 1 |
316 | 386 | child = children.first |
317 | if child.is_a?(::Hpricot::Text) | |
387 | if child.is_a?(::Nokogiri::XML::Text) | |
318 | 388 | if !child.to_s.include?("\n") |
319 | 389 | text = child.to_haml(tabs + 1, options) |
320 | 390 | return output + " " + text.lstrip.gsub(/^\\/, '') unless text.chomp.include?("\n") || text.empty? |
321 | 391 | return output + "\n" + text |
322 | 392 | elsif ["pre", "textarea"].include?(name) || |
323 | (name == "code" && parent.is_a?(::Hpricot::Elem) && parent.name == "pre") | |
393 | (name == "code" && parent.is_a?(::Nokogiri::XML::Element) && parent.name == "pre") | |
324 | 394 | return output + "\n#{tabulate(tabs + 1)}:preserve\n" + |
325 | innerText.gsub(/^/, tabulate(tabs + 2)) | |
395 | inner_text.gsub(/^/, tabulate(tabs + 2)) | |
326 | 396 | end |
327 | elsif child.is_a?(::Hpricot::Elem) && child.name == "haml:loud" | |
397 | elsif child.is_a?(::Nokogiri::XML::Element) && child.name == "haml_loud" | |
328 | 398 | return output + child.to_haml(tabs + 1, options).lstrip |
329 | 399 | end |
330 | 400 | end |
341 | 411 | end |
342 | 412 | |
343 | 413 | def dynamic_attributes |
344 | @dynamic_attributes ||= begin | |
345 | Hash[attr_hash.map do |name, value| | |
346 | next if value.empty? | |
347 | full_match = nil | |
348 | ruby_value = value.gsub(%r{<haml:loud>\s*(.+?)\s*</haml:loud>}) do | |
349 | full_match = $`.empty? && $'.empty? | |
350 | CGI.unescapeHTML(full_match ? $1: "\#{#{$1}}") | |
414 | #reject any attrs without <haml> | |
415 | @dynamic_attributes = attr_hash.select {|name, value| value =~ %r{<haml.*</haml} } | |
416 | @dynamic_attributes.each do |name, value| | |
417 | fragment = Nokogiri::XML.fragment(CGI.unescapeHTML(value)) | |
418 | ||
419 | # unwrap interpolation if we can: | |
420 | if fragment.children.size == 1 && fragment.child.name == 'haml_loud' | |
421 | if attribute_value_can_be_bare_ruby?(fragment.text) | |
422 | value.replace(fragment.text.strip) | |
423 | next | |
351 | 424 | end |
352 | next if ruby_value == value | |
353 | [name, full_match ? ruby_value : %("#{ruby_value}")] | |
354 | end] | |
355 | end | |
356 | end | |
425 | end | |
426 | ||
427 | # turn erb into interpolations | |
428 | fragment.css('haml_loud').each do |el| | |
429 | inner_text = el.text.strip | |
430 | next if inner_text == "" | |
431 | el.replace('#{' + inner_text + '}') | |
432 | end | |
433 | ||
434 | # put the resulting text in a string | |
435 | value.replace('"' + fragment.text.strip + '"') | |
436 | end | |
437 | end | |
438 | ||
439 | def attribute_value_can_be_bare_ruby?(value) | |
440 | begin | |
441 | ruby = RubyParser.new.parse(value) | |
442 | rescue Racc::ParseError, RubyParser::SyntaxError | |
443 | return false | |
444 | end | |
445 | ||
446 | return false if ruby.nil? | |
447 | return true if ruby.sexp_type == :str #regular string | |
448 | return true if ruby.sexp_type == :dstr #string with interpolation | |
449 | return true if ruby.sexp_type == :lit #symbol | |
450 | return true if ruby.sexp_type == :call && ruby.mass == 1 #local var or method with no params | |
451 | ||
452 | false | |
453 | end | |
454 | ||
357 | 455 | |
358 | 456 | def to_haml_filter(filter, tabs, options) |
359 | 457 | content = |
360 | if children.first.is_a?(::Hpricot::CData) | |
361 | children.first.content | |
458 | if children.first && children.first.cdata? | |
459 | decode_entities(children.first.content_without_cdata_tokens) | |
362 | 460 | else |
363 | CGI.unescapeHTML(self.innerText) | |
461 | decode_entities(self.inner_text) | |
364 | 462 | end |
365 | 463 | |
366 | 464 | content = erb_to_interpolation(content, options) |
381 | 479 | "#{tabulate(tabs)}:#{filter}\n#{content}" |
382 | 480 | end |
383 | 481 | |
482 | # TODO: this method is utterly awful, find a better way to decode HTML entities. | |
483 | def decode_entities(str) | |
484 | str.gsub(/&[\S]+;/) do |entity| | |
485 | begin | |
486 | [Nokogiri::HTML::NamedCharacters[entity[1..-2]]].pack("C") | |
487 | rescue TypeError | |
488 | entity | |
489 | end | |
490 | end | |
491 | end | |
492 | ||
384 | 493 | def static_attribute?(name, options) |
385 | 494 | attr_hash[name] && !dynamic_attribute?(name, options) |
386 | 495 | end |
405 | 514 | # that's prettier than that produced by Hash#inspect |
406 | 515 | def haml_attributes(options) |
407 | 516 | attrs = attr_hash.sort.map do |name, value| |
408 | haml_attribute_pair(name, value, options) | |
517 | haml_attribute_pair(name, value.to_s, options) | |
409 | 518 | end |
410 | 519 | if options[:html_style_attributes] |
411 | 520 | "(#{attrs.join(' ')})" |
417 | 526 | # Returns the string representation of a single attribute key value pair |
418 | 527 | def haml_attribute_pair(name, value, options) |
419 | 528 | value = dynamic_attribute?(name, options) ? dynamic_attributes[name] : value.inspect |
529 | ||
420 | 530 | if options[:html_style_attributes] |
421 | "#{name}=#{value}" | |
422 | else | |
423 | name = name.index(/\W/) ? name.inspect : ":#{name}" | |
424 | "#{name} => #{value}" | |
425 | end | |
531 | return "#{name}=#{value}" | |
532 | end | |
533 | ||
534 | if name.index(/\W/) | |
535 | return "#{name.inspect} => #{value}" | |
536 | end | |
537 | ||
538 | if options[:ruby19_style_attributes] | |
539 | return "#{name}: #{value}" | |
540 | end | |
541 | ||
542 | ":#{name} => #{value}" | |
426 | 543 | end |
427 | 544 | end |
428 | 545 | end |
0 | --- !ruby/object:Gem::Specification | |
0 | --- !ruby/object:Gem::Specification | |
1 | 1 | name: html2haml |
2 | version: !ruby/object:Gem::Version | |
3 | prerelease: false | |
4 | segments: | |
5 | - 1 | |
6 | - 0 | |
7 | - 1 | |
8 | version: 1.0.1 | |
2 | version: !ruby/object:Gem::Version | |
3 | version: 2.0.0 | |
9 | 4 | platform: ruby |
10 | authors: | |
5 | authors: | |
11 | 6 | - Norman Clarke |
7 | - Stefan Natchev | |
12 | 8 | autorequire: |
13 | 9 | bindir: bin |
14 | 10 | cert_chain: [] |
15 | ||
16 | date: 2013-02-16 00:00:00 -03:00 | |
17 | default_executable: | |
18 | dependencies: | |
19 | - !ruby/object:Gem::Dependency | |
20 | name: hpricot | |
11 | date: 2015-01-19 00:00:00.000000000 Z | |
12 | dependencies: | |
13 | - !ruby/object:Gem::Dependency | |
14 | name: nokogiri | |
15 | requirement: !ruby/object:Gem::Requirement | |
16 | requirements: | |
17 | - - "~>" | |
18 | - !ruby/object:Gem::Version | |
19 | version: 1.6.0 | |
20 | type: :runtime | |
21 | 21 | prerelease: false |
22 | requirement: &id001 !ruby/object:Gem::Requirement | |
23 | requirements: | |
24 | - - ~> | |
25 | - !ruby/object:Gem::Version | |
26 | segments: | |
27 | - 0 | |
28 | - 8 | |
29 | - 6 | |
30 | version: 0.8.6 | |
31 | type: :runtime | |
32 | version_requirements: *id001 | |
33 | - !ruby/object:Gem::Dependency | |
22 | version_requirements: !ruby/object:Gem::Requirement | |
23 | requirements: | |
24 | - - "~>" | |
25 | - !ruby/object:Gem::Version | |
26 | version: 1.6.0 | |
27 | - !ruby/object:Gem::Dependency | |
34 | 28 | name: erubis |
35 | prerelease: false | |
36 | requirement: &id002 !ruby/object:Gem::Requirement | |
37 | requirements: | |
38 | - - ~> | |
39 | - !ruby/object:Gem::Version | |
40 | segments: | |
41 | - 2 | |
42 | - 7 | |
43 | - 0 | |
29 | requirement: !ruby/object:Gem::Requirement | |
30 | requirements: | |
31 | - - "~>" | |
32 | - !ruby/object:Gem::Version | |
44 | 33 | version: 2.7.0 |
45 | 34 | type: :runtime |
46 | version_requirements: *id002 | |
47 | - !ruby/object:Gem::Dependency | |
35 | prerelease: false | |
36 | version_requirements: !ruby/object:Gem::Requirement | |
37 | requirements: | |
38 | - - "~>" | |
39 | - !ruby/object:Gem::Version | |
40 | version: 2.7.0 | |
41 | - !ruby/object:Gem::Dependency | |
48 | 42 | name: ruby_parser |
43 | requirement: !ruby/object:Gem::Requirement | |
44 | requirements: | |
45 | - - "~>" | |
46 | - !ruby/object:Gem::Version | |
47 | version: '3.5' | |
48 | type: :runtime | |
49 | 49 | prerelease: false |
50 | requirement: &id003 !ruby/object:Gem::Requirement | |
51 | requirements: | |
52 | - - ~> | |
53 | - !ruby/object:Gem::Version | |
54 | segments: | |
55 | - 3 | |
56 | - 1 | |
57 | - 1 | |
58 | version: 3.1.1 | |
50 | version_requirements: !ruby/object:Gem::Requirement | |
51 | requirements: | |
52 | - - "~>" | |
53 | - !ruby/object:Gem::Version | |
54 | version: '3.5' | |
55 | - !ruby/object:Gem::Dependency | |
56 | name: haml | |
57 | requirement: !ruby/object:Gem::Requirement | |
58 | requirements: | |
59 | - - "~>" | |
60 | - !ruby/object:Gem::Version | |
61 | version: 4.0.0 | |
59 | 62 | type: :runtime |
60 | version_requirements: *id003 | |
61 | - !ruby/object:Gem::Dependency | |
62 | name: haml | |
63 | 63 | prerelease: false |
64 | requirement: &id004 !ruby/object:Gem::Requirement | |
65 | requirements: | |
66 | - - ">=" | |
67 | - !ruby/object:Gem::Version | |
68 | segments: | |
69 | - 4 | |
70 | - 0 | |
71 | - 0 | |
72 | - rc | |
73 | - 1 | |
74 | version: 4.0.0.rc.1 | |
75 | type: :runtime | |
76 | version_requirements: *id004 | |
77 | - !ruby/object:Gem::Dependency | |
64 | version_requirements: !ruby/object:Gem::Requirement | |
65 | requirements: | |
66 | - - "~>" | |
67 | - !ruby/object:Gem::Version | |
68 | version: 4.0.0 | |
69 | - !ruby/object:Gem::Dependency | |
78 | 70 | name: simplecov |
79 | prerelease: false | |
80 | requirement: &id005 !ruby/object:Gem::Requirement | |
81 | requirements: | |
82 | - - ~> | |
83 | - !ruby/object:Gem::Version | |
84 | segments: | |
85 | - 0 | |
86 | - 7 | |
87 | - 1 | |
71 | requirement: !ruby/object:Gem::Requirement | |
72 | requirements: | |
73 | - - "~>" | |
74 | - !ruby/object:Gem::Version | |
88 | 75 | version: 0.7.1 |
89 | 76 | type: :development |
90 | version_requirements: *id005 | |
91 | - !ruby/object:Gem::Dependency | |
77 | prerelease: false | |
78 | version_requirements: !ruby/object:Gem::Requirement | |
79 | requirements: | |
80 | - - "~>" | |
81 | - !ruby/object:Gem::Version | |
82 | version: 0.7.1 | |
83 | - !ruby/object:Gem::Dependency | |
92 | 84 | name: minitest |
93 | prerelease: false | |
94 | requirement: &id006 !ruby/object:Gem::Requirement | |
95 | requirements: | |
96 | - - ~> | |
97 | - !ruby/object:Gem::Version | |
98 | segments: | |
99 | - 4 | |
100 | - 4 | |
101 | - 0 | |
85 | requirement: !ruby/object:Gem::Requirement | |
86 | requirements: | |
87 | - - "~>" | |
88 | - !ruby/object:Gem::Version | |
102 | 89 | version: 4.4.0 |
103 | 90 | type: :development |
104 | version_requirements: *id006 | |
105 | - !ruby/object:Gem::Dependency | |
91 | prerelease: false | |
92 | version_requirements: !ruby/object:Gem::Requirement | |
93 | requirements: | |
94 | - - "~>" | |
95 | - !ruby/object:Gem::Version | |
96 | version: 4.4.0 | |
97 | - !ruby/object:Gem::Dependency | |
106 | 98 | name: rake |
99 | requirement: !ruby/object:Gem::Requirement | |
100 | requirements: | |
101 | - - ">=" | |
102 | - !ruby/object:Gem::Version | |
103 | version: '0' | |
104 | type: :development | |
107 | 105 | prerelease: false |
108 | requirement: &id007 !ruby/object:Gem::Requirement | |
109 | requirements: | |
106 | version_requirements: !ruby/object:Gem::Requirement | |
107 | requirements: | |
110 | 108 | - - ">=" |
111 | - !ruby/object:Gem::Version | |
112 | segments: | |
113 | - 0 | |
114 | version: "0" | |
115 | type: :development | |
116 | version_requirements: *id007 | |
109 | - !ruby/object:Gem::Version | |
110 | version: '0' | |
117 | 111 | description: Converts HTML into Haml |
118 | email: | |
112 | email: | |
119 | 113 | - norman@njclarke.com |
120 | executables: | |
114 | - stefan.natchev@gmail.com | |
115 | executables: | |
121 | 116 | - html2haml |
122 | 117 | extensions: [] |
123 | ||
124 | 118 | extra_rdoc_files: [] |
125 | ||
126 | files: | |
127 | - .gitignore | |
128 | - .travis.yml | |
119 | files: | |
120 | - ".gitignore" | |
121 | - ".travis.yml" | |
122 | - ".yardopts" | |
129 | 123 | - Changelog.markdown |
130 | 124 | - Gemfile |
131 | 125 | - MIT-LICENSE |
140 | 134 | - lib/html2haml/version.rb |
141 | 135 | - test/erb_test.rb |
142 | 136 | - test/html2haml_test.rb |
137 | - test/jruby/erb_test.rb | |
138 | - test/jruby/html2haml_test.rb | |
143 | 139 | - test/test_helper.rb |
144 | has_rdoc: true | |
145 | 140 | homepage: http://haml.info |
146 | 141 | licenses: [] |
147 | ||
142 | metadata: {} | |
148 | 143 | post_install_message: |
149 | 144 | rdoc_options: [] |
150 | ||
151 | require_paths: | |
145 | require_paths: | |
152 | 146 | - lib |
153 | required_ruby_version: !ruby/object:Gem::Requirement | |
154 | requirements: | |
147 | required_ruby_version: !ruby/object:Gem::Requirement | |
148 | requirements: | |
155 | 149 | - - ">=" |
156 | - !ruby/object:Gem::Version | |
157 | segments: | |
158 | - 0 | |
159 | version: "0" | |
160 | required_rubygems_version: !ruby/object:Gem::Requirement | |
161 | requirements: | |
150 | - !ruby/object:Gem::Version | |
151 | version: 1.9.2 | |
152 | required_rubygems_version: !ruby/object:Gem::Requirement | |
153 | requirements: | |
162 | 154 | - - ">=" |
163 | - !ruby/object:Gem::Version | |
164 | segments: | |
165 | - 0 | |
166 | version: "0" | |
155 | - !ruby/object:Gem::Version | |
156 | version: '0' | |
167 | 157 | requirements: [] |
168 | ||
169 | 158 | rubyforge_project: |
170 | rubygems_version: 1.3.6 | |
159 | rubygems_version: 2.4.5 | |
171 | 160 | signing_key: |
172 | specification_version: 3 | |
161 | specification_version: 4 | |
173 | 162 | summary: Converts HTML into Haml |
174 | test_files: | |
163 | test_files: | |
175 | 164 | - test/erb_test.rb |
176 | 165 | - test/html2haml_test.rb |
166 | - test/jruby/erb_test.rb | |
167 | - test/jruby/html2haml_test.rb | |
177 | 168 | - test/test_helper.rb |
9 | 9 | |
10 | 10 | def test_inline_erb |
11 | 11 | assert_equal("%p= foo", render_erb("<p><%= foo %></p>")) |
12 | assert_equal(<<HAML.rstrip, render_erb(<<HTML)) | |
13 | %p | |
14 | = foo | |
15 | HAML | |
16 | <p><%= foo %> | |
17 | </p> | |
18 | HTML | |
12 | 19 | end |
13 | 20 | |
14 | 21 | def test_non_inline_erb |
26 | 33 | HAML |
27 | 34 | <p> |
28 | 35 | <%= foo %></p> |
29 | HTML | |
30 | assert_equal(<<HAML.rstrip, render_erb(<<HTML)) | |
31 | %p | |
32 | = foo | |
33 | HAML | |
34 | <p><%= foo %> | |
35 | </p> | |
36 | 36 | HTML |
37 | 37 | end |
38 | 38 | |
115 | 115 | def test_erb_in_html_escaped_attribute |
116 | 116 | assert_equal '%div{:class => "foo"} Bang!', |
117 | 117 | render_erb('<div class="<%= "foo" %>">Bang!</div>') |
118 | end | |
119 | ||
120 | def test_erb_in_html_escaped_attribute_with_symbol | |
121 | assert_equal '%div{:class => :foo} Bang!', | |
122 | render_erb('<div class="<%= :foo %>">Bang!</div>') | |
123 | end | |
124 | ||
125 | def test_empty_erb_in_attribute | |
126 | assert_equal '%div{:class => ""}', | |
127 | render_erb('<div class="<%= %>"></div>') | |
118 | 128 | end |
119 | 129 | |
120 | 130 | def test_erb_in_attribute_to_multiple_interpolations |
198 | 208 | = foo + | |
199 | 209 | bar.baz.bang + | |
200 | 210 | baz | |
201 | -# | |
202 | 211 | = foo.bar do | |
203 | 212 | bang | |
204 | 213 | end | |
459 | 468 | def test_can_parse_ruby_19_hashes_as_arguments |
460 | 469 | erb = "<%= foobar 'foo', {bar: 'baz'} %>" |
461 | 470 | begin |
462 | Haml::HTML::ERB.new(erb) | |
471 | Html2haml::HTML::ERB.new(erb) | |
463 | 472 | rescue |
464 | 473 | flunk "should not raise an error" |
465 | 474 | end |
466 | 475 | end |
467 | 476 | |
468 | 477 | def test_should_wrap_in_silent |
469 | assert_equal(<<HTML.rstrip, Haml::HTML::ERB.new(<<ERB).src) | |
470 | <haml:silent> some_variable_or_function \n</haml:silent> | |
478 | assert_equal(<<HTML.rstrip, Html2haml::HTML::ERB.new(<<ERB).src) | |
479 | <haml_silent> some_variable_or_function \n</haml_silent> | |
471 | 480 | HTML |
472 | 481 | <% some_variable_or_function %> |
473 | 482 | ERB |
475 | 484 | |
476 | 485 | #comment content is removed by erubis |
477 | 486 | def test_should_wrap_process_comments_as_empty_lines |
478 | assert_equal(<<HTML.rstrip, Haml::HTML::ERB.new(<<ERB).src) | |
479 | <haml:silent>\n</haml:silent> | |
487 | assert_equal(<<HTML.rstrip, Html2haml::HTML::ERB.new(<<ERB).src) | |
488 | <haml_silent>\n</haml_silent> | |
480 | 489 | HTML |
481 | 490 | <%# some_variable_or_function %> |
482 | 491 | ERB |
483 | 492 | end |
484 | 493 | |
494 | def test_conditional_structure_in_argument | |
495 | assert_equal(<<HAML.rstrip, render_erb(<<HTML)) | |
496 | %span{:class => "\#{"active" if condition}"} | |
497 | HAML | |
498 | <span class="<%= "active" if condition %>"></span> | |
499 | HTML | |
500 | end | |
501 | ||
502 | def test_method_call_without_brackets_in_argument | |
503 | assert_equal(<<HAML.rstrip, render_erb(<<HTML)) | |
504 | %span{:class => "\#{call_me maybe}"} | |
505 | HAML | |
506 | <span class="<%= call_me maybe %>"></span> | |
507 | HTML | |
508 | end | |
509 | ||
510 | def test_multiline_erb_comment | |
511 | assert_equal(<<HAML.rstrip, render_erb(<<ERB)) | |
512 | - # comment | |
513 | %p hi | |
514 | HAML | |
515 | <% | |
516 | # comment | |
517 | -%> | |
518 | <p>hi</p> | |
519 | ERB | |
520 | end | |
521 | ||
522 | ## | |
523 | # <%== %> is supposed to be equal to <%= raw %> | |
524 | def test_erb_with_double_equals | |
525 | assert_equal(<<HAML.rstrip, render_erb(<<ERB)) | |
526 | != link_to "https://github.com/haml/html2haml/issues/44" | |
527 | HAML | |
528 | <%== link_to "https://github.com/haml/html2haml/issues/44" %> | |
529 | ERB | |
530 | end | |
531 | ||
532 | #https://github.com/haml/html2haml/issues/43 | |
533 | def test_escaped_ruby_call_when_preceeded_by_text | |
534 | assert_equal(<<HAML.rstrip, render_erb(<<ERB)) | |
535 | random text | |
536 | = form_tag(url: sessions_path) do | |
537 | = submit_tag "cdcd" | |
538 | HAML | |
539 | random text | |
540 | <%= form_tag(url: sessions_path) do %> | |
541 | <%= submit_tag "cdcd" %> | |
542 | <% end %> | |
543 | ERB | |
544 | end | |
485 | 545 | end |
0 | # encoding: UTF-8 | |
0 | 1 | require 'test_helper' |
1 | 2 | |
2 | 3 | class Html2HamlTest < MiniTest::Unit::TestCase |
44 | 45 | render('<meta http-equiv="Content-Type" content="text/html" />', :html_style_attributes => true)) |
45 | 46 | end |
46 | 47 | |
48 | def test_should_have_ruby_19_hash_style_attributes | |
49 | assert_equal('%input{name: "login", type: "text"}/', | |
50 | render('<input type="text" name="login" />', :ruby19_style_attributes => true)) | |
51 | assert_equal('%meta{content: "text/html", "http-equiv" => "Content-Type"}/', | |
52 | render('<meta http-equiv="Content-Type" content="text/html" />', :ruby19_style_attributes => true)) | |
53 | end | |
54 | ||
55 | def test_should_have_attributes_without_values | |
56 | assert_equal('%input{:disabled => "disabled"}/', render('<input disabled>')) | |
57 | end | |
58 | ||
47 | 59 | def test_class_with_dot_and_hash |
48 | 60 | assert_equal('%div{:class => "foo.bar"}', render("<div class='foo.bar'></div>")) |
49 | 61 | assert_equal('%div{:class => "foo#bar"}', render("<div class='foo#bar'></div>")) |
80 | 92 | end |
81 | 93 | |
82 | 94 | def test_self_closing_tag |
83 | assert_equal("%foo/", render("<foo />")) | |
95 | assert_equal("%img/", render("<img />")) | |
84 | 96 | end |
85 | 97 | |
86 | 98 | def test_inline_text |
135 | 147 | end |
136 | 148 | |
137 | 149 | def test_script_tag |
150 | assert_equal(<<HAML.rstrip, render(<<HTML)) | |
151 | :javascript | |
152 | function foo() { | |
153 | return "12" & "13"; | |
154 | } | |
155 | HAML | |
156 | <script type="text/javascript"> | |
157 | function foo() { | |
158 | return "12" & "13"; | |
159 | } | |
160 | </script> | |
161 | HTML | |
162 | end | |
163 | ||
164 | def test_script_tag_with_html_escaped_javascript | |
138 | 165 | assert_equal(<<HAML.rstrip, render(<<HTML)) |
139 | 166 | :javascript |
140 | 167 | function foo() { |
256 | 283 | HTML |
257 | 284 | end |
258 | 285 | |
286 | def test_style_to_css_filter_with_no_content | |
287 | assert_equal(<<HAML.rstrip, render(<<HTML)) | |
288 | :css | |
289 | HAML | |
290 | <style type="text/css"> </style> | |
291 | HTML | |
292 | assert_equal(<<HAML.rstrip, render(<<HTML)) | |
293 | :css | |
294 | HAML | |
295 | <style type="text/css"></style> | |
296 | HTML | |
297 | end | |
298 | ||
259 | 299 | def test_filter_with_inconsistent_indentation |
260 | 300 | assert_equal(<<HAML.rstrip, render(<<HTML)) |
261 | 301 | :css |
332 | 372 | |
333 | 373 | # Encodings |
334 | 374 | |
335 | unless RUBY_VERSION < "1.9" | |
336 | def test_encoding_error | |
337 | render("foo\nbar\nb\xFEaz".force_encoding("utf-8")) | |
338 | assert(false, "Expected exception") | |
339 | rescue Haml::Error => e | |
340 | assert_equal(3, e.line) | |
341 | assert_equal('Invalid UTF-8 character "\xFE"', e.message) | |
342 | end | |
343 | ||
344 | def test_ascii_incompatible_encoding_error | |
345 | template = "foo\nbar\nb_z".encode("utf-16le") | |
346 | template[9] = "\xFE".force_encoding("utf-16le") | |
347 | render(template) | |
348 | assert(false, "Expected exception") | |
349 | rescue Haml::Error => e | |
350 | assert_equal(3, e.line) | |
351 | assert_equal('Invalid UTF-16LE character "\xFE"', e.message) | |
352 | end | |
375 | def test_encoding_error | |
376 | render("foo\nbar\nb\xFEaz".force_encoding("utf-8")) | |
377 | assert(false, "Expected exception") | |
378 | rescue Haml::Error => e | |
379 | assert_equal(3, e.line) | |
380 | assert_match(/Invalid UTF-8 character/, e.message) | |
381 | end | |
382 | ||
383 | def test_ascii_incompatible_encoding_error | |
384 | template = "foo\nbar\nb_z".encode("utf-16le") | |
385 | template[9] = "\xFE".force_encoding("utf-16le") | |
386 | render(template) | |
387 | assert(false, "Expected exception") | |
388 | rescue Haml::Error => e | |
389 | assert_equal(3, e.line) | |
390 | assert_match(/Invalid UTF-16LE character/, e.message) | |
353 | 391 | end |
354 | 392 | |
355 | 393 | # Regression Tests |
360 | 398 | "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> |
361 | 399 | HTML |
362 | 400 | end |
401 | ||
402 | def test_html_document_without_doctype | |
403 | assert_equal(<<HAML.rstrip, render(<<HTML)) | |
404 | !!! | |
405 | %html | |
406 | %head | |
407 | %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/ | |
408 | %title Hello | |
409 | %body | |
410 | %p Hello | |
411 | HAML | |
412 | <html> | |
413 | <head> | |
414 | <title>Hello</title> | |
415 | </head> | |
416 | <body> | |
417 | <p>Hello</p> | |
418 | </body> | |
419 | </html> | |
420 | HTML | |
421 | end | |
422 | ||
363 | 423 | end |
0 | #reopen classes that need to be modified for JRuby specific behavior | |
1 | class ErbTest | |
2 | def test_two_multiline_erb_loud_scripts | |
3 | assert_equal(<<HAML.rstrip, render_erb(<<ERB)) | |
4 | .blah | |
5 | = foo + | | |
6 | bar.baz.bang + | | |
7 | baz | | |
8 | = foo.bar do | | |
9 | bang | | |
10 | end | | |
11 | %p foo | |
12 | HAML | |
13 | <div class="blah"> | |
14 | <%= | |
15 | foo + | |
16 | bar.baz.bang + | |
17 | baz | |
18 | %> | |
19 | <%= foo.bar do | |
20 | bang | |
21 | end %> | |
22 | <p>foo</p> | |
23 | </div> | |
24 | ERB | |
25 | end | |
26 | ||
27 | end |
0 | class Html2HamlTest | |
1 | ||
2 | def test_doctype | |
3 | empty_body = "\n%html\n %head\n %body" | |
4 | assert_equal '!!!' + empty_body, render("<!DOCTYPE html>") | |
5 | assert_equal '!!! 1.1' + empty_body, render('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">') | |
6 | assert_equal '!!! Strict' + empty_body, render('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">') | |
7 | assert_equal '!!! Frameset' + empty_body, render('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">') | |
8 | assert_equal '!!! Mobile 1.2' + empty_body, render('<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">') | |
9 | assert_equal '!!! Basic 1.1' + empty_body, render('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">') | |
10 | assert_equal '!!!' + empty_body, render('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">') | |
11 | assert_equal '!!! Strict' + empty_body, render('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">') | |
12 | assert_equal '!!! Frameset' + empty_body, render('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">') | |
13 | assert_equal '!!!' + empty_body, render('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">') | |
14 | end | |
15 | ||
16 | def test_xhtml_strict_doctype | |
17 | assert_equal(<<HAML.rstrip, render(<<HTML)) | |
18 | !!! Strict | |
19 | %html | |
20 | %head | |
21 | %body | |
22 | HAML | |
23 | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" | |
24 | "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> | |
25 | HTML | |
26 | end | |
27 | ||
28 | def test_html_document_without_doctype | |
29 | assert_equal(<<HAML.rstrip, render(<<HTML)) | |
30 | %html | |
31 | %head | |
32 | %title Hello | |
33 | %body | |
34 | %p Hello | |
35 | HAML | |
36 | <html> | |
37 | <head> | |
38 | <title>Hello</title> | |
39 | </head> | |
40 | <body> | |
41 | <p>Hello</p> | |
42 | </body> | |
43 | </html> | |
44 | HTML | |
45 | end | |
46 | ||
47 | def test_should_have_attributes_without_values | |
48 | assert_equal('%input{:disabled => ""}/', render('<input disabled>')) | |
49 | end | |
50 | ||
51 | def test_style_to_css_filter_with_following_content | |
52 | assert_equal(<<HAML.rstrip, render(<<HTML)) | |
53 | %head | |
54 | :css | |
55 | foo { | |
56 | bar: baz; | |
57 | } | |
58 | %body | |
59 | Hello | |
60 | HAML | |
61 | <head> | |
62 | <style type="text/css"> | |
63 | foo { | |
64 | bar: baz; | |
65 | } | |
66 | </style> | |
67 | </head> | |
68 | <body>Hello</body> | |
69 | HTML | |
70 | end | |
71 | end |