dress 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README +145 -0
- data/Rakefile +45 -0
- data/VERSION +1 -0
- data/lib/dress.rb +92 -0
- data/spec/dress_spec.rb +111 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +9 -0
- metadata +75 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Howard Yeh
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
h1. 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
|
+
Dress is an XML transformation library built on
|
22
|
+
top of Nokogiri. Nokogiri is awesome. Dress is
|
23
|
+
built on top of Nokogiri...
|
24
|
+
|
25
|
+
By syllogism, Dress is awesome.
|
26
|
+
|
27
|
+
|
28
|
+
h1. Transformation Elegant Dress
|
29
|
+
|
30
|
+
Honestly, there's not much to it. A @Dress@ is
|
31
|
+
just a sequence of transformations you perform on
|
32
|
+
a DOM tree. Unlike XSLT though, the
|
33
|
+
transformations are performed directly on the DOM
|
34
|
+
tree. That is to say, they are destructive.
|
35
|
+
|
36
|
+
This is a simple dress,
|
37
|
+
|
38
|
+
require 'dress'
|
39
|
+
doc = "<my><brain></brain></my>"
|
40
|
+
dress = Dress {
|
41
|
+
match("brain") do
|
42
|
+
set("size","pea")
|
43
|
+
end
|
44
|
+
}
|
45
|
+
result = dress.on(doc)
|
46
|
+
puts result.to_s
|
47
|
+
# <my><brain "size"="pea"></brain></my>
|
48
|
+
|
49
|
+
|
50
|
+
We can have more matchers,
|
51
|
+
|
52
|
+
dress = Dress {
|
53
|
+
match("brain") do
|
54
|
+
set("size","pea")
|
55
|
+
end
|
56
|
+
match("my") do
|
57
|
+
each { |e| e.name = "homer"}
|
58
|
+
end
|
59
|
+
}
|
60
|
+
result = dress.on(doc)
|
61
|
+
puts result.to_s
|
62
|
+
# <homer><brain "size"="pea"></brain></homer>
|
63
|
+
|
64
|
+
|
65
|
+
Note that match always yield a
|
66
|
+
Nokogiri::Nodeset. But sometimes we'd like to
|
67
|
+
operate on just one node (or the first one we
|
68
|
+
find). For that there's the @at@ matcher.
|
69
|
+
|
70
|
+
dress = Dress {
|
71
|
+
at("my") do
|
72
|
+
me.name = "homer"
|
73
|
+
end
|
74
|
+
}
|
75
|
+
|
76
|
+
@me@ is always the object yielded by the matcher
|
77
|
+
string. For @match@ it would be a Nodeset, and for
|
78
|
+
@at@, it would be an Element.
|
79
|
+
|
80
|
+
We can define helper methods on a Dress. This is
|
81
|
+
one that implements a counter,
|
82
|
+
|
83
|
+
dress = Dress {
|
84
|
+
def count
|
85
|
+
@count ||= 0
|
86
|
+
@count += 1
|
87
|
+
@count
|
88
|
+
end
|
89
|
+
|
90
|
+
match("brain") do
|
91
|
+
each { |e| e["area"] = count.to_s }
|
92
|
+
end
|
93
|
+
}
|
94
|
+
puts dress.on(Nokogiri.make { my { brain; brain; brain; brain }}).to_s
|
95
|
+
# <my><brain area="1"></brain><brain area="2"></brain><brain area="3"></brain><brain area="4"></brain></my>
|
96
|
+
|
97
|
+
|
98
|
+
We can combine dresses into lines,
|
99
|
+
|
100
|
+
d1 = Dress { ... }
|
101
|
+
d2 = Dress { ... }
|
102
|
+
(d1 | d2).on(doc)
|
103
|
+
|
104
|
+
|
105
|
+
We can link lines together,
|
106
|
+
|
107
|
+
d3 = Dress { ... }
|
108
|
+
d4 = Dress { ... }
|
109
|
+
((d1 | d2) | (d3 | d4)).on(doc)
|
110
|
+
|
111
|
+
|
112
|
+
A dress is a class inheriting from Dress.
|
113
|
+
|
114
|
+
class FancyDress < Dress
|
115
|
+
def helper1
|
116
|
+
end
|
117
|
+
def helper2
|
118
|
+
end
|
119
|
+
match(...) { ... }
|
120
|
+
...
|
121
|
+
end
|
122
|
+
|
123
|
+
class PrettyDress < Dress
|
124
|
+
...
|
125
|
+
end
|
126
|
+
|
127
|
+
|
128
|
+
Then we can combine these dresses,
|
129
|
+
|
130
|
+
(FancyDress | PrettyDress).on(document)
|
131
|
+
|
132
|
+
You can of course mix that with dynamic dresses,
|
133
|
+
|
134
|
+
(FancyDress | PrettyDress | Dress do ... end).on(doc)
|
135
|
+
|
136
|
+
|
137
|
+
h1. That Wasn't So Hard, Was It?
|
138
|
+
|
139
|
+
So Ruby saved us all from XSLT. Minus all the
|
140
|
+
XSLT-inspired buckets of tears, the world is a
|
141
|
+
better place. FTW.
|
142
|
+
|
143
|
+
h1. Copyright
|
144
|
+
|
145
|
+
Copyright (c) 2009 Howard Yeh. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "dress"
|
8
|
+
gem.summary = %Q{DOM transformation based on nokogiri}
|
9
|
+
gem.description = %Q{Inspired by the horror of XSLT}
|
10
|
+
gem.email = "hayeah@gmail.com"
|
11
|
+
gem.homepage = "http://github.com/hayeah/dress"
|
12
|
+
gem.authors = ["Howard Yeh"]
|
13
|
+
gem.add_development_dependency "rspec", ">= 1.2.9"
|
14
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
15
|
+
end
|
16
|
+
Jeweler::GemcutterTasks.new
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'spec/rake/spectask'
|
22
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
23
|
+
spec.libs << 'lib' << 'spec'
|
24
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
25
|
+
end
|
26
|
+
|
27
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
28
|
+
spec.libs << 'lib' << 'spec'
|
29
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
30
|
+
spec.rcov = true
|
31
|
+
end
|
32
|
+
|
33
|
+
task :spec => :check_dependencies
|
34
|
+
|
35
|
+
task :default => :spec
|
36
|
+
|
37
|
+
require 'rake/rdoctask'
|
38
|
+
Rake::RDocTask.new do |rdoc|
|
39
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
40
|
+
|
41
|
+
rdoc.rdoc_dir = 'rdoc'
|
42
|
+
rdoc.title = "dress #{version}"
|
43
|
+
rdoc.rdoc_files.include('README*')
|
44
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
45
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
data/lib/dress.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
class Dress
|
4
|
+
class Line
|
5
|
+
attr_reader :dresses
|
6
|
+
def initialize(dresses)
|
7
|
+
@dresses = dresses
|
8
|
+
end
|
9
|
+
|
10
|
+
def |(obj)
|
11
|
+
case obj
|
12
|
+
when Line
|
13
|
+
Line.new(self.dresses + obj.dresses)
|
14
|
+
when Dress
|
15
|
+
Line.new(self.dresses + [obj])
|
16
|
+
else
|
17
|
+
raise "expects a Dress or a Line"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def length
|
22
|
+
dresses.length
|
23
|
+
end
|
24
|
+
|
25
|
+
def on(node)
|
26
|
+
@dresses.each do |dress|
|
27
|
+
dress.on(node)
|
28
|
+
end
|
29
|
+
node
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class << self
|
34
|
+
attr_reader :transforms
|
35
|
+
def match(matcher,&block)
|
36
|
+
@transforms ||= []
|
37
|
+
@transforms << [:search,matcher,block]
|
38
|
+
end
|
39
|
+
|
40
|
+
def at(matcher,&block)
|
41
|
+
@transforms ||= []
|
42
|
+
@transforms << [:at,matcher,block]
|
43
|
+
end
|
44
|
+
|
45
|
+
# destructive transform of node
|
46
|
+
def on(node)
|
47
|
+
node =
|
48
|
+
case node
|
49
|
+
when Nokogiri::XML::Node
|
50
|
+
node
|
51
|
+
when String,IO
|
52
|
+
Nokogiri::XML(node)
|
53
|
+
else
|
54
|
+
raise "bad xml document: #{node}"
|
55
|
+
end
|
56
|
+
dresser = self.new(node)
|
57
|
+
node
|
58
|
+
end
|
59
|
+
|
60
|
+
def style(&block)
|
61
|
+
c = Class.new(Dress)
|
62
|
+
c.class_eval(&block)
|
63
|
+
c
|
64
|
+
end
|
65
|
+
|
66
|
+
def |(dress2)
|
67
|
+
raise "expects a Dress" unless dress2.ancestors.include?(Dress)
|
68
|
+
Line.new([self,dress2])
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
attr_reader :me
|
73
|
+
def initialize(node)
|
74
|
+
self.class.transforms.each do |(method,matcher,block)|
|
75
|
+
# method == :at | :search
|
76
|
+
@me = node.send(method,matcher)
|
77
|
+
self.instance_eval(&block) # this is so we can define helper methods on the dress
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def method_missing(method,*args,&block)
|
82
|
+
if block
|
83
|
+
@me.send(method,*args,&block)
|
84
|
+
else
|
85
|
+
@me.send(method,*args)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def Dress(&block)
|
91
|
+
Dress.style(&block)
|
92
|
+
end
|
data/spec/dress_spec.rb
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
require 'pp'
|
4
|
+
require 'tempfile'
|
5
|
+
|
6
|
+
describe "Dress" do
|
7
|
+
|
8
|
+
def test_data
|
9
|
+
Nokogiri.make { foo { bar { baz }}}.parent
|
10
|
+
end
|
11
|
+
|
12
|
+
it "transforms" do
|
13
|
+
d = Dress {
|
14
|
+
match("bar") do
|
15
|
+
wrap(nil) { qux }
|
16
|
+
end
|
17
|
+
|
18
|
+
match("bar") do
|
19
|
+
set("attr","oh yay!")
|
20
|
+
end
|
21
|
+
}
|
22
|
+
|
23
|
+
node = d.on(test_data)
|
24
|
+
(node / "qux bar").should_not be_empty
|
25
|
+
end
|
26
|
+
|
27
|
+
it "transforms xml string or file" do
|
28
|
+
d = Dress {
|
29
|
+
match("bar") do
|
30
|
+
wrap(nil) { qux }
|
31
|
+
end
|
32
|
+
}
|
33
|
+
doc_str = "<bar></bar>"
|
34
|
+
r = d.on(doc_str)
|
35
|
+
(r / "qux bar").should_not be_empty
|
36
|
+
|
37
|
+
Tempfile.open("test-xml") do |f|
|
38
|
+
f.puts doc_str
|
39
|
+
f.flush
|
40
|
+
r = d.on(File.new(f.path))
|
41
|
+
(r / "qux bar").should_not be_empty
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
it "transforms the first element found with at" do
|
46
|
+
d = Dress {
|
47
|
+
at("bar") do
|
48
|
+
me.name = "bar2"
|
49
|
+
end
|
50
|
+
}
|
51
|
+
r = d.on(test_data)
|
52
|
+
(r / "bar2").should_not be_empty
|
53
|
+
end
|
54
|
+
|
55
|
+
it "chains transforms" do
|
56
|
+
d1 = Dress {
|
57
|
+
match("bar") do
|
58
|
+
wrap(nil) { qux }
|
59
|
+
end
|
60
|
+
}
|
61
|
+
|
62
|
+
d2 = Dress {
|
63
|
+
match("bar") do
|
64
|
+
set("attr","oh yay!")
|
65
|
+
end
|
66
|
+
}
|
67
|
+
|
68
|
+
line = (d1 | d2)
|
69
|
+
line.should be_a(Dress::Line)
|
70
|
+
node = line.on(test_data)
|
71
|
+
(node / "qux").should_not be_empty
|
72
|
+
(node / "qux bar").should_not be_empty
|
73
|
+
node.xpath("//@attr").map(&:value).should == ["oh yay!"]
|
74
|
+
end
|
75
|
+
|
76
|
+
it "chains chains" do
|
77
|
+
d1 = Dress {
|
78
|
+
match("foo") do
|
79
|
+
set("a","1")
|
80
|
+
end
|
81
|
+
}
|
82
|
+
|
83
|
+
d2 = Dress {
|
84
|
+
match("foo") do
|
85
|
+
set("b","2")
|
86
|
+
end
|
87
|
+
}
|
88
|
+
|
89
|
+
d3 = Dress {
|
90
|
+
match("bar") do
|
91
|
+
set("a","1")
|
92
|
+
end
|
93
|
+
}
|
94
|
+
|
95
|
+
d4 = Dress {
|
96
|
+
match("bar") do
|
97
|
+
set("b","2")
|
98
|
+
end
|
99
|
+
}
|
100
|
+
|
101
|
+
line1 = (d1 | d2)
|
102
|
+
line2 = (d3 | d4)
|
103
|
+
line = (line1 | line2)
|
104
|
+
line.length.should == 4
|
105
|
+
node = line.on(test_data)
|
106
|
+
node.xpath("foo/@a").map(&:value).should == ["1"]
|
107
|
+
node.xpath("foo/@b").map(&:value).should == ["2"]
|
108
|
+
node.xpath("//bar/@a").map(&:value).should == ["1"]
|
109
|
+
node.xpath("//bar/@b").map(&:value).should == ["2"]
|
110
|
+
end
|
111
|
+
end
|
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dress
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Howard Yeh
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-11-28 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rspec
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.2.9
|
24
|
+
version:
|
25
|
+
description: Inspired by the horror of XSLT
|
26
|
+
email: hayeah@gmail.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- LICENSE
|
33
|
+
- README
|
34
|
+
files:
|
35
|
+
- .document
|
36
|
+
- .gitignore
|
37
|
+
- LICENSE
|
38
|
+
- README
|
39
|
+
- Rakefile
|
40
|
+
- VERSION
|
41
|
+
- lib/dress.rb
|
42
|
+
- spec/dress_spec.rb
|
43
|
+
- spec/spec.opts
|
44
|
+
- spec/spec_helper.rb
|
45
|
+
has_rdoc: true
|
46
|
+
homepage: http://github.com/hayeah/dress
|
47
|
+
licenses: []
|
48
|
+
|
49
|
+
post_install_message:
|
50
|
+
rdoc_options:
|
51
|
+
- --charset=UTF-8
|
52
|
+
require_paths:
|
53
|
+
- lib
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: "0"
|
59
|
+
version:
|
60
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: "0"
|
65
|
+
version:
|
66
|
+
requirements: []
|
67
|
+
|
68
|
+
rubyforge_project:
|
69
|
+
rubygems_version: 1.3.5
|
70
|
+
signing_key:
|
71
|
+
specification_version: 3
|
72
|
+
summary: DOM transformation based on nokogiri
|
73
|
+
test_files:
|
74
|
+
- spec/dress_spec.rb
|
75
|
+
- spec/spec_helper.rb
|