xbuilder 0.9

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "blankslate"
4
+ gem "libxml-ruby", ">= 2.3.3"
5
+
6
+ group :development do
7
+ gem "rdoc", "~> 3.12"
8
+ gem "bundler", "~> 1.0.0"
9
+ gem "jeweler", "~> 1.8.3"
10
+ gem "simplecov", require: false
11
+
12
+ # For performance comparison:
13
+ gem "nokogiri"
14
+ gem "builder"
15
+ end
@@ -0,0 +1,96 @@
1
+ = Xbuilder
2
+
3
+ Xbuilder is an API-compatible Builder implementation using <tt>libxml</tt>. Also it's almost <b>2 times faster</b> than Builder and Nokogiri (see <tt>test/performance.rb</tt> output for details).
4
+
5
+ Feel free to open an issue on any incompatibility with Builder API. The list of known incompatibilities is given below.
6
+
7
+ == Usage
8
+
9
+ Xbuilder supports almost all of the Builder's features. Here is a small example:
10
+
11
+ xml = Xbuilder.new(indent: 2)
12
+ xml.node attr: 1 do |xml| #=> <node attr="1">
13
+ xml.ns :child, attr: 2 #=> <ns:child attr="2"/>
14
+ end #=> </node>
15
+
16
+ == With Rails
17
+
18
+ # some.xml.xbuilder
19
+ xml.node do |xml|
20
+ # The first argument is your partial name, the second is a hash of locals.
21
+ xml.partial!("partial", value: 1)
22
+ end
23
+
24
+ # _partial.xml.xbuilder
25
+ xml.child(value)
26
+
27
+ will render:
28
+
29
+ <?xml version="1.0" encoding="UTF-8"?>
30
+ <node><child>1</child></node>
31
+
32
+ == Notes
33
+
34
+ * Child blocks with 0 arguments are not supported. So, instead of:
35
+
36
+ xml.node do
37
+ xml.node
38
+ end
39
+
40
+ write:
41
+
42
+ xml.node do |xml|
43
+ xml.node
44
+ end
45
+
46
+ * XML instruction will be added automatically.
47
+
48
+ xml = Xbuilder.new(encoding: "ISO-8859-1")
49
+ xml.node "test"
50
+ puts xml.target!
51
+
52
+ will print:
53
+
54
+ <?xml version="1.0" encoding="ISO-8859-1"?>
55
+ <node>test</node>
56
+
57
+ * <tt>libxml-ruby</tt> gem has an issue ({#46}[https://github.com/xml4r/libxml-ruby/pull/46]) with node content encoding:
58
+
59
+ xml.esc "escaped & text"
60
+ #=> without fix: <esc>escaped </esc>
61
+ #=> with fix: <esc>escaped &amp; text</esc>
62
+
63
+ For now I've used temporary workaround (see <tt>Xbuilder#__escape</tt>).
64
+
65
+
66
+ == Incompatibilities With Builder
67
+
68
+ * Builder-like unescaped symbol attributes are not supported.
69
+ It's currently a restriction of <tt>libxml</tt> (see <tt>xmlAttrSerializeContent</tt> implementation).
70
+
71
+ xml.node(attr: :"&esc") #=> <node attr="&amp;esc"/>
72
+
73
+ * Custom XML processing instructions are not supported yet. Instruction will be auto-generated depending on
74
+ encoding and version provided.
75
+
76
+ * XML entity declarations are not yet supported.
77
+
78
+ == Contributing to Xbuilder
79
+
80
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
81
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
82
+ * Fork the project.
83
+ * Start a feature/bugfix branch.
84
+ * Commit and push until you are happy with your contribution.
85
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
86
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
87
+
88
+ == Authors
89
+
90
+ Builder gem was created by Jim Weirich.
91
+
92
+ Xbuilder gem was created by Nikita Afanasenko. The project is hosted on Github: http://github.com/nikitug/xbuilder.
93
+
94
+ Copyright © 2012 Nikita Afanasenko, released under the MIT license.
95
+
96
+ * http://www.opensource.org/licenses/MIT
@@ -0,0 +1,44 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "xbuilder"
18
+ gem.version = "0.9"
19
+ gem.homepage = "http://github.com/nikitug/xbuilder"
20
+ gem.summary = %[API-compatible Builder implementation using libxml.]
21
+ gem.email = "nikita@afanasenko.name"
22
+ gem.authors = ["Nikita Afanasenko"]
23
+ # dependencies defined in Gemfile
24
+ end
25
+ Jeweler::RubygemsDotOrgTasks.new
26
+
27
+ require 'rake/testtask'
28
+ Rake::TestTask.new(:test) do |test|
29
+ test.libs << 'lib' << 'test'
30
+ test.pattern = 'test/**/test_*.rb'
31
+ test.verbose = true
32
+ end
33
+
34
+ task :default => :test
35
+
36
+ require 'rdoc/task'
37
+ Rake::RDocTask.new do |rdoc|
38
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
39
+
40
+ rdoc.rdoc_dir = 'rdoc'
41
+ rdoc.title = "Xbuilder #{version}"
42
+ rdoc.rdoc_files.include('README*')
43
+ rdoc.rdoc_files.include('lib/**/*.rb')
44
+ end
@@ -0,0 +1,185 @@
1
+ require 'blankslate'
2
+ require 'libxml'
3
+
4
+ # == Usage
5
+ #
6
+ # Xbuilder supports almost all of the Builder's features. Here is a small example:
7
+ #
8
+ # xml = Xbuilder.new(indent: 2)
9
+ # xml.node attr: 1 do |xml| #=> <node attr="1">
10
+ # xml.ns :child, attr: 2 #=> <ns:child attr="2"/>
11
+ # end #=> </node>
12
+ class Xbuilder < BlankSlate
13
+ XML = ::LibXML::XML #:nodoc:
14
+ define_method(:__class, find_hidden_method(:class))
15
+
16
+ # Create an XML builder. Available options are:
17
+ #
18
+ # :root::
19
+ # Root element. All nodes created by the builder will be attached to it.
20
+ #
21
+ # :encoding::
22
+ # Document encoding, e.g. "UTF-8".
23
+ # Will be used in XML instruction: <tt><?xml version="1.0" encoding="UTF-8"?></tt>
24
+ #
25
+ # Builder compatibility options:
26
+ # :indent:: Number of spaces used for indentation. Default is 0.
27
+ # :margin:: Amount of initial indentation (specified in levels, not spaces).
28
+ def initialize(options = {})
29
+ if options[:target]
30
+ ::Kernel.raise ::ArgumentError, "':target' option is not supported."
31
+ end
32
+
33
+ @indent = options[:indent].to_i
34
+ @margin = options[:margin].to_i
35
+ @root = options[:root] || XML::Document.new
36
+ @encoding = options[:encoding] || "UTF-8"
37
+ end
38
+
39
+ # Append a tag with the method's name to the output.
40
+ # xml.node { |xml| xml.child } #=> <node><child/></node>
41
+ def method_missing(name, *args, &block)
42
+ name = "#{name}:#{args.shift}" if args.first.kind_of?(::Symbol)
43
+ node = XML::Node.new(name.to_s)
44
+ text = nil
45
+
46
+ args.each do |arg|
47
+ case arg
48
+ when ::Hash
49
+ arg.each do |key, val|
50
+ k = key.to_s
51
+ v = val.to_s
52
+ node[k] = v
53
+ end
54
+ else
55
+ text ||= ''
56
+ text << arg.to_s
57
+ end
58
+ end
59
+
60
+ if block && text
61
+ ::Kernel.raise ::ArgumentError, "Cannot mix a text argument with a block"
62
+ end
63
+
64
+ # FIXME `__escape` is a temp solution till bugfix release.
65
+ # https://github.com/xml4r/libxml-ruby/pull/46
66
+ node.content = __escape(text) if text
67
+
68
+ if block
69
+ unless block.arity > 0
70
+ ::Kernel.raise ::ArgumentError, "Provide at least 1 block argument: `xml.node { |xml| xml.child }'"
71
+ end
72
+ block.call(__new_instance(root: node))
73
+ end
74
+
75
+ __append_node(node)
76
+ end
77
+
78
+ # Append a tag to the output. The first argument is a tag name.
79
+ # The rest of arguments are the same as <tt>method_missing</tt> ones.
80
+ # xml.tag!("node") { |xml| xml.tag!("child") } #=> <node><child/></node>
81
+ def tag!(name, *args, &block)
82
+ method_missing(name, *args, &block)
83
+ end
84
+
85
+ # Append text to the output. Escape by default.
86
+ #
87
+ # xml.node { xml.text!("escaped & text") } #=> <node>escaped &amp; text</node>
88
+ def text!(text, escape = true)
89
+ __ensure_no_block(::Kernel.block_given?)
90
+ node = XML::Node.new_text(text)
91
+ node.output_escaping = escape
92
+ __append_node(node)
93
+ end
94
+
95
+ # Append text to the output. Do not escape by default.
96
+ # xml.node { xml << "unescaped & text" } #=> <node>unescaped & text</node>
97
+ def <<(text, escape = false)
98
+ __ensure_no_block(::Kernel.block_given?)
99
+ text!(text, escape)
100
+ end
101
+
102
+ # Returns the target XML string.
103
+ def target!
104
+ # FIXME Temp solution for encoding constant lookup.
105
+ # (till bugfix release https://github.com/xml4r/libxml-ruby/pull/45 to be published)
106
+ const_name = @encoding.upcase.gsub!("-", "_")
107
+ encoding = XML::Encoding.const_get(const_name)
108
+
109
+ XML.indent_tree_output = (@indent > 0)
110
+ XML.default_tree_indent_string = (" " * @indent)
111
+
112
+ @root.to_s(encoding: encoding, indent: XML.indent_tree_output).tap do |xml|
113
+ if @margin > 0
114
+ xml.gsub!(/^/, (" " * @indent) * @margin)
115
+ end
116
+ end
117
+ end
118
+
119
+ # Insert comment node.
120
+ def comment!(comment_text)
121
+ __ensure_no_block(::Kernel.block_given?)
122
+ node = XML::Node.new_comment(comment_text)
123
+ __append_node(node)
124
+ end
125
+
126
+ # XML declarations are not yet supported.
127
+ def declare!(inst, *args, &block)
128
+ __warn("XML declarations are not yet supported. Pull requests are welcome!")
129
+ end
130
+
131
+ # Custom XML instructions are not supported.
132
+ # Left here for Builder API compatibility.
133
+ def instruct!(*args)
134
+ # TODO should we switch XML instruction off if `instruct!` is not called?
135
+ __warn("Custom XML instructions are not supported")
136
+ end
137
+
138
+ # Insert CDATA node.
139
+ def cdata!(text)
140
+ __ensure_no_block(::Kernel.block_given?)
141
+ node = XML::Node.new_cdata(text)
142
+ __append_node(node)
143
+ end
144
+
145
+ private
146
+
147
+ def __escape(text)
148
+ text.tap do |t|
149
+ t.gsub!(/&(?![a-zA-Z]+;)|<|>|'|"/) do |match|
150
+ case match
151
+ when "&" then "&amp;"
152
+ when "<" then "&lt;"
153
+ when ">" then "&gt;"
154
+ when "'" then "&apos;"
155
+ when '"' then "&quot;"
156
+ end
157
+ end
158
+ end
159
+ end
160
+
161
+ def __append_node(node)
162
+ if @root.kind_of?(XML::Document)
163
+ @root.root = node
164
+ else
165
+ @root << node
166
+ end
167
+ end
168
+
169
+ def __new_instance(root)
170
+ __class.new(root)
171
+ end
172
+
173
+ def __ensure_no_block(given)
174
+ if given
175
+ ::Kernel.raise ArgumentError.new("Blocks are not allowed on XML instructions")
176
+ end
177
+ end
178
+
179
+ def __warn(msg)
180
+ ::Kernel.warn("Xbuilder WARNING: #{msg}")
181
+ end
182
+
183
+ end
184
+
185
+ require "xbuilder/template" if defined?(ActionView::Template)
@@ -0,0 +1,49 @@
1
+ class XbuilderTemplate < Xbuilder
2
+ define_method(:__instance_variable_set, find_hidden_method(:instance_variable_set))
3
+
4
+ def self.encode(context)
5
+ xml = self.new
6
+ xml.__instance_variable_set(:@context, context)
7
+ yield xml
8
+ xml.target!
9
+ end
10
+
11
+ def partial!(options, locals = {})
12
+ case options
13
+ when Hash
14
+ options[:locals] ||= {}
15
+ options[:locals].merge!(xml: self)
16
+ @context.render(options)
17
+ else
18
+ @context.render(options, locals.merge(xml: self))
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def __new_instance(*args)
25
+ super.tap do |xml|
26
+ xml.__instance_variable_set(:@context, @context)
27
+ end
28
+ end
29
+
30
+ end
31
+
32
+ class XbuilderHandler
33
+ cattr_accessor :default_format
34
+ self.default_format = Mime::XML
35
+
36
+ def self.call(template)
37
+ %[
38
+ if defined?(xml)
39
+ #{template.source}
40
+ else
41
+ XbuilderTemplate.encode(self) do |xml|
42
+ #{template.source}
43
+ end
44
+ end
45
+ ]
46
+ end
47
+ end
48
+
49
+ ActionView::Template.register_template_handler :xbuilder, XbilderHandler
@@ -0,0 +1,22 @@
1
+ require 'simplecov'
2
+ SimpleCov.start
3
+
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'test/unit'
13
+
14
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
15
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
16
+ require 'xbuilder'
17
+
18
+ class Test::Unit::TestCase
19
+ def without_instruct(xml)
20
+ xml.gsub(/^\s*<\?xml[^>]*>\n/, "").rstrip
21
+ end
22
+ end
@@ -0,0 +1,71 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ require 'xbuilder'
3
+ require 'builder'
4
+ require 'nokogiri'
5
+ require 'benchmark'
6
+
7
+ xml_block = proc do |xml|
8
+ xml.root do |xml|
9
+ 10.times do
10
+ xml.node "text"
11
+ xml.node2 do |xml|
12
+ xml.test args: 1, args2: 4 do |xml|
13
+ xml.child arg: "text" do |xml|
14
+ xml.one_more arg: 1, arg: "2123"
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ repeat = 200
23
+ xmls = []
24
+ Benchmark.bm do |x|
25
+ x.report "builder" do
26
+ repeat.times do
27
+ xml = Builder::XmlMarkup.new
28
+ xml.instruct!
29
+ xml_block.call(xml)
30
+ xmls << xml.target!
31
+ end
32
+ end
33
+ x.report "nokogiri" do
34
+ repeat.times do
35
+ builder = Nokogiri::XML::Builder.new(encoding: "UTF-8") do |xml|
36
+ xml_block.call(xml)
37
+ end
38
+ xmls << builder.to_xml(indent: 0)
39
+ end
40
+ end
41
+ x.report "xbuilder" do
42
+ repeat.times do
43
+ xml = Xbuilder.new
44
+ xml_block.call(xml)
45
+ xmls << xml.target!
46
+ end
47
+ end
48
+ end
49
+
50
+ # Enshure all xmls are the same.
51
+ xml = xmls.first.split.join
52
+ xmls.each do |xml2|
53
+ if xml != xml2.split.join
54
+ puts xml
55
+ puts xml2
56
+ exit
57
+ end
58
+ end
59
+
60
+ require 'ruby-prof'
61
+
62
+ RubyProf.start
63
+ repeat.times do
64
+ xml = Xbuilder.new
65
+ xml_block.call(xml)
66
+ xmls << xml.target!
67
+ end
68
+ result = RubyProf.stop
69
+
70
+ printer = RubyProf::FlatPrinter.new(result)
71
+ printer.print(STDOUT)
@@ -0,0 +1,384 @@
1
+ require 'helper'
2
+
3
+ #--
4
+ # To enshure compatibility, a significant part of this test was taken from Builder gem.
5
+ #
6
+ # Portions copyright 2004 by Jim Weirich (jim@weirichhouse.org).
7
+ # Portions copyright 2005 by Sam Ruby (rubys@intertwingly.net).
8
+ # Portions copyright 2012 by Nikita Afanasenko (nikita@afanasenko.name).
9
+ # All rights reserved.
10
+
11
+ # Permission is granted for use, copying, modification, distribution,
12
+ # and distribution of modified versions of this work as long as the
13
+ # above copyright notice is included.
14
+ #++
15
+
16
+ class TestXbuilder < Test::Unit::TestCase
17
+ def setup
18
+ @xml = Xbuilder.new
19
+ end
20
+
21
+ def test_simple
22
+ @xml.simple
23
+ assert_equal "<simple/>", without_instruct(@xml.target!)
24
+ end
25
+
26
+ def test_value
27
+ @xml.value("hi")
28
+ assert_equal "<value>hi</value>", without_instruct(@xml.target!)
29
+ end
30
+
31
+ def test_nested
32
+ @xml.outer { |x| x.inner("x") }
33
+ assert_equal "<outer><inner>x</inner></outer>", without_instruct(@xml.target!)
34
+ end
35
+
36
+ def test_attributes
37
+ @xml.ref(:id => 12)
38
+ assert_equal %{<ref id="12"/>}, without_instruct(@xml.target!)
39
+ end
40
+
41
+ def test_string_attributes_are_quoted_by_default
42
+ @xml.ref(:id => "H&R")
43
+ end
44
+
45
+ def test_symbol_attributes_are_unquoted_by_default
46
+ skip "Unquoted symbol attributes are not supported."
47
+ @xml.ref(:id => :"H&amp;R")
48
+ assert_equal %{<ref id="H&amp;R"/>}, without_instruct(@xml.target!)
49
+ end
50
+
51
+ def test_attributes_quoted_can_be_turned_on
52
+ @xml.ref(:id => "<H&R \"block\">")
53
+ assert_equal %{<ref id="&lt;H&amp;R &quot;block&quot;&gt;"/>}, without_instruct(@xml.target!)
54
+ end
55
+
56
+ def test_multiple_attributes
57
+ @xml.ref(:id => 12, :name => "bill")
58
+ assert_match %r{^<ref( id="12"| name="bill"){2}/>$}, without_instruct(@xml.target!)
59
+ end
60
+
61
+ def test_attributes_with_text
62
+ @xml.a("link", :href=>"http://onestepback.org")
63
+ assert_equal %{<a href="http://onestepback.org">link</a>}, without_instruct(@xml.target!)
64
+ end
65
+
66
+ def test_complex
67
+ @xml.body(:bg=>"#ffffff") { |x|
68
+ x.title("T", :style=>"red")
69
+ }
70
+ assert_equal %{<body bg="#ffffff"><title style="red">T</title></body>}, without_instruct(@xml.target!)
71
+ end
72
+
73
+ def test_funky_symbol
74
+ @xml.tag!("non-ruby-token", :id=>1) { |x| x.ok }
75
+ assert_equal %{<non-ruby-token id="1"><ok/></non-ruby-token>}, without_instruct(@xml.target!)
76
+ end
77
+
78
+ def test_tag_can_handle_private_method
79
+ @xml.tag!("loop", :id=>1) { |x| x.ok }
80
+ assert_equal %{<loop id="1"><ok/></loop>}, without_instruct(@xml.target!)
81
+ end
82
+
83
+ def test_no_explicit_marker
84
+ @xml.p { |x| x.b("HI") }
85
+ assert_equal "<p><b>HI</b></p>", without_instruct(@xml.target!)
86
+ end
87
+
88
+ def test_reference_local_vars
89
+ n = 3
90
+ @xml.ol { |x| n.times { x.li(n) } }
91
+ assert_equal "<ol><li>3</li><li>3</li><li>3</li></ol>", without_instruct(@xml.target!)
92
+ end
93
+
94
+ def test_reference_methods
95
+ @xml.title { |x| x.a { |x| x.b(name) } }
96
+ assert_equal "<title><a><b>bob</b></a></title>", without_instruct(@xml.target!)
97
+ end
98
+
99
+ def test_append_text
100
+ @xml.p { |x| x.br; x.text! "HI" }
101
+ assert_equal "<p><br/>HI</p>", without_instruct(@xml.target!)
102
+ end
103
+
104
+ def test_ambiguous_markup
105
+ ex = assert_raise(ArgumentError) {
106
+ @xml.h1("data1") { b }
107
+ }
108
+ assert_match /\btext\b/, ex.message
109
+ assert_match /\bblock\b/, ex.message
110
+ end
111
+
112
+ def test_capitalized_method
113
+ @xml.P { |x| x.B("hi"); x.BR(); x.EM { |x| x.text! "world" } }
114
+ assert_equal "<P><B>hi</B><BR/><EM>world</EM></P>", without_instruct(@xml.target!)
115
+ end
116
+
117
+ def test_escaping
118
+ @xml.div { |x| x.text! "<hi>"; x.em("H&R Block") }
119
+ assert_equal %{<div>&lt;hi&gt;<em>H&amp;R Block</em></div>}, without_instruct(@xml.target!)
120
+ end
121
+
122
+ def test_non_escaping
123
+ @xml.div("ns:xml"=>"xml") { |x| x << "<h&i>"; x.em("H&R Block") }
124
+ assert_equal %{<div ns:xml="xml"><h&i><em>H&amp;R Block</em></div>}, without_instruct(@xml.target!)
125
+ end
126
+
127
+ def test_content_non_escaping
128
+ @xml.div { |x| x << "<h&i>" }
129
+ assert_equal %{<div><h&i></div>}, without_instruct(@xml.target!)
130
+ end
131
+
132
+ def test_content_escaping
133
+ @xml.div { |x| x.text!("<hi>") }
134
+ assert_equal %{<div>&lt;hi&gt;</div>}, without_instruct(@xml.target!)
135
+ end
136
+
137
+ def test_return_value
138
+ str = @xml.x("men").to_s
139
+ assert_equal without_instruct(@xml.target!), str
140
+ end
141
+
142
+ def test_stacked_builders
143
+ ex = assert_raise(ArgumentError) { Xbuilder.new( :target => @xml ) }
144
+ assert_match /target/, ex.message
145
+ end
146
+
147
+ def name
148
+ "bob"
149
+ end
150
+ end
151
+
152
+ class TestAttributeEscaping < Test::Unit::TestCase
153
+ def setup
154
+ @xml = Xbuilder.new
155
+ end
156
+
157
+ def test_element_gt
158
+ @xml.title('1<2')
159
+ assert_equal '<title>1&lt;2</title>', without_instruct(@xml.target!)
160
+ end
161
+
162
+ def test_element_amp
163
+ @xml.title('AT&T')
164
+ assert_equal '<title>AT&amp;T</title>', without_instruct(@xml.target!)
165
+ end
166
+
167
+ def test_element_amp2
168
+ @xml.title('&amp;')
169
+ assert_equal '<title>&amp;</title>', without_instruct(@xml.target!)
170
+ end
171
+
172
+ def test_attr_less
173
+ @xml.a(:title => '2>1')
174
+ assert_equal '<a title="2&gt;1"/>', without_instruct(@xml.target!)
175
+ end
176
+
177
+ def test_attr_amp
178
+ @xml.a(:title => 'AT&T')
179
+ assert_equal '<a title="AT&amp;T"/>', without_instruct(@xml.target!)
180
+ end
181
+
182
+ def test_attr_quot
183
+ @xml.a(:title => '"x"')
184
+ assert_equal '<a title="&quot;x&quot;"/>', without_instruct(@xml.target!)
185
+ end
186
+
187
+ end
188
+
189
+ class TestNameSpaces < Test::Unit::TestCase
190
+ def setup
191
+ @xml = Xbuilder.new(:indent=>2)
192
+ end
193
+
194
+ def test_simple_name_spaces
195
+ @xml.rdf :RDF
196
+ assert_equal "<rdf:RDF/>", without_instruct(@xml.target!)
197
+ end
198
+ end
199
+
200
+ class TestSpecialMarkup < Test::Unit::TestCase
201
+ def setup
202
+ @xml = Xbuilder.new(:indent=>2)
203
+ end
204
+
205
+ def test_comment
206
+ @xml.comment!("COMMENT")
207
+ assert_equal "<!--COMMENT-->", without_instruct(@xml.target!)
208
+ end
209
+
210
+ def test_indented_comment
211
+ @xml.p { |x| x.comment! "OK" }
212
+ assert_equal "<p>\n <!--OK-->\n</p>", without_instruct(@xml.target!)
213
+ end
214
+
215
+ def test_instruct
216
+ skip "Custom instructs are not supported yet."
217
+ @xml.instruct! :abc, :version=>"0.9"
218
+ assert_equal "<?abc version=\"0.9\"?>\n", without_instruct(@xml.target!)
219
+ end
220
+
221
+ def test_indented_instruct
222
+ skip "Custom instructs are not supported yet."
223
+ @xml.p { @xml.instruct! :xml }
224
+ assert_match %r{<p>\n <\?xml version="1.0" encoding="UTF-8"\?>\n</p>\n},
225
+ without_instruct(@xml.target!)
226
+ end
227
+
228
+ def test_instruct_without_attributes
229
+ skip "Custom instructs are not supported yet."
230
+ @xml.instruct! :zz
231
+ assert_equal "<?zz?>\n", without_instruct(@xml.target!)
232
+ end
233
+
234
+ def test_xml_instruct
235
+ #@xml.instruct!
236
+ assert_match /^<\?xml version="1.0" encoding="UTF-8"\?>$/, @xml.target!
237
+ end
238
+
239
+ def test_xml_instruct_with_overrides
240
+ skip "Custom instructs are not supported yet."
241
+ # FIXME shoud deal with encodings
242
+ @xml.instruct! :xml, :encoding=>"UCS-2"
243
+ assert_match /^<\?xml version="1.0" encoding="UCS-2"\?>$/, without_instruct(@xml.target!)
244
+ end
245
+
246
+ def test_xml_instruct_with_standalong
247
+ skip "Custom instructs are not supported yet."
248
+ @xml.instruct! :xml, :encoding=>"UCS-2", :standalone=>"yes"
249
+ assert_match /^<\?xml version="1.0" encoding="UCS-2" standalone="yes"\?>$/, without_instruct(@xml.target!)
250
+ end
251
+
252
+ def test_no_blocks
253
+ assert_raise(ArgumentError) do
254
+ @xml.cdata!("test") { |x| x.hi }
255
+ end
256
+ assert_raise(ArgumentError) do
257
+ @xml.comment!(:element) { |x| x.hi }
258
+ end
259
+ end
260
+
261
+ def test_block_arity_check
262
+ assert_raise(ArgumentError) do
263
+ @xml.node { x.hi }
264
+ end
265
+ end
266
+
267
+ def test_cdata
268
+ @xml.cdata!("TEST")
269
+ assert_equal "<![CDATA[TEST]]>", without_instruct(@xml.target!)
270
+ end
271
+
272
+ def test_cdata_with_ampersand
273
+ @xml.cdata!("TEST&CHECK")
274
+ assert_equal "<![CDATA[TEST&CHECK]]>", without_instruct(@xml.target!)
275
+ end
276
+ end
277
+
278
+ class TestIndentedXmlMarkup < Test::Unit::TestCase
279
+ def setup
280
+ @xml = Xbuilder.new(:indent=>2)
281
+ end
282
+
283
+ def test_one_level
284
+ @xml.ol { |x| x.li "text" }
285
+ assert_equal "<ol>\n <li>text</li>\n</ol>", without_instruct(@xml.target!)
286
+ end
287
+
288
+ def test_two_levels
289
+ @xml.p { |x|
290
+ x.ol { |x| x.li "text" }
291
+ x.br
292
+ }
293
+ assert_equal "<p>\n <ol>\n <li>text</li>\n </ol>\n <br/>\n</p>", without_instruct(@xml.target!)
294
+ end
295
+
296
+ def test_initial_level
297
+ @xml = Xbuilder.new(:indent=>2, :margin=>4)
298
+ @xml.name { |x| x.first("Jim") }
299
+ assert_equal " <name>\n <first>Jim</first>\n </name>", without_instruct(@xml.target!)
300
+ end
301
+ end
302
+
303
+ # FIXME should it be supported?
304
+ #
305
+ #class TestUtfMarkup < Test::Unit::TestCase
306
+ #if ! String.method_defined?(:encode)
307
+ #def setup
308
+ #@old_kcode = $KCODE
309
+ #end
310
+
311
+ #def teardown
312
+ #$KCODE = @old_kcode
313
+ #end
314
+
315
+ #def test_use_entities_if_no_encoding_is_given_and_kcode_is_none
316
+ #$KCODE = 'NONE'
317
+ #xml = Xbuilder.new
318
+ #xml.p("\xE2\x80\x99")
319
+ #assert_match(%r(<p>&#8217;</p>), xml.target!) #
320
+ #end
321
+
322
+ #def test_use_entities_if_encoding_is_utf_but_kcode_is_not
323
+ #$KCODE = 'NONE'
324
+ #xml = Xbuilder.new
325
+ #xml.instruct!(:xml, :encoding => 'UTF-8')
326
+ #xml.p("\xE2\x80\x99")
327
+ #assert_match(%r(<p>&#8217;</p>), xml.target!) #
328
+ #end
329
+ #else
330
+ ## change in behavior. As there is no $KCODE anymore, the default
331
+ ## moves from "does not understand utf-8" to "supports utf-8".
332
+
333
+ #def test_use_entities_if_no_encoding_is_given_and_kcode_is_none
334
+ #xml = Xbuilder.new
335
+ #xml.p("\xE2\x80\x99")
336
+ #assert_match("<p>\u2019</p>", xml.target!) #
337
+ #end
338
+
339
+ #def test_use_entities_if_encoding_is_utf_but_kcode_is_not
340
+ #xml = Xbuilder.new
341
+ #xml.instruct!(:xml, :encoding => 'UTF-8')
342
+ #xml.p("\xE2\x80\x99")
343
+ #assert_match("<p>\u2019</p>", xml.target!) #
344
+ #end
345
+ #end
346
+
347
+ #def encode string, encoding
348
+ #if !String.method_defined?(:encode)
349
+ #$KCODE = encoding
350
+ #string
351
+ #elsif encoding == 'UTF8'
352
+ #string.force_encoding('UTF-8')
353
+ #else
354
+ #string
355
+ #end
356
+ #end
357
+
358
+ #def test_use_entities_if_kcode_is_utf_but_encoding_is_something_else
359
+ #xml = Xbuilder.new
360
+ #xml.instruct!(:xml, :encoding => 'UTF-16')
361
+ #xml.p(encode("\xE2\x80\x99", 'UTF8'))
362
+ #assert_match(%r(<p>&#8217;</p>), xml.target!) #
363
+ #end
364
+
365
+ #def test_use_utf8_if_encoding_defaults_and_kcode_is_utf8
366
+ #xml = Xbuilder.new
367
+ #xml.p(encode("\xE2\x80\x99",'UTF8'))
368
+ #assert_equal encode("<p>\xE2\x80\x99</p>",'UTF8'), xml.target!
369
+ #end
370
+
371
+ #def test_use_utf8_if_both_encoding_and_kcode_are_utf8
372
+ #xml = Xbuilder.new
373
+ #xml.instruct!(:xml, :encoding => 'UTF-8')
374
+ #xml.p(encode("\xE2\x80\x99",'UTF8'))
375
+ #assert_match encode("<p>\xE2\x80\x99</p>",'UTF8'), xml.target!
376
+ #end
377
+
378
+ #def test_use_utf8_if_both_encoding_and_kcode_are_utf8_with_lowercase
379
+ #xml = Xbuilder.new
380
+ #xml.instruct!(:xml, :encoding => 'utf-8')
381
+ #xml.p(encode("\xE2\x80\x99",'UTF8'))
382
+ #assert_match encode("<p>\xE2\x80\x99</p>",'UTF8'), xml.target!
383
+ #end
384
+ #end
@@ -0,0 +1,66 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
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 = "xbuilder"
8
+ s.version = "0.9"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Nikita Afanasenko"]
12
+ s.date = "2012-08-26"
13
+ s.email = "nikita@afanasenko.name"
14
+ s.extra_rdoc_files = [
15
+ "README.rdoc"
16
+ ]
17
+ s.files = [
18
+ "Gemfile",
19
+ "README.rdoc",
20
+ "Rakefile",
21
+ "lib/xbuilder.rb",
22
+ "lib/xbuilder_template.rb",
23
+ "test/helper.rb",
24
+ "test/performance.rb",
25
+ "test/test_xbuilder.rb",
26
+ "xbuilder.gemspec"
27
+ ]
28
+ s.homepage = "http://github.com/nikitug/xbuilder"
29
+ s.require_paths = ["lib"]
30
+ s.rubygems_version = "1.8.15"
31
+ s.summary = "API-compatible Builder implementation using libxml."
32
+
33
+ if s.respond_to? :specification_version then
34
+ s.specification_version = 3
35
+
36
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
37
+ s.add_runtime_dependency(%q<blankslate>, [">= 0"])
38
+ s.add_runtime_dependency(%q<libxml-ruby>, [">= 2.3.3"])
39
+ s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
40
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
41
+ s.add_development_dependency(%q<jeweler>, ["~> 1.8.3"])
42
+ s.add_development_dependency(%q<simplecov>, [">= 0"])
43
+ s.add_development_dependency(%q<nokogiri>, [">= 0"])
44
+ s.add_development_dependency(%q<builder>, [">= 0"])
45
+ else
46
+ s.add_dependency(%q<blankslate>, [">= 0"])
47
+ s.add_dependency(%q<libxml-ruby>, [">= 2.3.3"])
48
+ s.add_dependency(%q<rdoc>, ["~> 3.12"])
49
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
50
+ s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
51
+ s.add_dependency(%q<simplecov>, [">= 0"])
52
+ s.add_dependency(%q<nokogiri>, [">= 0"])
53
+ s.add_dependency(%q<builder>, [">= 0"])
54
+ end
55
+ else
56
+ s.add_dependency(%q<blankslate>, [">= 0"])
57
+ s.add_dependency(%q<libxml-ruby>, [">= 2.3.3"])
58
+ s.add_dependency(%q<rdoc>, ["~> 3.12"])
59
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
60
+ s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
61
+ s.add_dependency(%q<simplecov>, [">= 0"])
62
+ s.add_dependency(%q<nokogiri>, [">= 0"])
63
+ s.add_dependency(%q<builder>, [">= 0"])
64
+ end
65
+ end
66
+
metadata ADDED
@@ -0,0 +1,145 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: xbuilder
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.9'
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Nikita Afanasenko
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-08-26 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: blankslate
16
+ requirement: &70319835213640 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70319835213640
25
+ - !ruby/object:Gem::Dependency
26
+ name: libxml-ruby
27
+ requirement: &70319835224860 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: 2.3.3
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70319835224860
36
+ - !ruby/object:Gem::Dependency
37
+ name: rdoc
38
+ requirement: &70319835220600 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: '3.12'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70319835220600
47
+ - !ruby/object:Gem::Dependency
48
+ name: bundler
49
+ requirement: &70319835218740 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 1.0.0
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *70319835218740
58
+ - !ruby/object:Gem::Dependency
59
+ name: jeweler
60
+ requirement: &70319835238360 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ~>
64
+ - !ruby/object:Gem::Version
65
+ version: 1.8.3
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *70319835238360
69
+ - !ruby/object:Gem::Dependency
70
+ name: simplecov
71
+ requirement: &70319835236620 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: *70319835236620
80
+ - !ruby/object:Gem::Dependency
81
+ name: nokogiri
82
+ requirement: &70319835235820 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: *70319835235820
91
+ - !ruby/object:Gem::Dependency
92
+ name: builder
93
+ requirement: &70319835234380 !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ type: :development
100
+ prerelease: false
101
+ version_requirements: *70319835234380
102
+ description:
103
+ email: nikita@afanasenko.name
104
+ executables: []
105
+ extensions: []
106
+ extra_rdoc_files:
107
+ - README.rdoc
108
+ files:
109
+ - Gemfile
110
+ - README.rdoc
111
+ - Rakefile
112
+ - lib/xbuilder.rb
113
+ - lib/xbuilder_template.rb
114
+ - test/helper.rb
115
+ - test/performance.rb
116
+ - test/test_xbuilder.rb
117
+ - xbuilder.gemspec
118
+ homepage: http://github.com/nikitug/xbuilder
119
+ licenses: []
120
+ post_install_message:
121
+ rdoc_options: []
122
+ require_paths:
123
+ - lib
124
+ required_ruby_version: !ruby/object:Gem::Requirement
125
+ none: false
126
+ requirements:
127
+ - - ! '>='
128
+ - !ruby/object:Gem::Version
129
+ version: '0'
130
+ segments:
131
+ - 0
132
+ hash: -2434364586004529730
133
+ required_rubygems_version: !ruby/object:Gem::Requirement
134
+ none: false
135
+ requirements:
136
+ - - ! '>='
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ requirements: []
140
+ rubyforge_project:
141
+ rubygems_version: 1.8.15
142
+ signing_key:
143
+ specification_version: 3
144
+ summary: API-compatible Builder implementation using libxml.
145
+ test_files: []