cml 1.4.2

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.
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