jarrett-quarto 1.3.0 → 1.4.0
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/README +6 -1
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/lib/quarto/element_wrapper.rb +9 -2
- data/lib/quarto/generator.rb +2 -2
- data/lib/quarto/html_transformer.rb +103 -0
- data/lib/quarto/transformation_helper.rb +29 -0
- data/lib/quarto/transformer.rb +143 -0
- data/lib/quarto.rb +3 -0
- data/quarto.gemspec +12 -7
- data/spec/element_wrapper_spec.rb +6 -1
- data/spec/init_project_spec.rb +1 -1
- data/spec/matchers/file_matchers.rb +17 -15
- data/spec/matchers/rexml_matchers.rb +65 -0
- data/spec/sample_project/models/company.rb +1 -1
- data/spec/transformation_helper_spec.rb +34 -0
- data/spec/transformer_spec.rb +148 -0
- metadata +9 -4
- data/test.rb +0 -20
data/README
CHANGED
@@ -1,9 +1,14 @@
|
|
1
1
|
Quarto is a Ruby framework for generating collections of documents from XML. Potential applications
|
2
|
-
include web sites and e-books. It's built on top of ERB and REXML.
|
2
|
+
include web sites and e-books. It's built on top of ERB and REXML. It works as a standalone
|
3
|
+
application and inside Rails.
|
3
4
|
|
4
5
|
Quarto was built with HTML output in mind, but there's nothing to prevent you from outputting
|
5
6
|
to any other format. You could even output to an interpolated scripting language like PHP.
|
6
7
|
|
8
|
+
As of the inception of Quarto, there is no cross-platform XSLT gem. So, Quarto comes with its
|
9
|
+
own alternative to XSLT in the form of the Tranformer class and the TransformationHelper
|
10
|
+
module.
|
11
|
+
|
7
12
|
Why Quarto?
|
8
13
|
===========
|
9
14
|
|
data/Rakefile
CHANGED
@@ -10,7 +10,7 @@ begin
|
|
10
10
|
gemspec.authors = ['Jarrett Colby']
|
11
11
|
gemspec.files = FileList['[A-Z]*', '{bin,lib,test}/**/*']
|
12
12
|
gemspec.executables = 'quarto'
|
13
|
-
gemspec.add_dependency('activesupport', '
|
13
|
+
gemspec.add_dependency('activesupport', '~> 2.3.2')
|
14
14
|
end
|
15
15
|
rescue LoadError
|
16
16
|
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.4.0
|
@@ -124,8 +124,15 @@ module Quarto
|
|
124
124
|
end
|
125
125
|
if element_attrs = self.class.read_inheritable_attribute(:element_attrs)
|
126
126
|
element_attrs.each do |el_name|
|
127
|
-
|
128
|
-
|
127
|
+
if child_el = @element.elements[el_name.to_s]
|
128
|
+
if child_el.elements.empty?
|
129
|
+
@attributes[el_name.to_sym] = typecast_text(child_el.text)
|
130
|
+
else
|
131
|
+
@attributes[el_name.to_sym] = child_el
|
132
|
+
end
|
133
|
+
else
|
134
|
+
@attributes[el_name.to_sym] = nil
|
135
|
+
end
|
129
136
|
end
|
130
137
|
end
|
131
138
|
if text_attr = self.class.read_inheritable_attribute(:text_attr)
|
data/lib/quarto/generator.rb
CHANGED
@@ -41,7 +41,7 @@ module Quarto
|
|
41
41
|
# This class responds to all the directives that are available for use within
|
42
42
|
# a generate.rb file.
|
43
43
|
class Generator
|
44
|
-
include UrlHelper
|
44
|
+
include Quarto::UrlHelper
|
45
45
|
|
46
46
|
# Sets the name of the default layout file in the layouts directory. If
|
47
47
|
# default_layout isn't specified, the default layout is the first file matching <tt>default.*</tt>.
|
@@ -130,7 +130,7 @@ module Quarto
|
|
130
130
|
def render_to_s(template, locals, options = {})
|
131
131
|
require urls_file_path
|
132
132
|
|
133
|
-
mixins = [Quarto::ProjectUrls, Quarto::RailsHelper, Quarto::UrlHelper]
|
133
|
+
mixins = [Quarto::ProjectUrls, Quarto::RailsHelper, Quarto::UrlHelper, Quarto::TransformationHelper]
|
134
134
|
|
135
135
|
page_template_path = "#{@project_path}/pages/#{template}"
|
136
136
|
page_template = ERB.new(File.read(page_template_path))
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module Quarto
|
2
|
+
class HtmlTransformer < Transformer
|
3
|
+
HTML_ELEMENTS = %w(
|
4
|
+
a
|
5
|
+
abbr
|
6
|
+
acronym
|
7
|
+
address
|
8
|
+
applet
|
9
|
+
area
|
10
|
+
b
|
11
|
+
base
|
12
|
+
basefont
|
13
|
+
bdo
|
14
|
+
big
|
15
|
+
blockquote
|
16
|
+
body
|
17
|
+
br
|
18
|
+
button
|
19
|
+
caption
|
20
|
+
center
|
21
|
+
cite
|
22
|
+
code
|
23
|
+
col
|
24
|
+
colgroup
|
25
|
+
dd
|
26
|
+
del
|
27
|
+
dfn
|
28
|
+
dir
|
29
|
+
div
|
30
|
+
dl
|
31
|
+
dt
|
32
|
+
em
|
33
|
+
fieldset
|
34
|
+
font
|
35
|
+
form
|
36
|
+
frame
|
37
|
+
frameset
|
38
|
+
h1
|
39
|
+
h2
|
40
|
+
h3
|
41
|
+
h4
|
42
|
+
h5
|
43
|
+
h6
|
44
|
+
head
|
45
|
+
hr
|
46
|
+
html
|
47
|
+
i
|
48
|
+
iframe
|
49
|
+
img
|
50
|
+
input
|
51
|
+
ins
|
52
|
+
isindex
|
53
|
+
kbd
|
54
|
+
label
|
55
|
+
legend
|
56
|
+
li
|
57
|
+
link
|
58
|
+
map
|
59
|
+
menu
|
60
|
+
meta
|
61
|
+
noframes
|
62
|
+
noscript
|
63
|
+
object
|
64
|
+
ol
|
65
|
+
optgroup
|
66
|
+
option
|
67
|
+
p
|
68
|
+
param
|
69
|
+
pre
|
70
|
+
q
|
71
|
+
s
|
72
|
+
samp
|
73
|
+
script
|
74
|
+
select
|
75
|
+
small
|
76
|
+
span
|
77
|
+
strike
|
78
|
+
strong
|
79
|
+
style
|
80
|
+
sub
|
81
|
+
sup
|
82
|
+
table
|
83
|
+
tbody
|
84
|
+
td
|
85
|
+
textarea
|
86
|
+
tfoot
|
87
|
+
th
|
88
|
+
thead
|
89
|
+
title
|
90
|
+
tr
|
91
|
+
tt
|
92
|
+
u
|
93
|
+
ul
|
94
|
+
var
|
95
|
+
)
|
96
|
+
|
97
|
+
protected
|
98
|
+
|
99
|
+
def literal?(element)
|
100
|
+
HTML_ELEMENTS.include?(element.name)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Quarto
|
2
|
+
module TransformationHelper
|
3
|
+
# Tranforms the children of +element+ using
|
4
|
+
# an instance of +transformer_class+ and returns
|
5
|
+
# a string representation of the result.
|
6
|
+
#
|
7
|
+
# +transformer_class+ must be a subclass of
|
8
|
+
# Quarto::Transformer or <tt>nil</tt>. If it's
|
9
|
+
# <tt>nil</tt> (which is the default), the default
|
10
|
+
# subclass will be used. The default subclass is set
|
11
|
+
# in <tt>generate.rb</tt> as follows:
|
12
|
+
#
|
13
|
+
# config(:default_transformer_class, YourSubclass)
|
14
|
+
#
|
15
|
+
# If you do not specify a default class,
|
16
|
+
# <tt>HtmlTransformer</tt> will be used.
|
17
|
+
def transform_xml(element, transformer_class = Quarto::HtmlTransformer)
|
18
|
+
raise ArgumentError, "Expected REXML::Element but got #{element.inspect}" unless element.is_a?(REXML::Element)
|
19
|
+
class_error = "Expected subclass of Tranformer but got #{transformer_class.inspect}"
|
20
|
+
raise ArgumentError, class_error unless transformer_class.is_a?(Class)
|
21
|
+
klass = transformer_class
|
22
|
+
while klass != Quarto::Transformer and klass != Object
|
23
|
+
klass = klass.superclass
|
24
|
+
end
|
25
|
+
raise ArgumentError, class_error unless Quarto::Transformer == klass
|
26
|
+
transformer_class.new.transform(element)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
module Quarto
|
2
|
+
# This abstract base class is a substitute for XSLT. Its
|
3
|
+
# <tt>transform</tt> method takes a single
|
4
|
+
# <tt>REXML::Element</tt> and applies rules defined in
|
5
|
+
# the subclass.
|
6
|
+
#
|
7
|
+
# To define those rules, you subclass Transformer and
|
8
|
+
# write methods to handle each element type. For example:
|
9
|
+
#
|
10
|
+
# class MyTranformer < Quarto::Transformer
|
11
|
+
# # This method will handle all <book> elements
|
12
|
+
# def transform_book(book_element)
|
13
|
+
# # Return whatever string you like
|
14
|
+
# # raise_on_unrecognized_element is provided
|
15
|
+
# # so that you can pass it to recursive_transform
|
16
|
+
# # if necessary.
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
class Transformer
|
20
|
+
# Macro to define the <tt>literal?</tt> method. Accepts
|
21
|
+
# one or more element names.
|
22
|
+
#
|
23
|
+
# Example:
|
24
|
+
#
|
25
|
+
# class MyTransformer < Quarto::Transformer
|
26
|
+
# literals 'div', 'p', 'a'
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# The above is equivalent to:
|
30
|
+
#
|
31
|
+
# class MyTransformer < Quarto::Transformer
|
32
|
+
# def literal?(element)
|
33
|
+
# ['div', 'p', 'a'].include?(element.name)
|
34
|
+
# end
|
35
|
+
# end
|
36
|
+
def self.literals(*args)
|
37
|
+
class_eval(%Q(
|
38
|
+
def literal?(element)
|
39
|
+
[#{args.collect { |e| "'#{e}'" }.join(',')}].include?(element.name)
|
40
|
+
end
|
41
|
+
))
|
42
|
+
end
|
43
|
+
|
44
|
+
# Recursively applies the transformation rules
|
45
|
+
# you've defined to +element+ and its children,
|
46
|
+
# returning the results as a string. Depending
|
47
|
+
# on the rules you've set up, the result may
|
48
|
+
# be XML or something else altogether.
|
49
|
+
#
|
50
|
+
# +element+ must be a <tt>REXML::Element</tt>.
|
51
|
+
#
|
52
|
+
# By default, unrecognized elements (and all their
|
53
|
+
# descendants) will be ommited from the result tree.
|
54
|
+
#
|
55
|
+
# However, you can cause these unrecognized elements to
|
56
|
+
# raise an exception by setting +raise_on_unrecognized_element+
|
57
|
+
# to <tt>true</tt>.
|
58
|
+
def transform(element, raise_on_unrecognized_element = false)
|
59
|
+
raise ArgumentError, "Expected REXML::Element but got #{element.inspect}" unless element.is_a?(REXML::Element)
|
60
|
+
if element.is_a?(REXML::Document)
|
61
|
+
recursive_transform(element.root, raise_on_unrecognized_element)
|
62
|
+
else
|
63
|
+
recursive_transform(element, raise_on_unrecognized_element)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
protected
|
68
|
+
|
69
|
+
def content_tag(tag_name, *args) # :nodoc:
|
70
|
+
if args.last.is_a?(Hash)
|
71
|
+
attributes = args.pop
|
72
|
+
else
|
73
|
+
attributes = {}
|
74
|
+
end
|
75
|
+
if args.empty?
|
76
|
+
if block_given?
|
77
|
+
contents = yield
|
78
|
+
else
|
79
|
+
contents = nil
|
80
|
+
end
|
81
|
+
else
|
82
|
+
contents = args[0]
|
83
|
+
end
|
84
|
+
output = "<#{tag_name}"
|
85
|
+
attributes.each do |attr, value|
|
86
|
+
output << " #{attr}=\"#{value}\""
|
87
|
+
end
|
88
|
+
if contents.nil? or contents.empty?
|
89
|
+
output << '/>'
|
90
|
+
else
|
91
|
+
output << ">#{contents}</#{tag_name}>"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# This method is meant to be overriden in subclasses. The
|
96
|
+
# default implementation always returns false.
|
97
|
+
#
|
98
|
+
# When <tt>tranform</tt> is called, each descendant element
|
99
|
+
# is passed to <tt>literal?</tt>. If it returns true,
|
100
|
+
# the descdant is added to the result tree with the same
|
101
|
+
# tag. If not, the <tt>Transformer</tt> will look for a
|
102
|
+
# custom tranform method for that element. If none is found,
|
103
|
+
# what happens next depends on the value of
|
104
|
+
# <tt>raise_on_unrecognized_element</tt> in <tt>transform</tt>.
|
105
|
+
# If it's true, an exception will be raised. Otherwise, the
|
106
|
+
# element and its descendant will be ommitted from the result
|
107
|
+
# tree.
|
108
|
+
#
|
109
|
+
# If <tt>literal?</tt> returns true for an element,
|
110
|
+
# its descendant elements will not be added to the tree
|
111
|
+
# verbatim, but will instead be subjected to the same
|
112
|
+
# transformation process as everything else.
|
113
|
+
#
|
114
|
+
# See <tt>HtmlTransformer</tt> for an example implementation.
|
115
|
+
def literal?(element)
|
116
|
+
false
|
117
|
+
end
|
118
|
+
|
119
|
+
# Recursively transform the +element+ and all its children,
|
120
|
+
# returning a string. Custom transform methods often call
|
121
|
+
# this method.
|
122
|
+
def recursive_transform(element, raise_on_unrecognized_element)
|
123
|
+
if element.is_a?(REXML::Element)
|
124
|
+
if respond_to?("transform_#{element.name}")
|
125
|
+
send("transform_#{element.name}", element, raise_on_unrecognized_element)
|
126
|
+
elsif literal?(element)
|
127
|
+
contents = element.children.inject('') do |result, child|
|
128
|
+
result + recursive_transform(child, raise_on_unrecognized_element)
|
129
|
+
end
|
130
|
+
content_tag(element.name, contents, element.attributes)
|
131
|
+
elsif raise_on_unrecognized_element
|
132
|
+
raise UnrecognizedElementError, "Unrecognized element: #{element.name}"
|
133
|
+
else
|
134
|
+
''
|
135
|
+
end
|
136
|
+
else
|
137
|
+
element.to_s
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
class UnrecognizedElementError < RuntimeError; end
|
143
|
+
end
|
data/lib/quarto.rb
CHANGED
@@ -15,6 +15,9 @@ require 'quarto/element_wrapper'
|
|
15
15
|
require 'quarto/children'
|
16
16
|
require 'quarto/rendering'
|
17
17
|
require 'quarto/generator'
|
18
|
+
require 'quarto/transformer'
|
19
|
+
require 'quarto/html_transformer'
|
20
|
+
require 'quarto/transformation_helper'
|
18
21
|
require 'quarto/init_project'
|
19
22
|
|
20
23
|
# Quarto is a Ruby framework for generating collections of documents from XML. Potential applications
|
data/quarto.gemspec
CHANGED
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{quarto}
|
5
|
-
s.version = "1.
|
5
|
+
s.version = "1.4.0"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["Jarrett Colby"]
|
9
|
-
s.date = %q{2009-06-
|
9
|
+
s.date = %q{2009-06-15}
|
10
10
|
s.default_executable = %q{quarto}
|
11
11
|
s.description = %q{Quarto is a Ruby framework for generating collections of documents from XML. It steps in where XSLT just won't cut it. Potential applications include web sites and e-books. It's built on top of ERB and REXML.}
|
12
12
|
s.email = %q{jarrett@uchicago.edu}
|
@@ -24,14 +24,16 @@ Gem::Specification.new do |s|
|
|
24
24
|
"lib/quarto/config.rb",
|
25
25
|
"lib/quarto/element_wrapper.rb",
|
26
26
|
"lib/quarto/generator.rb",
|
27
|
+
"lib/quarto/html_transformer.rb",
|
27
28
|
"lib/quarto/inheritable_attributes.rb",
|
28
29
|
"lib/quarto/init_project.rb",
|
29
30
|
"lib/quarto/rails_helper.rb",
|
30
31
|
"lib/quarto/rendering.rb",
|
32
|
+
"lib/quarto/transformation_helper.rb",
|
33
|
+
"lib/quarto/transformer.rb",
|
31
34
|
"lib/quarto/url_helper.rb",
|
32
35
|
"lib/quarto/xml_doc.rb",
|
33
|
-
"quarto.gemspec"
|
34
|
-
"test.rb"
|
36
|
+
"quarto.gemspec"
|
35
37
|
]
|
36
38
|
s.has_rdoc = true
|
37
39
|
s.homepage = %q{http://github.com/jarrett/quarto}
|
@@ -45,6 +47,7 @@ Gem::Specification.new do |s|
|
|
45
47
|
"spec/generator_spec.rb",
|
46
48
|
"spec/init_project_spec.rb",
|
47
49
|
"spec/matchers/file_matchers.rb",
|
50
|
+
"spec/matchers/rexml_matchers.rb",
|
48
51
|
"spec/rendering_spec.rb",
|
49
52
|
"spec/sample_models.rb",
|
50
53
|
"spec/sample_project/generate.rb",
|
@@ -55,6 +58,8 @@ Gem::Specification.new do |s|
|
|
55
58
|
"spec/sample_project/models/product.rb",
|
56
59
|
"spec/sample_project/urls.rb",
|
57
60
|
"spec/spec_helper.rb",
|
61
|
+
"spec/transformation_helper_spec.rb",
|
62
|
+
"spec/transformer_spec.rb",
|
58
63
|
"spec/url_helper_spec.rb"
|
59
64
|
]
|
60
65
|
|
@@ -63,11 +68,11 @@ Gem::Specification.new do |s|
|
|
63
68
|
s.specification_version = 2
|
64
69
|
|
65
70
|
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
66
|
-
s.add_runtime_dependency(%q<activesupport>, ["
|
71
|
+
s.add_runtime_dependency(%q<activesupport>, ["~> 2.3.2"])
|
67
72
|
else
|
68
|
-
s.add_dependency(%q<activesupport>, ["
|
73
|
+
s.add_dependency(%q<activesupport>, ["~> 2.3.2"])
|
69
74
|
end
|
70
75
|
else
|
71
|
-
s.add_dependency(%q<activesupport>, ["
|
76
|
+
s.add_dependency(%q<activesupport>, ["~> 2.3.2"])
|
72
77
|
end
|
73
78
|
end
|
@@ -7,7 +7,7 @@ describe Quarto::ElementWrapper::Base do
|
|
7
7
|
@xml = Quarto.xml_doc
|
8
8
|
end
|
9
9
|
|
10
|
-
context 'wrapping an element with attributes' do
|
10
|
+
context 'wrapping an element with attributes and child elements' do
|
11
11
|
before :each do
|
12
12
|
@element = @xml.elements['companies/company']
|
13
13
|
@company = Company.new(@element)
|
@@ -22,6 +22,11 @@ describe Quarto::ElementWrapper::Base do
|
|
22
22
|
@company.name.should == '37Signals'
|
23
23
|
end
|
24
24
|
|
25
|
+
it 'should return a REXML::Element if an elemement_attr contains markup' do
|
26
|
+
company = Company.find(:first, :xpath => "//company[name='Mega-lo-Mart']")
|
27
|
+
company.description.should be_a(REXML::Element)
|
28
|
+
end
|
29
|
+
|
25
30
|
it 'should define methods from XML attributes by default' do
|
26
31
|
@company.should respond_to(:reality)
|
27
32
|
@company.reality.should == 'real'
|
data/spec/init_project_spec.rb
CHANGED
@@ -1,20 +1,22 @@
|
|
1
|
-
module
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
1
|
+
module Quarto
|
2
|
+
module FileMatchers
|
3
|
+
class ExistOnDisk
|
4
|
+
def matches?(target)
|
5
|
+
@target = File.expand_path(target)
|
6
|
+
File.exists?(@target)
|
7
|
+
end
|
8
|
+
|
9
|
+
def failure_message
|
10
|
+
"Expected file #{@target} to exist"
|
11
|
+
end
|
12
|
+
|
13
|
+
def negative_failure_message
|
14
|
+
"Expected file #{@target} not to exist"
|
15
|
+
end
|
6
16
|
end
|
7
17
|
|
8
|
-
def
|
9
|
-
|
18
|
+
def exist_on_disk
|
19
|
+
ExistOnDisk.new
|
10
20
|
end
|
11
|
-
|
12
|
-
def negative_failure_message
|
13
|
-
"Expected file #{@target} not to exist"
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
def exist_on_disk
|
18
|
-
ExistOnDisk.new
|
19
21
|
end
|
20
22
|
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Quarto
|
2
|
+
module REXMLMatchers
|
3
|
+
class HaveElement
|
4
|
+
def initialize(element_name, options)
|
5
|
+
@element_name = element_name
|
6
|
+
@options = options
|
7
|
+
end
|
8
|
+
|
9
|
+
def matches?(target)
|
10
|
+
@target = target
|
11
|
+
!@target.find_first_recursive { |node| node_matches?(node) }.nil?
|
12
|
+
end
|
13
|
+
|
14
|
+
def failure_message
|
15
|
+
"Expected element '#{element_desc}' to be in:\n\n#{@target.to_s}"
|
16
|
+
end
|
17
|
+
|
18
|
+
def negative_failure_message
|
19
|
+
"Expected element '#{element_desc}' not to be in:\n\n#{@target.to_s}"
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
|
24
|
+
def element_desc
|
25
|
+
desc = @element_name
|
26
|
+
if @options.has_key?(:attributes)
|
27
|
+
desc << " with attributes: #{@options[:attributes].inspect}"
|
28
|
+
if @options.has_key?(:text)
|
29
|
+
desc << " and text: '#{@options[:text]}'"
|
30
|
+
end
|
31
|
+
else
|
32
|
+
if @options.has_key?(:text)
|
33
|
+
desc << " with text: '#{@options[:text]}'"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
desc
|
37
|
+
end
|
38
|
+
|
39
|
+
def node_matches?(node)
|
40
|
+
if node.name != @element_name
|
41
|
+
return false
|
42
|
+
end
|
43
|
+
if @options.has_key?(:attributes) and rexml_attrs_to_hash(node.attributes) != @options[:attributes]
|
44
|
+
return false
|
45
|
+
end
|
46
|
+
if @options.has_key?(:text) and node.text != @options[:text]
|
47
|
+
return false
|
48
|
+
end
|
49
|
+
true
|
50
|
+
end
|
51
|
+
|
52
|
+
def rexml_attrs_to_hash(rexml_attrs)
|
53
|
+
hash = {}
|
54
|
+
rexml_attrs.each do |attr_name, value|
|
55
|
+
hash[attr_name] = value
|
56
|
+
end
|
57
|
+
hash
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def have_element(element_name, options = {})
|
62
|
+
HaveElement.new(element_name, options)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe Quarto::TransformationHelper do
|
4
|
+
include Quarto::TransformationHelper
|
5
|
+
|
6
|
+
context '#tranform_xml' do
|
7
|
+
before :each do
|
8
|
+
@html = REXML::Document.new(%Q(
|
9
|
+
<div>
|
10
|
+
<p>Foo</p>
|
11
|
+
<p><a href="http://example.com">Bar</a></p>
|
12
|
+
</div>
|
13
|
+
))
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should return a string' do
|
17
|
+
transform_xml(@html).should be_a(String)
|
18
|
+
transform_xml(@html.root).should be_a(String)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should raise if passed anything other than a REXML::Element' do
|
22
|
+
lambda { transform_xml(nil) }.should raise_error(ArgumentError, 'Expected REXML::Element but got nil')
|
23
|
+
lambda { transform_xml('foo') }.should raise_error(ArgumentError, 'Expected REXML::Element but got "foo"')
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'without transformer_class given' do
|
27
|
+
it 'should use HtmlTransformer' do
|
28
|
+
t = Quarto::HtmlTransformer.new
|
29
|
+
Quarto::HtmlTransformer.should_receive(:new).and_return(t)
|
30
|
+
transform_xml(@html)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe Quarto::Transformer do
|
4
|
+
include Quarto::REXMLMatchers
|
5
|
+
|
6
|
+
context '#transform' do
|
7
|
+
before :each do
|
8
|
+
@xml = %q(
|
9
|
+
<doc>
|
10
|
+
<div>
|
11
|
+
<p>Foo</p>
|
12
|
+
<p>Bar</p>
|
13
|
+
|
14
|
+
<book>
|
15
|
+
<title>Foobar</title>
|
16
|
+
<author>Baz</author>
|
17
|
+
<cover_url>http://example.com/foobar.jpeg</cover_url>
|
18
|
+
</book>
|
19
|
+
</div>
|
20
|
+
</doc>
|
21
|
+
)
|
22
|
+
@doc = REXML::Document.new(@xml)
|
23
|
+
|
24
|
+
class TestTransformer < Quarto::Transformer
|
25
|
+
def literal?(element)
|
26
|
+
['doc', 'div', 'p'].include?(element.name)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
@t = TestTransformer.new
|
31
|
+
end
|
32
|
+
|
33
|
+
after :each do
|
34
|
+
Object.class_eval do
|
35
|
+
remove_const :TestTransformer
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should return a string' do
|
40
|
+
@t.transform(@doc).should be_a(String)
|
41
|
+
@t.transform(@doc.root).should be_a(String)
|
42
|
+
@t.transform(REXML::Document.new('')).should be_a(String)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'should raise if passed anything other than a REXML::Element' do
|
46
|
+
lambda { @t.transform(nil) }.should raise_error(ArgumentError, 'Expected REXML::Element but got nil')
|
47
|
+
lambda { @t.transform('foo') }.should raise_error(ArgumentError, 'Expected REXML::Element but got "foo"')
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'should not transform elements that lack custom transform methods and for which literal? returns true' do
|
51
|
+
result = REXML::Document.new(@t.transform(@doc))
|
52
|
+
result.should have_element('doc')
|
53
|
+
result.should have_element('div')
|
54
|
+
result.should have_element('p', :text => 'Foo')
|
55
|
+
result.should have_element('p', :text => 'Bar')
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'should ignore elements that lack custom transform methods and for which literal? returns false' do
|
59
|
+
result = REXML::Document.new(@t.transform(@doc))
|
60
|
+
['book', 'title', 'author' 'cover_url'].each do |el|
|
61
|
+
result.should_not have_element(el)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'should use custom transform methods' do
|
66
|
+
begin
|
67
|
+
class CustomizedTestTransformer < TestTransformer
|
68
|
+
def transform_book(book_element, raise_on_unrecognized_element)
|
69
|
+
%Q(
|
70
|
+
<div class="book">
|
71
|
+
<h1 class="title">#{book_element.elements['title'].text}</h1>
|
72
|
+
<h2 class="author">#{book_element.elements['author'].text}</h2>
|
73
|
+
<img src="#{book_element.elements['cover_url'].text}"/>
|
74
|
+
</div>
|
75
|
+
)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
t = CustomizedTestTransformer.new
|
80
|
+
result = REXML::Document.new(t.transform(@doc))
|
81
|
+
result.should have_element('div', :attributes => {'class' => 'book'})
|
82
|
+
result.should have_element('h1', :attributes => {'class' => 'title'}, :text => 'Foobar')
|
83
|
+
result.should have_element('h2', :attributes => {'class' => 'author'}, :text => 'Baz')
|
84
|
+
result.should have_element('img', :attributes => {'src' => 'http://example.com/foobar.jpeg'})
|
85
|
+
ensure
|
86
|
+
Object.class_eval do
|
87
|
+
remove_const :CustomizedTestTransformer
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'should give precedence to custom transform methods over literal?' do
|
93
|
+
begin
|
94
|
+
class CustomizedTestTransformer < TestTransformer
|
95
|
+
def transform_p(p_element, raise_on_unrecognized_element)
|
96
|
+
%Q(<div class="paragraph">#{p_element.text}</div>)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
t = CustomizedTestTransformer.new
|
101
|
+
result = REXML::Document.new(t.transform(@doc))
|
102
|
+
result.should have_element('div', :attributes => {'class' => 'paragraph'}, :text => 'Foo')
|
103
|
+
result.should have_element('div', :attributes => {'class' => 'paragraph'}, :text => 'Bar')
|
104
|
+
ensure
|
105
|
+
Object.class_eval do
|
106
|
+
remove_const :CustomizedTestTransformer
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
context '.literals' do
|
113
|
+
before :each do
|
114
|
+
class TestTransformer < Quarto::Transformer
|
115
|
+
literals :doc, :div, 'p'
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
after :each do
|
120
|
+
Object.class_eval do
|
121
|
+
remove_const :TestTransformer
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'should define literal? so that it returns true for any element on the list and false for any other' do
|
126
|
+
t = TestTransformer.new
|
127
|
+
['doc', 'div', 'p'].each do |el_name|
|
128
|
+
t.send(:literal?, REXML::Element.new(el_name)).should == true
|
129
|
+
end
|
130
|
+
['foo', 'bar', 'baz'].each do |el_name|
|
131
|
+
t.send(:literal?, REXML::Element.new(el_name)).should == false
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'should have no effect if literal? is subsequently defined' do
|
136
|
+
class TestTransformer < Quarto::Transformer
|
137
|
+
def literal?(element)
|
138
|
+
false
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
t = TestTransformer.new
|
143
|
+
['doc', 'div', 'p'].each do |el_name|
|
144
|
+
t.send(:literal?, REXML::Element.new(el_name)).should == false
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jarrett-quarto
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jarrett Colby
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-06-
|
12
|
+
date: 2009-06-15 00:00:00 -07:00
|
13
13
|
default_executable: quarto
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -18,7 +18,7 @@ dependencies:
|
|
18
18
|
version_requirement:
|
19
19
|
version_requirements: !ruby/object:Gem::Requirement
|
20
20
|
requirements:
|
21
|
-
- -
|
21
|
+
- - ~>
|
22
22
|
- !ruby/object:Gem::Version
|
23
23
|
version: 2.3.2
|
24
24
|
version:
|
@@ -40,14 +40,16 @@ files:
|
|
40
40
|
- lib/quarto/config.rb
|
41
41
|
- lib/quarto/element_wrapper.rb
|
42
42
|
- lib/quarto/generator.rb
|
43
|
+
- lib/quarto/html_transformer.rb
|
43
44
|
- lib/quarto/inheritable_attributes.rb
|
44
45
|
- lib/quarto/init_project.rb
|
45
46
|
- lib/quarto/rails_helper.rb
|
46
47
|
- lib/quarto/rendering.rb
|
48
|
+
- lib/quarto/transformation_helper.rb
|
49
|
+
- lib/quarto/transformer.rb
|
47
50
|
- lib/quarto/url_helper.rb
|
48
51
|
- lib/quarto/xml_doc.rb
|
49
52
|
- quarto.gemspec
|
50
|
-
- test.rb
|
51
53
|
has_rdoc: true
|
52
54
|
homepage: http://github.com/jarrett/quarto
|
53
55
|
post_install_message:
|
@@ -80,6 +82,7 @@ test_files:
|
|
80
82
|
- spec/generator_spec.rb
|
81
83
|
- spec/init_project_spec.rb
|
82
84
|
- spec/matchers/file_matchers.rb
|
85
|
+
- spec/matchers/rexml_matchers.rb
|
83
86
|
- spec/rendering_spec.rb
|
84
87
|
- spec/sample_models.rb
|
85
88
|
- spec/sample_project/generate.rb
|
@@ -90,4 +93,6 @@ test_files:
|
|
90
93
|
- spec/sample_project/models/product.rb
|
91
94
|
- spec/sample_project/urls.rb
|
92
95
|
- spec/spec_helper.rb
|
96
|
+
- spec/transformation_helper_spec.rb
|
97
|
+
- spec/transformer_spec.rb
|
93
98
|
- spec/url_helper_spec.rb
|
data/test.rb
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
require 'erb'
|
2
|
-
|
3
|
-
module Foo
|
4
|
-
def foo
|
5
|
-
6
|
6
|
-
end
|
7
|
-
end
|
8
|
-
|
9
|
-
template = ERB.new %q(
|
10
|
-
The value of x is <%= x %>
|
11
|
-
The method foo returns <%= foo %>
|
12
|
-
)
|
13
|
-
x = 42
|
14
|
-
|
15
|
-
b = binding
|
16
|
-
|
17
|
-
eval 'include Foo', b
|
18
|
-
|
19
|
-
template.extend(Foo)
|
20
|
-
puts template.result(b)
|