dolores-cml 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +5 -0
- data/LICENSE +20 -0
- data/README.rdoc +18 -0
- data/Rakefile +56 -0
- data/VERSION +1 -0
- data/cml.gemspec +103 -0
- data/lib/cml/converters/jsawesome.rb +102 -0
- data/lib/cml/liquid_filters.rb +7 -0
- data/lib/cml/parser.rb +107 -0
- data/lib/cml/tag.rb +134 -0
- data/lib/cml/tags/checkbox.rb +37 -0
- data/lib/cml/tags/checkboxes.rb +24 -0
- data/lib/cml/tags/group.rb +18 -0
- data/lib/cml/tags/hidden.rb +13 -0
- data/lib/cml/tags/meta.rb +12 -0
- data/lib/cml/tags/option.rb +25 -0
- data/lib/cml/tags/radio.rb +29 -0
- data/lib/cml/tags/radios.rb +24 -0
- data/lib/cml/tags/ratings.rb +42 -0
- data/lib/cml/tags/select.rb +23 -0
- data/lib/cml/tags/text.rb +10 -0
- data/lib/cml/tags/textarea.rb +10 -0
- data/lib/cml/tags/unknown.rb +21 -0
- data/lib/cml.rb +9 -0
- data/spec/complex_spec.rb +53 -0
- data/spec/converters/jsawesome_spec.rb +57 -0
- data/spec/fixtures/complex.cml +22 -0
- data/spec/fixtures/invalid.cml +10 -0
- data/spec/meta_spec.rb +31 -0
- data/spec/sorta_match.rb +41 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/tags/checkbox_spec.rb +98 -0
- data/spec/tags/checkboxes_spec.rb +30 -0
- data/spec/tags/group_spec.rb +35 -0
- data/spec/tags/hidden_spec.rb +15 -0
- data/spec/tags/meta_spec.rb +14 -0
- data/spec/tags/radios_spec.rb +30 -0
- data/spec/tags/select_spec.rb +50 -0
- data/spec/tags/text_spec.rb +36 -0
- data/spec/tags/textarea_spec.rb +18 -0
- data/spec/tags/unknown_spec.rb +19 -0
- data/spec/validation_spec.rb +28 -0
- metadata +140 -0
data/.document
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Chris Van Pelt
|
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.rdoc
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
= cml
|
2
|
+
|
3
|
+
Description goes here.
|
4
|
+
|
5
|
+
== Note on Patches/Pull Requests
|
6
|
+
|
7
|
+
* Fork the project.
|
8
|
+
* Make your feature addition or bug fix.
|
9
|
+
* Add tests for it. This is important so I don't break it in a
|
10
|
+
future version unintentionally.
|
11
|
+
* Commit, do not mess with rakefile, version, or history.
|
12
|
+
(if you want to have your own version, that is fine but
|
13
|
+
bump version in a commit by itself I can ignore when I pull)
|
14
|
+
* Send me a pull request. Bonus points for topic branches.
|
15
|
+
|
16
|
+
== Copyright
|
17
|
+
|
18
|
+
Copyright (c) 2009 Chris Van Pelt. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "cml"
|
8
|
+
gem.summary = "CML is CrowdFlower Markup Language"
|
9
|
+
gem.description = "CML let's you easily create form elements and custom interfaces for the CrowdFlower Croud Labor platform"
|
10
|
+
gem.email = "vanpelt@doloreslabs.com"
|
11
|
+
gem.homepage = "http://github.com/dolores/cml"
|
12
|
+
gem.authors = ["Chris Van Pelt"]
|
13
|
+
gem.add_dependency 'liquid', '>= 2.0.0'
|
14
|
+
gem.add_dependency 'nokogiri', '>= 1.3.3'
|
15
|
+
gem.add_dependency 'json_pure', '>= 1.1.3'
|
16
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
17
|
+
end
|
18
|
+
|
19
|
+
rescue LoadError
|
20
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
21
|
+
end
|
22
|
+
|
23
|
+
require 'spec/rake/spectask'
|
24
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
25
|
+
spec.libs << 'lib' << 'spec'
|
26
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
27
|
+
end
|
28
|
+
|
29
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
30
|
+
spec.libs << 'lib' << 'spec'
|
31
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
32
|
+
spec.rcov = true
|
33
|
+
end
|
34
|
+
|
35
|
+
task :refresh_builder => [:build] do
|
36
|
+
cp "pkg/cml-#{File.read("VERSION").strip}.gem", "../builder/gems/cache/"
|
37
|
+
rm_rf "../builder/gems/gems/cml-#{File.read("VERSION").strip}/"
|
38
|
+
`cd ../builder && bin/thor merb:gem:redeploy`
|
39
|
+
end
|
40
|
+
|
41
|
+
task :default => :spec
|
42
|
+
|
43
|
+
require 'rake/rdoctask'
|
44
|
+
Rake::RDocTask.new do |rdoc|
|
45
|
+
if File.exist?('VERSION.yml')
|
46
|
+
config = YAML.load(File.read('VERSION.yml'))
|
47
|
+
version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
|
48
|
+
else
|
49
|
+
version = ""
|
50
|
+
end
|
51
|
+
|
52
|
+
rdoc.rdoc_dir = 'rdoc'
|
53
|
+
rdoc.title = "cml #{version}"
|
54
|
+
rdoc.rdoc_files.include('README*')
|
55
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
56
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.3.0
|
data/cml.gemspec
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{cml}
|
5
|
+
s.version = "0.3.0"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Chris Van Pelt"]
|
9
|
+
s.date = %q{2009-08-25}
|
10
|
+
s.description = %q{CML let's you easily create form elements and custom interfaces for the CrowdFlower Croud Labor platform}
|
11
|
+
s.email = %q{vanpelt@doloreslabs.com}
|
12
|
+
s.extra_rdoc_files = [
|
13
|
+
"LICENSE",
|
14
|
+
"README.rdoc"
|
15
|
+
]
|
16
|
+
s.files = [
|
17
|
+
".document",
|
18
|
+
".gitignore",
|
19
|
+
"LICENSE",
|
20
|
+
"README.rdoc",
|
21
|
+
"Rakefile",
|
22
|
+
"VERSION",
|
23
|
+
"cml.gemspec",
|
24
|
+
"lib/cml.rb",
|
25
|
+
"lib/cml/converters/jsawesome.rb",
|
26
|
+
"lib/cml/liquid_filters.rb",
|
27
|
+
"lib/cml/parser.rb",
|
28
|
+
"lib/cml/tag.rb",
|
29
|
+
"lib/cml/tags/checkbox.rb",
|
30
|
+
"lib/cml/tags/checkboxes.rb",
|
31
|
+
"lib/cml/tags/group.rb",
|
32
|
+
"lib/cml/tags/hidden.rb",
|
33
|
+
"lib/cml/tags/meta.rb",
|
34
|
+
"lib/cml/tags/option.rb",
|
35
|
+
"lib/cml/tags/radio.rb",
|
36
|
+
"lib/cml/tags/radios.rb",
|
37
|
+
"lib/cml/tags/ratings.rb",
|
38
|
+
"lib/cml/tags/select.rb",
|
39
|
+
"lib/cml/tags/text.rb",
|
40
|
+
"lib/cml/tags/textarea.rb",
|
41
|
+
"lib/cml/tags/unknown.rb",
|
42
|
+
"spec/complex_spec.rb",
|
43
|
+
"spec/converters/jsawesome_spec.rb",
|
44
|
+
"spec/fixtures/complex.cml",
|
45
|
+
"spec/fixtures/invalid.cml",
|
46
|
+
"spec/meta_spec.rb",
|
47
|
+
"spec/sorta_match.rb",
|
48
|
+
"spec/spec_helper.rb",
|
49
|
+
"spec/tags/checkbox_spec.rb",
|
50
|
+
"spec/tags/checkboxes_spec.rb",
|
51
|
+
"spec/tags/group_spec.rb",
|
52
|
+
"spec/tags/hidden_spec.rb",
|
53
|
+
"spec/tags/meta_spec.rb",
|
54
|
+
"spec/tags/radios_spec.rb",
|
55
|
+
"spec/tags/select_spec.rb",
|
56
|
+
"spec/tags/text_spec.rb",
|
57
|
+
"spec/tags/textarea_spec.rb",
|
58
|
+
"spec/tags/unknown_spec.rb",
|
59
|
+
"spec/validation_spec.rb"
|
60
|
+
]
|
61
|
+
s.homepage = %q{http://github.com/dolores/cml}
|
62
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
63
|
+
s.require_paths = ["lib"]
|
64
|
+
s.rubygems_version = %q{1.3.4}
|
65
|
+
s.summary = %q{CML is CrowdFlower Markup Language}
|
66
|
+
s.test_files = [
|
67
|
+
"spec/complex_spec.rb",
|
68
|
+
"spec/converters/jsawesome_spec.rb",
|
69
|
+
"spec/meta_spec.rb",
|
70
|
+
"spec/sorta_match.rb",
|
71
|
+
"spec/spec_helper.rb",
|
72
|
+
"spec/tags/checkbox_spec.rb",
|
73
|
+
"spec/tags/checkboxes_spec.rb",
|
74
|
+
"spec/tags/group_spec.rb",
|
75
|
+
"spec/tags/hidden_spec.rb",
|
76
|
+
"spec/tags/meta_spec.rb",
|
77
|
+
"spec/tags/radios_spec.rb",
|
78
|
+
"spec/tags/select_spec.rb",
|
79
|
+
"spec/tags/text_spec.rb",
|
80
|
+
"spec/tags/textarea_spec.rb",
|
81
|
+
"spec/tags/unknown_spec.rb",
|
82
|
+
"spec/validation_spec.rb"
|
83
|
+
]
|
84
|
+
|
85
|
+
if s.respond_to? :specification_version then
|
86
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
87
|
+
s.specification_version = 3
|
88
|
+
|
89
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
90
|
+
s.add_runtime_dependency(%q<liquid>, [">= 2.0.0"])
|
91
|
+
s.add_runtime_dependency(%q<nokogiri>, [">= 1.3.3"])
|
92
|
+
s.add_runtime_dependency(%q<json_pure>, [">= 1.1.3"])
|
93
|
+
else
|
94
|
+
s.add_dependency(%q<liquid>, [">= 2.0.0"])
|
95
|
+
s.add_dependency(%q<nokogiri>, [">= 1.3.3"])
|
96
|
+
s.add_dependency(%q<json_pure>, [">= 1.1.3"])
|
97
|
+
end
|
98
|
+
else
|
99
|
+
s.add_dependency(%q<liquid>, [">= 2.0.0"])
|
100
|
+
s.add_dependency(%q<nokogiri>, [">= 1.3.3"])
|
101
|
+
s.add_dependency(%q<json_pure>, [">= 1.1.3"])
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module CML
|
2
|
+
module Converters
|
3
|
+
class JSAwesome
|
4
|
+
def initialize(json)
|
5
|
+
parsed = JSON.parse(json)
|
6
|
+
@json = parsed[0] || []
|
7
|
+
@labels = parsed[1] || {}
|
8
|
+
@tags = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def convert
|
12
|
+
@json.map do |k,v|
|
13
|
+
if k.is_a?(Array)
|
14
|
+
(k << v).map {|k,v| process(k,v)}
|
15
|
+
else
|
16
|
+
process(k,v)
|
17
|
+
end
|
18
|
+
end.join("\n") + meta
|
19
|
+
end
|
20
|
+
|
21
|
+
def meta
|
22
|
+
(@labels["gold"] || {}).select do |k,v|
|
23
|
+
next if k =~ /^_/
|
24
|
+
!@tags.map {|t| t.name }.include?(k)
|
25
|
+
end.map do |k,v|
|
26
|
+
tag("meta", k, {:meta => true})
|
27
|
+
end.join("\n")
|
28
|
+
end
|
29
|
+
|
30
|
+
def label(k)
|
31
|
+
k = k.sub(/^[_#*^~]?\+?/,'')
|
32
|
+
l = @labels[k]
|
33
|
+
label = l.is_a?(Hash) ? l["label"] : l if l
|
34
|
+
label ||= k.capitalize.gsub(/_/,' ')
|
35
|
+
end
|
36
|
+
|
37
|
+
def tag(tag, val, opts = {})
|
38
|
+
val = Array(val)
|
39
|
+
parts = val[0].split("|")
|
40
|
+
key = parts[0].sub(/^[_#*^~]?\+?/,'')
|
41
|
+
label = label(parts[0])
|
42
|
+
parts << key if label != key.capitalize.gsub(/_/,' ') || opts[:meta]
|
43
|
+
attrs = opts[:meta] ? [] : ["label=\"#{label}\""]
|
44
|
+
attrs << "name=\"#{parts[1]}\"" if parts[1]
|
45
|
+
attrs << "checked=\"true\"" if val[1]
|
46
|
+
attrs << "validates=\"required\"" if @labels[key] && @labels[key]["required"]
|
47
|
+
opts.reject {|k,v| v.to_s.strip.length == 0 || k.is_a?(Symbol) }.each do |k,v|
|
48
|
+
attrs << "#{k}=\"#{v}\""
|
49
|
+
end
|
50
|
+
if @labels["gold"] && @labels["gold"][key]
|
51
|
+
opts["gold"] = {}
|
52
|
+
src = @labels["gold"][key]
|
53
|
+
opts["gold"]["src"] = src
|
54
|
+
opts["gold"]["regex"], opts["gold"]["flags"] = Array(@labels["gold"]["_"+src+"_regex"])
|
55
|
+
opts["gold"]["strict"] = @labels["gold"]["_"+src+"_strict"]
|
56
|
+
else
|
57
|
+
opts["gold"] = false
|
58
|
+
end
|
59
|
+
|
60
|
+
tag = Parser.tag_class(tag).new("<cml:#{tag} #{attrs.join(" ")} />")
|
61
|
+
tag.gold = opts["gold"]
|
62
|
+
opts[:children].each { |c| tag.cml.add_child(tag(*c).cml) } if opts[:children]
|
63
|
+
@tags << tag if opts[:parent]
|
64
|
+
tag
|
65
|
+
end
|
66
|
+
|
67
|
+
def process(k,v)
|
68
|
+
select_default = @labels['{}'] || "Choose Category"
|
69
|
+
cml = case v
|
70
|
+
#a select tag or group of radios/checkboxes
|
71
|
+
when Array:
|
72
|
+
if(k =~ /^[^_]/)
|
73
|
+
if $~[0] == "*"
|
74
|
+
parent = "radios"
|
75
|
+
child = "radio"
|
76
|
+
elsif $~[0] == "^"
|
77
|
+
parent = "checkboxes"
|
78
|
+
child = "checkbox"
|
79
|
+
else
|
80
|
+
parent = "select"
|
81
|
+
child = "option"
|
82
|
+
end
|
83
|
+
tag(parent, k, {:children => v.map { |c| [child, c]}, :parent => true })
|
84
|
+
#Multiple hidden fields...
|
85
|
+
elsif(k =~ /^_/)
|
86
|
+
v.map { |c| tag("hidden", c, :parent => true)}.join("\n")
|
87
|
+
end
|
88
|
+
else
|
89
|
+
if(k =~ /^#/)
|
90
|
+
tag("textarea", k, {"default" => v, :parent => true})
|
91
|
+
elsif(k =~ /^_/)
|
92
|
+
tag("hidden", k, {"value" => v, :parent => true})
|
93
|
+
elsif(k =~ /^\^/)
|
94
|
+
tag("checkbox", k, {"default" => v, :parent => true})
|
95
|
+
else
|
96
|
+
tag("text", k, {"default" => v, :parent => true})
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
data/lib/cml/parser.rb
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
module CML
|
2
|
+
class Parser
|
3
|
+
attr_reader :doc, :cftags, :tags, :errors
|
4
|
+
|
5
|
+
def initialize(content, opts = {})
|
6
|
+
@opts = opts
|
7
|
+
@doc = Parser.parse(content)
|
8
|
+
@cftags = @doc.xpath("//cml:*[not(ancestor::cml:*)]")
|
9
|
+
@tags = @cftags.map do |t|
|
10
|
+
Parser.tag_class(t.name).new(t, @opts)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.parse(content)
|
15
|
+
Nokogiri::XML("<root xmlns:cml=\"http://crowdflower.com\">#{content}</root>")
|
16
|
+
end
|
17
|
+
|
18
|
+
def convert(opts = nil)
|
19
|
+
@opts = opts if opts
|
20
|
+
cloned = @doc.dup
|
21
|
+
cloned.xpath("//cml:*[not(ancestor::cml:*)]").each_with_index do |t,i|
|
22
|
+
t.replace(@tags[i].convert(opts))
|
23
|
+
end
|
24
|
+
cloned
|
25
|
+
end
|
26
|
+
|
27
|
+
def fields
|
28
|
+
@fields = {}
|
29
|
+
@tags.each do |g|
|
30
|
+
agg = g.attrs["aggregation"]
|
31
|
+
@fields[g.name] = agg.to_s if agg
|
32
|
+
end
|
33
|
+
@fields
|
34
|
+
end
|
35
|
+
|
36
|
+
def golds
|
37
|
+
return @golds if @golds
|
38
|
+
@golds = {}
|
39
|
+
@tags.each do |g|
|
40
|
+
gold = g.gold?
|
41
|
+
next unless gold
|
42
|
+
val = (gold.attributes["gold"] || gold.attributes["src"]).to_s
|
43
|
+
val = "#{g.name}_gold" if val =~ /^(true|\s*)$/
|
44
|
+
@golds[g.name] = val
|
45
|
+
["strict","regex"].each do |attrs|
|
46
|
+
if a = gold.attributes[attrs]
|
47
|
+
@golds["_#{val}_#{attrs}"] = attrs == "regex" ? [a.to_s, gold.attributes["flags"].to_s] : a.to_s
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
@golds
|
52
|
+
end
|
53
|
+
|
54
|
+
def valid?
|
55
|
+
@errors = []
|
56
|
+
if e = @doc.errors.select {|e| s.fatal? }.last
|
57
|
+
@errors << "Malformed CML (#{e.level}). #{e.message.chomp} on line #{e.line} column #{e.column}."
|
58
|
+
end
|
59
|
+
@tags.select {|t| t.validate? && t.name =~ /^\s*$/ }.each do |t|
|
60
|
+
@errors << "#{t.to_s.split("\n")[0]} does not have a label or name specified."
|
61
|
+
end
|
62
|
+
dupes = @tags.select do |tag|
|
63
|
+
tag.validate? && @tags.select {|t| t.name == tag.name}.length > 1
|
64
|
+
end
|
65
|
+
(dupes[1..-1] || []).each do |t|
|
66
|
+
@errors << "#{t.to_s.split("\n")[0]} has a duplicated name, please specify a unique name attribute."
|
67
|
+
end
|
68
|
+
@tags.each do |t|
|
69
|
+
next unless t.children
|
70
|
+
dupes = t.children.select do |child|
|
71
|
+
t.children.select {|c| c.value == child.value}.length > 1
|
72
|
+
end
|
73
|
+
(dupes[1..-1] || []).each do |c|
|
74
|
+
@errors << "#{c} a child of #{t.to_s.split("\n")[0]} has a duplicated value, please specify a unique value attribute."
|
75
|
+
end
|
76
|
+
end
|
77
|
+
@errors.length == 0
|
78
|
+
end
|
79
|
+
|
80
|
+
def wrap(content)
|
81
|
+
@opts[:no_wrap] ? content : "<div class=\"cml#{" "+@opts[:class] if @opts[:class]}\" id=\"#{@opts[:prefix]}\">#{content}</div>"
|
82
|
+
end
|
83
|
+
|
84
|
+
def to_s
|
85
|
+
@doc.root.children.to_s
|
86
|
+
end
|
87
|
+
|
88
|
+
def to_html(opts = nil)
|
89
|
+
xml = convert(opts)
|
90
|
+
wrap(xml.at("root").inner_html)
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.escape( string )
|
94
|
+
string.to_s.gsub( /&/, "&" ).
|
95
|
+
gsub( /</, "<" ).
|
96
|
+
gsub( />/, ">" ).
|
97
|
+
gsub( /"/, """ )
|
98
|
+
end
|
99
|
+
|
100
|
+
#This takes the name of the tag and converts it to the appropriate task
|
101
|
+
def self.tag_class(name)
|
102
|
+
CML::Tags.module_eval(name.gsub(/(^|_)(.)/) { $2.upcase })
|
103
|
+
rescue
|
104
|
+
CML::Tags::Unknown
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
data/lib/cml/tag.rb
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
module CML
|
2
|
+
class Tag
|
3
|
+
attr_reader :attrs, :tag, :data, :cml
|
4
|
+
|
5
|
+
def initialize(cml, opts = {})
|
6
|
+
@cml = cml.is_a?(String) ? Parser.parse(cml).at("/root/*") : cml
|
7
|
+
@attrs = @cml.attributes#Hash[*cml.attributes.map {|k,v| [k.intern,v]}.flatten]
|
8
|
+
@tag = @cml.name
|
9
|
+
@opts = opts
|
10
|
+
end
|
11
|
+
|
12
|
+
def prefix(name)
|
13
|
+
@opts[:prefix] ? "#{@opts[:prefix]}[#{name}]" : "#{name}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def gold?
|
17
|
+
@attrs["gold"] ? @cml : @cml.at("./cml:gold")
|
18
|
+
end
|
19
|
+
|
20
|
+
def gold=(opts = {})
|
21
|
+
cur = gold?
|
22
|
+
advanced = ["strict", "regex"].any? {|k| opts && opts[k] }
|
23
|
+
if !cur
|
24
|
+
cur = advanced ? @cml.add_child(Parser.parse("<cml:gold/>").at("/root/*")) : @cml
|
25
|
+
end
|
26
|
+
|
27
|
+
if cur.name == "gold"
|
28
|
+
if !opts
|
29
|
+
cur.remove
|
30
|
+
elsif advanced
|
31
|
+
opts.each do |k,v|
|
32
|
+
cur[k] = v.to_s if v
|
33
|
+
end
|
34
|
+
else
|
35
|
+
cur.remove
|
36
|
+
cur = @cml
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
if cur.name != "gold"
|
41
|
+
if !opts
|
42
|
+
@cml.remove_attribute("gold")
|
43
|
+
elsif opts["src"] && opts["src"] != "#{name}_gold"
|
44
|
+
@cml["gold"] = opts["src"]
|
45
|
+
else
|
46
|
+
@cml["gold"] = "true"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def name
|
52
|
+
(@attrs["name"] || @attrs["label"]).to_s.downcase.gsub(/\s/,"_").gsub(/\W/,"")
|
53
|
+
end
|
54
|
+
|
55
|
+
def wrapper_classes
|
56
|
+
extras = @attrs["only-if"] ? "only-if:#{@attrs["only-if"]}" : ""
|
57
|
+
"#{@tag} #{extras}".strip
|
58
|
+
end
|
59
|
+
|
60
|
+
def children
|
61
|
+
false
|
62
|
+
end
|
63
|
+
|
64
|
+
def classes
|
65
|
+
@classes ||= [name] << validations << @attrs["class"].to_s.split(/ /)
|
66
|
+
@classes.uniq.reject {|c| c.length == 0 }.join(" ").strip
|
67
|
+
end
|
68
|
+
|
69
|
+
def convert(opts = nil)
|
70
|
+
@opts = opts if opts
|
71
|
+
html = to_html
|
72
|
+
html.strip.length == 0 ? Nokogiri::XML::Text.new(html, @cml.document) : Nokogiri::HTML.fragment(html)
|
73
|
+
end
|
74
|
+
|
75
|
+
def to_html
|
76
|
+
wrapper(Liquid::Template.parse(self.class::Template).render(data, [LiquidFilters]))
|
77
|
+
end
|
78
|
+
|
79
|
+
def to_s
|
80
|
+
@cml.to_s
|
81
|
+
end
|
82
|
+
|
83
|
+
def label(label, tag = "label")
|
84
|
+
label ||= @attrs["name"].to_s.capitalize
|
85
|
+
required = " <span class=\"required\">(required)</span>" if validations.include?("required")
|
86
|
+
"<#{tag}>#{label}#{required}</#{tag.split(" ")[0]}>" if label.to_s != ""
|
87
|
+
end
|
88
|
+
|
89
|
+
def legend(label)
|
90
|
+
label(label, "legend") if label
|
91
|
+
end
|
92
|
+
|
93
|
+
def to_liquid
|
94
|
+
to_html
|
95
|
+
end
|
96
|
+
|
97
|
+
def validations
|
98
|
+
@validations ||= @attrs["validates"].to_s.split(/ /).map {|v| "validates-#{v}"}
|
99
|
+
end
|
100
|
+
|
101
|
+
def instructions
|
102
|
+
@instructions ||= @attrs["instructions"] || (@cml.at("./cml:instructions") ? @cml.at("./cml:instructions").inner_html : nil)
|
103
|
+
@instructions ? "<p class=\"instructions\">#{@instructions}</p>" : nil
|
104
|
+
end
|
105
|
+
|
106
|
+
def validate?
|
107
|
+
true
|
108
|
+
end
|
109
|
+
|
110
|
+
def wrapper(parsed, tag = "div")
|
111
|
+
#Deal with a label only wrapper... kinda jank
|
112
|
+
if tag == "label" && instructions
|
113
|
+
tag = "div"
|
114
|
+
parsed = "<label>#{parsed}</label>"
|
115
|
+
end
|
116
|
+
#Inserting instructions here makes it a non-variable input maybe...
|
117
|
+
"<#{tag} class=\"#{wrapper_classes}\">#{parsed}#{instructions}</#{tag}>"
|
118
|
+
end
|
119
|
+
|
120
|
+
def value
|
121
|
+
@attrs["value"].to_s
|
122
|
+
end
|
123
|
+
|
124
|
+
def data
|
125
|
+
(@opts[:data] || {}).merge({
|
126
|
+
"name" => prefix(name),
|
127
|
+
"label" => label(@attrs["label"]),
|
128
|
+
"legend" => label(@attrs["label"], "h2 class=\"legend\""),
|
129
|
+
"classes" => classes,
|
130
|
+
"value" => value
|
131
|
+
})
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module CML
|
2
|
+
module Tags
|
3
|
+
class Checkbox < Tag
|
4
|
+
Template = <<-HTML.freeze
|
5
|
+
{{default}}
|
6
|
+
<label><input name="{{name}}" type="checkbox" value="{{value}}" class="{{classes}}" {{checked}}/> {{label}}</label>
|
7
|
+
HTML
|
8
|
+
|
9
|
+
def prefix(name)
|
10
|
+
super(name) + (grouped? ? "[]" : "")
|
11
|
+
end
|
12
|
+
|
13
|
+
def grouped?
|
14
|
+
@cml.parent && @cml.parent.name == "checkboxes"
|
15
|
+
end
|
16
|
+
|
17
|
+
def wrapper(parsed)
|
18
|
+
grouped? ? parsed : super
|
19
|
+
end
|
20
|
+
|
21
|
+
def value
|
22
|
+
(@attrs["value"] || (grouped? ? @attrs["label"] : "true")).to_s
|
23
|
+
end
|
24
|
+
|
25
|
+
def data
|
26
|
+
default = ""
|
27
|
+
if @attrs["default"]
|
28
|
+
default = Hidden.new("<cml:hidden name=\"#{name}\" value=\"#{@attrs["default"]}\"/>", @opts).to_html
|
29
|
+
end
|
30
|
+
super.merge({
|
31
|
+
"default" => default,
|
32
|
+
"checked" => @attrs["checked"] ? "checked=\"checked\"" : "",
|
33
|
+
"label" => @attrs["label"].to_s})
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module CML
|
2
|
+
module Tags
|
3
|
+
class Checkboxes < Tag
|
4
|
+
Template = <<-HTML.freeze
|
5
|
+
{{legend}}
|
6
|
+
{% for checkbox in checkboxes %}
|
7
|
+
{{checkbox}}
|
8
|
+
{% endfor %}
|
9
|
+
HTML
|
10
|
+
|
11
|
+
def data
|
12
|
+
super.merge({"checkboxes" => children})
|
13
|
+
end
|
14
|
+
|
15
|
+
def children
|
16
|
+
@cml.xpath("cml:checkbox").map do |c|
|
17
|
+
c["name"] ||= name
|
18
|
+
c["validates"] ||= @attrs["validates"].to_s
|
19
|
+
Checkbox.new(c, @opts)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module CML
|
2
|
+
module Tags
|
3
|
+
class Group < Tag
|
4
|
+
Template = <<-HTML.freeze
|
5
|
+
{{label}}
|
6
|
+
{{content}}
|
7
|
+
HTML
|
8
|
+
|
9
|
+
def validate?
|
10
|
+
false
|
11
|
+
end
|
12
|
+
|
13
|
+
def data
|
14
|
+
super.merge({"content" => Parser.new(@cml.inner_html, @opts.merge(:no_wrap => true)).to_html})
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|