dress 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.html +152 -0
- data/README.markdown +139 -0
- data/{README → README.textile} +0 -0
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/dress.gemspec +57 -0
- data/lib/dress.rb +109 -0
- data/spec/dress_spec.rb +36 -0
- metadata +10 -5
data/README.html
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
<h1
|
2
|
+
>XSLT(not!) with Elegant Dress</h1
|
3
|
+
><p
|
4
|
+
>You'd swear XSLT is a joke, but it's not. It's cruelty. XSLT is like this: it's good to rid the city of this horrible vermin infestation-- Let's nuke the city. It's an out-of-proportion insanity. It's Turing-complete, and it's XML.</p
|
5
|
+
><p
|
6
|
+
>Hmm.</p
|
7
|
+
><p
|
8
|
+
>But what it is, is just a set of transformations on fragments of your XML document. The beauty of XSLT (they say), is that XSLT itself is XML, so you can (in principle) use XSLT to transform itself.</p
|
9
|
+
><p
|
10
|
+
>Hmm.</p
|
11
|
+
><p
|
12
|
+
>Oh I know, how about let's use Ruby?</p
|
13
|
+
><h1
|
14
|
+
>Transformation Elegant Dress</h1
|
15
|
+
><p
|
16
|
+
>Honestly, there's not much to it. A <code
|
17
|
+
>Dress</code
|
18
|
+
> is just a sequence of transformations you perform on a DOM tree. Unlike XSLT though, the transformations are performed directly on the DOM tree. That is to say, they are destructive.</p
|
19
|
+
><p
|
20
|
+
>This is a simple dress,</p
|
21
|
+
><pre
|
22
|
+
><code
|
23
|
+
>require 'dress'
|
24
|
+
doc = "<my><brain></brain></my>"
|
25
|
+
dress = Dress {
|
26
|
+
match("brain") do
|
27
|
+
set("size","pea")
|
28
|
+
end
|
29
|
+
}
|
30
|
+
result = dress.on(doc)
|
31
|
+
puts result.to_s
|
32
|
+
# <my><brain "size"="pea"></brain></my>
|
33
|
+
</code
|
34
|
+
></pre
|
35
|
+
><p
|
36
|
+
>We can have more matchers,</p
|
37
|
+
><pre
|
38
|
+
><code
|
39
|
+
>dress = Dress {
|
40
|
+
match("brain") do
|
41
|
+
set("size","pea")
|
42
|
+
end
|
43
|
+
match("my") do
|
44
|
+
each { |e| e.name = "homer"}
|
45
|
+
end
|
46
|
+
}
|
47
|
+
result = dress.on(doc)
|
48
|
+
puts result.to_s
|
49
|
+
# <homer><brain "size"="pea"></brain></homer>
|
50
|
+
</code
|
51
|
+
></pre
|
52
|
+
><p
|
53
|
+
>Note that match always yield a <code
|
54
|
+
>Nokogiri::Nodeset</code
|
55
|
+
>. But sometimes we'd like to operate on just one node (or the first one we find). For that there's the <code
|
56
|
+
>at</code
|
57
|
+
> matcher.</p
|
58
|
+
><pre
|
59
|
+
><code
|
60
|
+
>dress = Dress {
|
61
|
+
at("my") do
|
62
|
+
me.name = "homer"
|
63
|
+
end
|
64
|
+
}
|
65
|
+
</code
|
66
|
+
></pre
|
67
|
+
><p
|
68
|
+
><code
|
69
|
+
>me</code
|
70
|
+
> is always the object yielded by the matcher string. For <code
|
71
|
+
>match</code
|
72
|
+
> it would be a Nodeset, and for <code
|
73
|
+
>at</code
|
74
|
+
>, it would be an Element.</p
|
75
|
+
><p
|
76
|
+
>We can define helper methods on a Dress. This is one that implements a counter,</p
|
77
|
+
><pre
|
78
|
+
><code
|
79
|
+
>dress = Dress {
|
80
|
+
def count
|
81
|
+
@count ||= 0
|
82
|
+
@count += 1
|
83
|
+
@count
|
84
|
+
end
|
85
|
+
|
86
|
+
match("brain") do
|
87
|
+
each { |e| e["area"] = count.to_s }
|
88
|
+
end
|
89
|
+
}
|
90
|
+
puts dress.on(Nokogiri.make { my { brain; brain; brain; brain }}).to_s
|
91
|
+
# <my><brain area="1"></brain><brain area="2"></brain><brain area="3"></brain><brain area="4"></brain></my>
|
92
|
+
</code
|
93
|
+
></pre
|
94
|
+
><p
|
95
|
+
>We can combine dresses into lines,</p
|
96
|
+
><pre
|
97
|
+
><code
|
98
|
+
>d1 = Dress { ... }
|
99
|
+
d2 = Dress { ... }
|
100
|
+
(d1 | d2).on(doc)
|
101
|
+
</code
|
102
|
+
></pre
|
103
|
+
><p
|
104
|
+
>We can link lines together,</p
|
105
|
+
><pre
|
106
|
+
><code
|
107
|
+
>d3 = Dress { ... }
|
108
|
+
d4 = Dress { ... }
|
109
|
+
((d1 | d2) | (d3 | d4)).on(doc)
|
110
|
+
</code
|
111
|
+
></pre
|
112
|
+
><p
|
113
|
+
>A dress is a class inheriting from Dress.</p
|
114
|
+
><pre
|
115
|
+
><code
|
116
|
+
>class FancyDress < Dress
|
117
|
+
def helper1
|
118
|
+
end
|
119
|
+
def helper2
|
120
|
+
end
|
121
|
+
match(...) { ... }
|
122
|
+
...
|
123
|
+
end
|
124
|
+
|
125
|
+
class PrettyDress < Dress
|
126
|
+
...
|
127
|
+
end
|
128
|
+
</code
|
129
|
+
></pre
|
130
|
+
><p
|
131
|
+
>Then we can combine these dresses,</p
|
132
|
+
><pre
|
133
|
+
><code
|
134
|
+
>(FancyDress | PrettyDress).on(document)
|
135
|
+
</code
|
136
|
+
></pre
|
137
|
+
><p
|
138
|
+
>You can of course mix that with dynamic dresses,</p
|
139
|
+
><pre
|
140
|
+
><code
|
141
|
+
>(FancyDress | PrettyDress | Dress do ... end).on(doc)
|
142
|
+
</code
|
143
|
+
></pre
|
144
|
+
><h1
|
145
|
+
>That Wasn't So Hard, Was It?</h1
|
146
|
+
><p
|
147
|
+
>So Ruby saved us all from XSLT. Minus all the XSLT-inspired buckets of tears, the world is a better place. FTW.</p
|
148
|
+
><h1
|
149
|
+
>Copyright</h1
|
150
|
+
><p
|
151
|
+
>Copyright (c) 2009 Howard Yeh. See LICENSE for details.</p
|
152
|
+
>
|
data/README.markdown
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
# XSLT(not!) with Elegant Dress
|
2
|
+
|
3
|
+
You'd swear XSLT is a joke, but it's not. It's
|
4
|
+
cruelty. XSLT is like this: it's good to rid the
|
5
|
+
city of this horrible vermin infestation-- Let's
|
6
|
+
nuke the city. It's an out-of-proportion
|
7
|
+
insanity. It's Turing-complete, and it's XML.
|
8
|
+
|
9
|
+
Hmm.
|
10
|
+
|
11
|
+
But what it is, is just a set of transformations
|
12
|
+
on fragments of your XML document. The beauty of
|
13
|
+
XSLT (they say), is that XSLT itself is XML, so
|
14
|
+
you can (in principle) use XSLT to transform
|
15
|
+
itself.
|
16
|
+
|
17
|
+
Hmm.
|
18
|
+
|
19
|
+
Oh I know, how about let's use Ruby?
|
20
|
+
|
21
|
+
# Transformation Elegant Dress
|
22
|
+
|
23
|
+
Honestly, there's not much to it. A `Dress` is
|
24
|
+
just a sequence of transformations you perform on
|
25
|
+
a DOM tree. Unlike XSLT though, the
|
26
|
+
transformations are performed directly on the DOM
|
27
|
+
tree. That is to say, they are destructive.
|
28
|
+
|
29
|
+
This is a simple dress,
|
30
|
+
|
31
|
+
require 'dress'
|
32
|
+
doc = "<my><brain></brain></my>"
|
33
|
+
dress = Dress {
|
34
|
+
match("brain") do
|
35
|
+
set("size","pea")
|
36
|
+
end
|
37
|
+
}
|
38
|
+
result = dress.on(doc)
|
39
|
+
puts result.to_s
|
40
|
+
# <my><brain "size"="pea"></brain></my>
|
41
|
+
|
42
|
+
|
43
|
+
We can have more matchers,
|
44
|
+
|
45
|
+
dress = Dress {
|
46
|
+
match("brain") do
|
47
|
+
set("size","pea")
|
48
|
+
end
|
49
|
+
match("my") do
|
50
|
+
each { |e| e.name = "homer"}
|
51
|
+
end
|
52
|
+
}
|
53
|
+
result = dress.on(doc)
|
54
|
+
puts result.to_s
|
55
|
+
# <homer><brain "size"="pea"></brain></homer>
|
56
|
+
|
57
|
+
|
58
|
+
Note that match always yield a
|
59
|
+
`Nokogiri::Nodeset`. But sometimes we'd like to
|
60
|
+
operate on just one node (or the first one we
|
61
|
+
find). For that there's the `at` matcher.
|
62
|
+
|
63
|
+
dress = Dress {
|
64
|
+
at("my") do
|
65
|
+
me.name = "homer"
|
66
|
+
end
|
67
|
+
}
|
68
|
+
|
69
|
+
`me` is always the object yielded by the matcher
|
70
|
+
string. For `match` it would be a Nodeset, and for
|
71
|
+
`at`, it would be an Element.
|
72
|
+
|
73
|
+
We can define helper methods on a Dress. This is
|
74
|
+
one that implements a counter,
|
75
|
+
|
76
|
+
dress = Dress {
|
77
|
+
def count
|
78
|
+
@count ||= 0
|
79
|
+
@count += 1
|
80
|
+
@count
|
81
|
+
end
|
82
|
+
|
83
|
+
match("brain") do
|
84
|
+
each { |e| e["area"] = count.to_s }
|
85
|
+
end
|
86
|
+
}
|
87
|
+
puts dress.on(Nokogiri.make { my { brain; brain; brain; brain }}).to_s
|
88
|
+
# <my><brain area="1"></brain><brain area="2"></brain><brain area="3"></brain><brain area="4"></brain></my>
|
89
|
+
|
90
|
+
|
91
|
+
|
92
|
+
We can combine dresses into lines,
|
93
|
+
|
94
|
+
d1 = Dress { ... }
|
95
|
+
d2 = Dress { ... }
|
96
|
+
(d1 | d2).on(doc)
|
97
|
+
|
98
|
+
|
99
|
+
We can link lines together,
|
100
|
+
|
101
|
+
d3 = Dress { ... }
|
102
|
+
d4 = Dress { ... }
|
103
|
+
((d1 | d2) | (d3 | d4)).on(doc)
|
104
|
+
|
105
|
+
|
106
|
+
A dress is a class inheriting from Dress.
|
107
|
+
|
108
|
+
class FancyDress < Dress
|
109
|
+
def helper1
|
110
|
+
end
|
111
|
+
def helper2
|
112
|
+
end
|
113
|
+
match(...) { ... }
|
114
|
+
...
|
115
|
+
end
|
116
|
+
|
117
|
+
class PrettyDress < Dress
|
118
|
+
...
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
Then we can combine these dresses,
|
123
|
+
|
124
|
+
(FancyDress | PrettyDress).on(document)
|
125
|
+
|
126
|
+
You can of course mix that with dynamic dresses,
|
127
|
+
|
128
|
+
(FancyDress | PrettyDress | Dress do ... end).on(doc)
|
129
|
+
|
130
|
+
|
131
|
+
# That Wasn't So Hard, Was It?
|
132
|
+
|
133
|
+
So Ruby saved us all from XSLT. Minus all the
|
134
|
+
XSLT-inspired buckets of tears, the world is a
|
135
|
+
better place. FTW.
|
136
|
+
|
137
|
+
# Copyright
|
138
|
+
|
139
|
+
Copyright (c) 2009 Howard Yeh. See LICENSE for details.
|
data/{README → README.textile}
RENAMED
File without changes
|
data/Rakefile
CHANGED
@@ -5,7 +5,7 @@ begin
|
|
5
5
|
require 'jeweler'
|
6
6
|
Jeweler::Tasks.new do |gem|
|
7
7
|
gem.name = "dress"
|
8
|
-
gem.summary = %Q{DOM transformation based on
|
8
|
+
gem.summary = %Q{DOM transformation based on Nokogiri}
|
9
9
|
gem.description = %Q{Inspired by the horror of XSLT}
|
10
10
|
gem.email = "hayeah@gmail.com"
|
11
11
|
gem.homepage = "http://github.com/hayeah/dress"
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.2
|
data/dress.gemspec
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{dress}
|
8
|
+
s.version = "0.0.2"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Howard Yeh"]
|
12
|
+
s.date = %q{2010-01-07}
|
13
|
+
s.description = %q{Inspired by the horror of XSLT}
|
14
|
+
s.email = %q{hayeah@gmail.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.html",
|
18
|
+
"README.markdown",
|
19
|
+
"README.textile"
|
20
|
+
]
|
21
|
+
s.files = [
|
22
|
+
".document",
|
23
|
+
".gitignore",
|
24
|
+
"LICENSE",
|
25
|
+
"README.markdown",
|
26
|
+
"Rakefile",
|
27
|
+
"VERSION",
|
28
|
+
"dress.gemspec",
|
29
|
+
"lib/dress.rb",
|
30
|
+
"spec/dress_spec.rb",
|
31
|
+
"spec/spec.opts",
|
32
|
+
"spec/spec_helper.rb"
|
33
|
+
]
|
34
|
+
s.homepage = %q{http://github.com/hayeah/dress}
|
35
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
36
|
+
s.require_paths = ["lib"]
|
37
|
+
s.rubygems_version = %q{1.3.5}
|
38
|
+
s.summary = %q{DOM transformation based on Nokogiri}
|
39
|
+
s.test_files = [
|
40
|
+
"spec/dress_spec.rb",
|
41
|
+
"spec/spec_helper.rb"
|
42
|
+
]
|
43
|
+
|
44
|
+
if s.respond_to? :specification_version then
|
45
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
46
|
+
s.specification_version = 3
|
47
|
+
|
48
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
49
|
+
s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
|
50
|
+
else
|
51
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
52
|
+
end
|
53
|
+
else
|
54
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
data/lib/dress.rb
CHANGED
@@ -90,3 +90,112 @@ end
|
|
90
90
|
def Dress(&block)
|
91
91
|
Dress.style(&block)
|
92
92
|
end
|
93
|
+
|
94
|
+
# TODO move to monkey patch
|
95
|
+
class Nokogiri::XML::Builder
|
96
|
+
def n(*docs)
|
97
|
+
docs.each do |doc|
|
98
|
+
case doc
|
99
|
+
when String
|
100
|
+
self << doc
|
101
|
+
when Nokogiri::XML::Node
|
102
|
+
insert(doc)
|
103
|
+
#self << doc.to_s
|
104
|
+
#self.doc.children.each
|
105
|
+
else
|
106
|
+
raise "bad node: #{doc}"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def t(*texts)
|
112
|
+
texts.each do |text|
|
113
|
+
self.text(text.to_s)
|
114
|
+
self.text(" ")
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
class Dress::Maker
|
120
|
+
require 'active_support'
|
121
|
+
require 'active_support/core_ext'
|
122
|
+
|
123
|
+
class_inheritable_hash :layout_defs
|
124
|
+
self.layout_defs = {}
|
125
|
+
|
126
|
+
class << self
|
127
|
+
|
128
|
+
|
129
|
+
def layouts
|
130
|
+
#read_inheritable_attribute(:layouts).keys
|
131
|
+
layout_defs.keys
|
132
|
+
end
|
133
|
+
|
134
|
+
def layout(name=nil,&block)
|
135
|
+
layout_defs[name] = block
|
136
|
+
end
|
137
|
+
|
138
|
+
def with(name,page,*args,&block)
|
139
|
+
content = self.new.send(page,*args,&block)
|
140
|
+
l = layout(name).clone
|
141
|
+
l.at("content").replace(content)
|
142
|
+
l
|
143
|
+
end
|
144
|
+
|
145
|
+
def render(page,*args,&block)
|
146
|
+
self.new.render(page,*args,&block)
|
147
|
+
end
|
148
|
+
|
149
|
+
def render_with(layout,page,*args,&block)
|
150
|
+
self.new.render_with(layout,page,*args,&block)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def render(page,*args,&block)
|
155
|
+
# use default layout
|
156
|
+
render_with(nil,page,*args,&block)
|
157
|
+
end
|
158
|
+
|
159
|
+
def render_with(layout,page,*args,&block)
|
160
|
+
content = self.send(page,*args,&block)
|
161
|
+
l = self.instance_eval(&self.class.layout_defs[layout])
|
162
|
+
l.at("content").replace(content)
|
163
|
+
l
|
164
|
+
end
|
165
|
+
|
166
|
+
def method_missing(method,*args,&block)
|
167
|
+
Nokogiri.make {
|
168
|
+
#builder = self
|
169
|
+
d = self.send(method,*args,&block)
|
170
|
+
}
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def DressMaker(&block)
|
175
|
+
c = Class.new(Dress::Maker)
|
176
|
+
c.class_eval(&block)
|
177
|
+
c
|
178
|
+
end
|
179
|
+
|
180
|
+
|
181
|
+
# TODO break this out to a separate loadable file
|
182
|
+
class Dress::ActiveView < Dress::Maker
|
183
|
+
require 'action_pack'
|
184
|
+
require 'action_view'
|
185
|
+
extend ActionView::Helpers
|
186
|
+
include ActionView::Helpers
|
187
|
+
DEFAULT_CONFIG = ActionView::DEFAULT_CONFIG unless defined?(DEFAULT_CONFIG)
|
188
|
+
|
189
|
+
def config
|
190
|
+
self.config = DEFAULT_CONFIG unless @config
|
191
|
+
@config
|
192
|
+
end
|
193
|
+
|
194
|
+
def config=(config)
|
195
|
+
@config = ActiveSupport::OrderedOptions.new.merge(config)
|
196
|
+
end
|
197
|
+
|
198
|
+
def initialize(controller)
|
199
|
+
@controller = controller
|
200
|
+
end
|
201
|
+
end
|
data/spec/dress_spec.rb
CHANGED
@@ -109,3 +109,39 @@ describe "Dress" do
|
|
109
109
|
node.xpath("//bar/@b").map(&:value).should == ["2"]
|
110
110
|
end
|
111
111
|
end
|
112
|
+
|
113
|
+
describe "Dress::Maker" do
|
114
|
+
it "renders" do
|
115
|
+
d = DressMaker {
|
116
|
+
layout { n(wrapper_helper) }
|
117
|
+
layout(:foo) { foo { content }}
|
118
|
+
|
119
|
+
def content1
|
120
|
+
some_stuff(:a => "10", :b => "20") { inside }
|
121
|
+
end
|
122
|
+
|
123
|
+
def wrapper_helper
|
124
|
+
wrap1 { wrap2 { content }}
|
125
|
+
end
|
126
|
+
}
|
127
|
+
d.render(:content1).to_s.should == '<wrap1><wrap2><some_stuff a="10" b="20"><inside></inside></some_stuff></wrap2></wrap1>'
|
128
|
+
d.render_with(:foo,:content1).to_s.should == '<foo><some_stuff a="10" b="20"><inside></inside></some_stuff></foo>'
|
129
|
+
end
|
130
|
+
|
131
|
+
class D1 < Dress::Maker
|
132
|
+
layout { foo { content }}
|
133
|
+
end
|
134
|
+
|
135
|
+
class D2 < D1
|
136
|
+
def bar
|
137
|
+
"bar"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
it "inherit layouts" do
|
142
|
+
#p D1.layout_defs
|
143
|
+
#p D2.layout_defs
|
144
|
+
D2.layouts.should_not be_empty
|
145
|
+
#D1.render(:bar)
|
146
|
+
end
|
147
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dress
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Howard Yeh
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date:
|
12
|
+
date: 2010-01-07 00:00:00 -08:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -30,18 +30,23 @@ extensions: []
|
|
30
30
|
|
31
31
|
extra_rdoc_files:
|
32
32
|
- LICENSE
|
33
|
-
- README
|
33
|
+
- README.html
|
34
|
+
- README.markdown
|
35
|
+
- README.textile
|
34
36
|
files:
|
35
37
|
- .document
|
36
38
|
- .gitignore
|
37
39
|
- LICENSE
|
38
|
-
- README
|
40
|
+
- README.markdown
|
39
41
|
- Rakefile
|
40
42
|
- VERSION
|
43
|
+
- dress.gemspec
|
41
44
|
- lib/dress.rb
|
42
45
|
- spec/dress_spec.rb
|
43
46
|
- spec/spec.opts
|
44
47
|
- spec/spec_helper.rb
|
48
|
+
- README.html
|
49
|
+
- README.textile
|
45
50
|
has_rdoc: true
|
46
51
|
homepage: http://github.com/hayeah/dress
|
47
52
|
licenses: []
|
@@ -69,7 +74,7 @@ rubyforge_project:
|
|
69
74
|
rubygems_version: 1.3.5
|
70
75
|
signing_key:
|
71
76
|
specification_version: 3
|
72
|
-
summary: DOM transformation based on
|
77
|
+
summary: DOM transformation based on Nokogiri
|
73
78
|
test_files:
|
74
79
|
- spec/dress_spec.rb
|
75
80
|
- spec/spec_helper.rb
|