cml 1.4.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/.document +5 -0
  2. data/.gitignore +5 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +18 -0
  5. data/Rakefile +56 -0
  6. data/VERSION +1 -0
  7. data/cml.gemspec +129 -0
  8. data/lib/cml/converters/jsawesome.rb +103 -0
  9. data/lib/cml/gold.rb +33 -0
  10. data/lib/cml/liquid_filters.rb +13 -0
  11. data/lib/cml/parser.rb +160 -0
  12. data/lib/cml/tag.rb +308 -0
  13. data/lib/cml/tags/checkbox.rb +77 -0
  14. data/lib/cml/tags/checkboxes.rb +38 -0
  15. data/lib/cml/tags/group.rb +25 -0
  16. data/lib/cml/tags/hidden.rb +17 -0
  17. data/lib/cml/tags/iterate.rb +86 -0
  18. data/lib/cml/tags/meta.rb +12 -0
  19. data/lib/cml/tags/multiple_text.rb +11 -0
  20. data/lib/cml/tags/option.rb +29 -0
  21. data/lib/cml/tags/radio.rb +48 -0
  22. data/lib/cml/tags/radios.rb +36 -0
  23. data/lib/cml/tags/ratings.rb +61 -0
  24. data/lib/cml/tags/search.rb +97 -0
  25. data/lib/cml/tags/select.rb +50 -0
  26. data/lib/cml/tags/text.rb +45 -0
  27. data/lib/cml/tags/textarea.rb +45 -0
  28. data/lib/cml/tags/thumb.rb +25 -0
  29. data/lib/cml/tags/unknown.rb +21 -0
  30. data/lib/cml.rb +15 -0
  31. data/spec/complex_spec.rb +127 -0
  32. data/spec/converters/jsawesome_spec.rb +75 -0
  33. data/spec/fixtures/complex.cml +23 -0
  34. data/spec/fixtures/html.cml +34 -0
  35. data/spec/fixtures/invalid.cml +10 -0
  36. data/spec/fixtures/script-style.cml +12 -0
  37. data/spec/fixtures/segfault.cml +1 -0
  38. data/spec/gold_spec.rb +29 -0
  39. data/spec/meta_spec.rb +47 -0
  40. data/spec/normalize_spec.rb +214 -0
  41. data/spec/show_data_spec.rb +308 -0
  42. data/spec/sorta_match.rb +41 -0
  43. data/spec/spec_helper.rb +26 -0
  44. data/spec/tags/checkbox_spec.rb +155 -0
  45. data/spec/tags/checkboxes_spec.rb +171 -0
  46. data/spec/tags/group_spec.rb +108 -0
  47. data/spec/tags/hidden_spec.rb +17 -0
  48. data/spec/tags/iterate_spec.rb +259 -0
  49. data/spec/tags/meta_spec.rb +14 -0
  50. data/spec/tags/multiple_text_spec.rb +40 -0
  51. data/spec/tags/radios_spec.rb +81 -0
  52. data/spec/tags/ratings_spec.rb +79 -0
  53. data/spec/tags/search_spec.rb +132 -0
  54. data/spec/tags/select_spec.rb +76 -0
  55. data/spec/tags/tag_spec.rb +93 -0
  56. data/spec/tags/text_spec.rb +66 -0
  57. data/spec/tags/textarea_spec.rb +58 -0
  58. data/spec/tags/thumb_spec.rb +20 -0
  59. data/spec/tags/unknown_spec.rb +19 -0
  60. data/spec/validation_spec.rb +62 -0
  61. metadata +182 -0
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
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.4.2'
15
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
16
+ end
17
+
18
+ rescue LoadError
19
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
20
+ end
21
+
22
+ require 'spec/rake/spectask'
23
+ Spec::Rake::SpecTask.new(:spec) do |spec|
24
+ spec.libs << 'lib' << 'spec'
25
+ spec.spec_opts = ["--format", "specdoc"]
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
+ 1.4.1
data/cml.gemspec ADDED
@@ -0,0 +1,129 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{cml}
8
+ s.version = "1.4.2"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Chris Van Pelt"]
12
+ s.date = %q{2011-02-28}
13
+ s.description = %q{CML let's you easily create form elements and custom interfaces for the CrowdFlower Croud Labor platform}
14
+ s.email = %q{vanpelt@doloreslabs.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".gitignore",
22
+ "LICENSE",
23
+ "README.rdoc",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "cml.gemspec",
27
+ "lib/cml.rb",
28
+ "lib/cml/converters/jsawesome.rb",
29
+ "lib/cml/gold.rb",
30
+ "lib/cml/liquid_filters.rb",
31
+ "lib/cml/parser.rb",
32
+ "lib/cml/tag.rb",
33
+ "lib/cml/tags/checkbox.rb",
34
+ "lib/cml/tags/checkboxes.rb",
35
+ "lib/cml/tags/group.rb",
36
+ "lib/cml/tags/hidden.rb",
37
+ "lib/cml/tags/iterate.rb",
38
+ "lib/cml/tags/meta.rb",
39
+ "lib/cml/tags/multiple_text.rb",
40
+ "lib/cml/tags/option.rb",
41
+ "lib/cml/tags/radio.rb",
42
+ "lib/cml/tags/radios.rb",
43
+ "lib/cml/tags/ratings.rb",
44
+ "lib/cml/tags/search.rb",
45
+ "lib/cml/tags/select.rb",
46
+ "lib/cml/tags/text.rb",
47
+ "lib/cml/tags/textarea.rb",
48
+ "lib/cml/tags/thumb.rb",
49
+ "lib/cml/tags/unknown.rb",
50
+ "spec/complex_spec.rb",
51
+ "spec/converters/jsawesome_spec.rb",
52
+ "spec/fixtures/complex.cml",
53
+ "spec/fixtures/html.cml",
54
+ "spec/fixtures/invalid.cml",
55
+ "spec/fixtures/script-style.cml",
56
+ "spec/fixtures/segfault.cml",
57
+ "spec/gold_spec.rb",
58
+ "spec/meta_spec.rb",
59
+ "spec/normalize_spec.rb",
60
+ "spec/show_data_spec.rb",
61
+ "spec/sorta_match.rb",
62
+ "spec/spec_helper.rb",
63
+ "spec/tags/checkbox_spec.rb",
64
+ "spec/tags/checkboxes_spec.rb",
65
+ "spec/tags/group_spec.rb",
66
+ "spec/tags/hidden_spec.rb",
67
+ "spec/tags/iterate_spec.rb",
68
+ "spec/tags/meta_spec.rb",
69
+ "spec/tags/multiple_text_spec.rb",
70
+ "spec/tags/radios_spec.rb",
71
+ "spec/tags/ratings_spec.rb",
72
+ "spec/tags/search_spec.rb",
73
+ "spec/tags/select_spec.rb",
74
+ "spec/tags/tag_spec.rb",
75
+ "spec/tags/text_spec.rb",
76
+ "spec/tags/textarea_spec.rb",
77
+ "spec/tags/thumb_spec.rb",
78
+ "spec/tags/unknown_spec.rb",
79
+ "spec/validation_spec.rb"
80
+ ]
81
+ s.homepage = %q{http://github.com/dolores/cml}
82
+ s.rdoc_options = ["--charset=UTF-8"]
83
+ s.require_paths = ["lib"]
84
+ s.rubygems_version = %q{1.3.7}
85
+ s.summary = %q{CML is CrowdFlower Markup Language}
86
+ s.test_files = [
87
+ "spec/complex_spec.rb",
88
+ "spec/converters/jsawesome_spec.rb",
89
+ "spec/gold_spec.rb",
90
+ "spec/meta_spec.rb",
91
+ "spec/normalize_spec.rb",
92
+ "spec/show_data_spec.rb",
93
+ "spec/sorta_match.rb",
94
+ "spec/spec_helper.rb",
95
+ "spec/tags/checkbox_spec.rb",
96
+ "spec/tags/checkboxes_spec.rb",
97
+ "spec/tags/group_spec.rb",
98
+ "spec/tags/hidden_spec.rb",
99
+ "spec/tags/iterate_spec.rb",
100
+ "spec/tags/meta_spec.rb",
101
+ "spec/tags/multiple_text_spec.rb",
102
+ "spec/tags/radios_spec.rb",
103
+ "spec/tags/ratings_spec.rb",
104
+ "spec/tags/search_spec.rb",
105
+ "spec/tags/select_spec.rb",
106
+ "spec/tags/tag_spec.rb",
107
+ "spec/tags/text_spec.rb",
108
+ "spec/tags/textarea_spec.rb",
109
+ "spec/tags/thumb_spec.rb",
110
+ "spec/tags/unknown_spec.rb",
111
+ "spec/validation_spec.rb"
112
+ ]
113
+
114
+ if s.respond_to? :specification_version then
115
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
116
+ s.specification_version = 3
117
+
118
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
119
+ s.add_runtime_dependency(%q<liquid>, [">= 2.0.0"])
120
+ s.add_runtime_dependency(%q<nokogiri>, [">= 1.4.2"])
121
+ else
122
+ s.add_dependency(%q<liquid>, [">= 2.0.0"])
123
+ s.add_dependency(%q<nokogiri>, [">= 1.4.2"])
124
+ end
125
+ else
126
+ s.add_dependency(%q<liquid>, [">= 2.0.0"])
127
+ s.add_dependency(%q<nokogiri>, [">= 1.4.2"])
128
+ end
129
+ end
@@ -0,0 +1,103 @@
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.compact.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
+ return nil unless val[0]
40
+ parts = val[0].split("|")
41
+ key = parts[0].sub(/^[_#*^~]?\+?/,'')
42
+ label = label(parts[0])
43
+ parts << key if label != key.capitalize.gsub(/_/,' ') || opts[:meta]
44
+ attrs = opts[:meta] ? [] : ["label=\"#{label}\""]
45
+ attrs << "#{opts[:nested] ? "value" : "name"}=\"#{parts[1]}\"" if parts[1]
46
+ attrs << "checked=\"true\"" if val[1]
47
+ attrs << "validates=\"required\"" if @labels[key] && @labels[key]["required"]
48
+ opts.reject {|k,v| v.to_s.strip.length == 0 || k.is_a?(Symbol) }.each do |k,v|
49
+ attrs << "#{k}=\"#{v}\""
50
+ end
51
+ if @labels["gold"] && @labels["gold"][key]
52
+ opts["gold"] = {}
53
+ src = @labels["gold"][key]
54
+ opts["gold"]["src"] = src
55
+ opts["gold"]["regex"], opts["gold"]["flags"] = Array(@labels["gold"]["_"+src+"_regex"])
56
+ opts["gold"]["strict"] = @labels["gold"]["_"+src+"_strict"]
57
+ else
58
+ opts["gold"] = false
59
+ end
60
+
61
+ tag = Parser.tag_class(tag).new("<cml:#{tag} #{attrs.join(" ")} />")
62
+ tag.gold = opts["gold"]
63
+ opts[:children].each { |c| tag.cml.add_child(tag(*c).cml) } if opts[:children]
64
+ @tags << tag if opts[:parent]
65
+ tag
66
+ end
67
+
68
+ def process(k,v)
69
+ select_default = @labels['{}'] || "Choose Category"
70
+ cml = case v
71
+ #a select tag or group of radios/checkboxes
72
+ when Array:
73
+ if(k =~ /^[^_]/)
74
+ if $~[0] == "*"
75
+ parent = "radios"
76
+ child = "radio"
77
+ elsif $~[0] == "^"
78
+ parent = "checkboxes"
79
+ child = "checkbox"
80
+ else
81
+ parent = "select"
82
+ child = "option"
83
+ end
84
+ tag(parent, k, {:children => v.map { |c| [child, c, {:nested => child}]}, :parent => parent })
85
+ #Multiple hidden fields...
86
+ elsif(k =~ /^_/)
87
+ v.map { |c| tag("hidden", c, :parent => true)}.join("\n")
88
+ end
89
+ else
90
+ if(k =~ /^#/)
91
+ tag("textarea", k, {"default" => v, :parent => true})
92
+ elsif(k =~ /^_/)
93
+ tag("hidden", k, {"value" => v, :parent => true})
94
+ elsif(k =~ /^\^/)
95
+ tag("checkbox", k, {"default" => v, :parent => true})
96
+ else
97
+ tag("text", k, {"default" => v, :parent => true})
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
data/lib/cml/gold.rb ADDED
@@ -0,0 +1,33 @@
1
+ module CML
2
+ class Gold
3
+ Attrs = ["exact","strict","regex","structured","matcher"]
4
+ attr_reader *(Attrs + [:src, :name])
5
+
6
+ def initialize(tag, gold)
7
+ @name = tag.name(false)
8
+ @src = (gold.attributes["gold"] || gold.attributes["src"]).to_s
9
+ @src = "#{@name}_gold" if @src =~ /^(true|\s*)$/
10
+ Attrs.each do |attribute|
11
+ if a = gold.attributes[attribute]
12
+ value = case attribute
13
+ when "regex": [a.to_s, gold.attributes["flags"].to_s, gold.attributes["no_escape"].to_s]
14
+ when "matcher": a.to_s
15
+ else
16
+ a.to_s == "true"
17
+ end
18
+ instance_variable_set "@#{attribute}", value
19
+ end
20
+ end
21
+ @structured = true if tag.iterating?
22
+ end
23
+
24
+ def to_hash
25
+ Hash[
26
+ *[@name,@src].concat(Attrs.map do |a|
27
+ val = instance_variable_get("@#{a}")
28
+ val ? ["_#{@src}_#{a}", val] : nil
29
+ end.compact.flatten(1))
30
+ ]
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,13 @@
1
+ module CML
2
+ module LiquidFilters
3
+ def to_json(input)
4
+ input.to_json
5
+ end
6
+ end
7
+
8
+ class LiquidHash < Hash
9
+ def to_s
10
+ self["to_s"]
11
+ end
12
+ end
13
+ end
data/lib/cml/parser.rb ADDED
@@ -0,0 +1,160 @@
1
+ module CML
2
+ class Parser
3
+ attr_reader :doc, :cftags, :tags, :errors
4
+ BASE_TAGS_XPATH = ".//cml:*[not(ancestor::cml:checkboxes or ancestor::cml:ratings or ancestor::cml:select or ancestor::cml:radios or self::cml:gold or self::cml:instructions)]"
5
+
6
+ def initialize(content, opts = {})
7
+ @opts = opts.merge(:parser => self)
8
+ if content.is_a?(String)
9
+ #Because nokogiri is munging my CDATA sections, we parse it out ahead of time
10
+ @cdata = content.scan(/(<(script|style)[^>]*?>)(.*?)(<\/\2>)/m)
11
+ @doc = Parser.parse(content)
12
+ else
13
+ @cdata = []
14
+ @doc = content
15
+ end
16
+ #Pull all cml tags that aren't children
17
+ @cftags = @doc.xpath(BASE_TAGS_XPATH, "xmlns:cml"=>"http://crowdflower.com")
18
+ normalize if opts[:normalize]
19
+ @tags = @cftags.map do |t|
20
+ t = Parser.tag_class(t.name).new(t, @opts)
21
+ end.flatten
22
+ end
23
+
24
+ def self.parse(content)
25
+ #This sucks, we remove scripts, styles, and close non self closed tags
26
+ #We could potentially add CDATA clauses to them, but this is "easier"
27
+ xhtml = content.gsub(/(<(script|style)[^>]*?>)(.*?)(<\/\2>)/m, "\\1\\4").gsub(/(<(input|link|img|br|hr).*?)\/?>/,'\1/>') #base, basefont, area, meta
28
+ Nokogiri::XML("<root xmlns:cml=\"http://crowdflower.com\">#{xhtml}</root>")
29
+ end
30
+
31
+ def convert(opts = nil)
32
+ @opts.merge!(opts) if opts
33
+ cloned = @doc.dup
34
+ cloned.xpath(BASE_TAGS_XPATH, "xmlns:cml"=>"http://crowdflower.com").each_with_index do |t,i|
35
+ t.namespace = nil
36
+ t.replace(@tags[i].convert(opts))
37
+ end
38
+ cloned
39
+ end
40
+
41
+ def normalize
42
+ @cftags.each do |t|
43
+ if ["radios", "select"].include?(t.name)
44
+ child = t.name == "radios" ? "radio" : "option"
45
+ t.name = "checkboxes"
46
+ t.xpath("cml:#{child}").each { |c| c.name = "checkbox" }
47
+ elsif t.name == "meta"
48
+ t.name = "text"
49
+ end
50
+ end
51
+ end
52
+
53
+ def fields
54
+ @fields = {}
55
+ @tags.each do |g|
56
+ agg = g.attrs["aggregation"]
57
+ @fields[g.name] = agg.to_s if agg
58
+ end
59
+ @fields
60
+ end
61
+
62
+ def golds(rich = false, opts={})
63
+ unless @golds
64
+ @golds = @tags.map do |tag|
65
+ next unless gold = tag.gold?
66
+ Gold.new(tag, gold)
67
+ end.compact
68
+ end
69
+
70
+ if rich
71
+ golds = @golds
72
+ else
73
+ golds = rich_gold_to_hash( @golds )
74
+ if opts[:no_meta]
75
+ golds.reject! { |k,v| k =~ /^_/ }
76
+ end
77
+ end
78
+
79
+ golds
80
+ end
81
+
82
+ def rich_gold_to_hash(golds)
83
+ golds.inject({}) do |m, gold|
84
+ m.merge(gold.to_hash)
85
+ end
86
+ end
87
+
88
+ def valid?
89
+ @errors = []
90
+ if e = @doc.errors.select {|e| e.fatal? }.last
91
+ @errors << "Malformed CML (#{e.level}). #{e.message.chomp} on line #{e.line} column #{e.column}."
92
+ end
93
+ @tags.select {|t| t.validate? && t.name =~ /^\s*$/ }.each do |t|
94
+ @errors << "#{t.to_s.split("\n")[0]} does not have a label or name specified."
95
+ end
96
+ dupes = @tags.select do |tag|
97
+ tag.validate? && @tags.select {|t| t.name == tag.name}.length > 1
98
+ end
99
+ (dupes[1..-1] || []).each do |t|
100
+ @errors << "#{t.to_s.split("\n")[0]} has a duplicated name, please specify a unique name attribute."
101
+ end
102
+ @tags.each do |t|
103
+ next if !t.children || ["group","iterate"].include?(t.tag)
104
+ dupes = t.children.select do |child|
105
+ child.validate? && t.children.select {|c| c.value == child.value}.length > 1
106
+ end
107
+ (dupes[1..-1] || []).each do |c|
108
+ @errors << "#{c} a child of #{t.to_s.split("\n")[0]} has a duplicated value, please specify a unique value attribute."
109
+ end
110
+ end
111
+ @errors.length == 0
112
+ end
113
+
114
+ def wrap(content)
115
+ #This happens when variables are inside of href's
116
+ content = content.gsub(/%7B%7B/,'{{').gsub(/%7D%7D/,'}}')
117
+ @opts[:no_wrap] ? content : "<div class=\"cml#{" "+@opts[:class] if @opts[:class]}\" id=\"#{@opts[:prefix]}\">#{content}</div>"
118
+ end
119
+
120
+ def to_s
121
+ to_html
122
+ end
123
+
124
+ def to_cml
125
+ cml = @doc.to_xhtml.gsub(/<\/?root[^>]*?>|<\/?>/,'').gsub(/(<(input|link|img|br|hr).*?)\/?>/,'\1/>').gsub(/%7B%7B/,'{{').gsub(/%7D%7D/,'}}')
126
+ #Hack to ensure the next sub doesn't match...
127
+ @cdata.each do |matches|
128
+ cml.sub!(/(<(script|style)[^>]*>)<\/\2>/m) do |m|
129
+ "#{$1}#{matches[2].empty? ? " " : matches[2]}</#{$2}>"
130
+ end
131
+ end
132
+ cml
133
+ end
134
+
135
+ def to_html(opts = nil)
136
+ #We convert the entire document and strip root tags / rando empty tags ALA libxml 2.6.32
137
+ #We're also adding self closing tags
138
+ html = convert(opts).to_xhtml.gsub(/<\/?(root|group)[^>]*?>|<\/?>/,'').gsub(/(<(input|link|img|br|hr).*?)\/?>/,'\1/>').gsub(/%7B%7B/,'{{').gsub(/%7D%7D/,'}}')
139
+ #Let's re-insert that CDATA, tricky because scripts will sometimes contain single quotes...
140
+ @cdata.each do |matches|
141
+ html.sub!(/(<(script|style)[^>]*>)<\/\2>/m) do |m|
142
+ "#{$1}#{matches[2].empty? ? " " : matches[2]}</#{$2}>"
143
+ end
144
+ end
145
+ wrap(html)
146
+ end
147
+
148
+ def self.escape( string )
149
+ string.to_s.gsub( /&/, "&amp;" ).
150
+ gsub( /</, "&lt;" ).
151
+ gsub( />/, "&gt;" ).
152
+ gsub( /"/, "&quot;" )
153
+ end
154
+
155
+ #This takes the name of the tag and converts it to the appropriate tag class
156
+ def self.tag_class(name)
157
+ CML::TagClasses[name] || CML::Tags::Unknown
158
+ end
159
+ end
160
+ end