xmlhash 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/.autotest ADDED
@@ -0,0 +1,14 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'autotest/restart'
4
+
5
+ Autotest.add_hook :initialize do |at|
6
+ at.add_mapping(/.*\.c/) do |f, _|
7
+ at.files_matching(/test_.*rb$/)
8
+ end
9
+ end
10
+
11
+ Autotest.add_hook :run_command do |at|
12
+ system "rake clean compile"
13
+ end
14
+
data/History.txt ADDED
@@ -0,0 +1,22 @@
1
+ === 1.2.1 / 2012-03-24
2
+
3
+ * mark even more variables for ruby
4
+
5
+ === 1.2 / 2012-03-21
6
+
7
+ * be careful with ruby variables to avoid GC stress killing us
8
+
9
+ === 1.1 / 2012-03-19
10
+
11
+ * make it C code to avoid problems on sle11
12
+
13
+ === 1.0.1 / 2012-03-09
14
+
15
+ * Compile with ruby 1.9
16
+
17
+ === 1.0.0 / 2012-02-28
18
+
19
+ * 1 major enhancement
20
+
21
+ * Birthday!
22
+
data/Manifest.txt ADDED
@@ -0,0 +1,10 @@
1
+ .autotest
2
+ History.txt
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ ext/xmlhash/extconf.rb
7
+ ext/xmlhash/xmlhash.c
8
+ lib/xmlhash.rb
9
+ lib/xmlhash/xmlhash.so
10
+ test/test_xmlhash.rb
data/README.txt ADDED
@@ -0,0 +1,59 @@
1
+ = xmlhash
2
+
3
+ * https://github.com/coolo/xmlhash
4
+
5
+ == DESCRIPTION:
6
+
7
+ A small C module that wraps libxml2's xmlreader to parse a XML
8
+ string into a ruby hash
9
+
10
+ == FEATURES/PROBLEMS:
11
+
12
+ * only one function: parse(xml) -> hash
13
+
14
+ == SYNOPSIS:
15
+
16
+ ret = parse("<hello who='world'/>")
17
+ assert_equal ret, { 'who' => 'world' }
18
+
19
+ == REQUIREMENTS:
20
+
21
+ * libxml2 >= 2.6
22
+
23
+ == INSTALL:
24
+
25
+ * sudo gem install
26
+
27
+ == DEVELOPERS:
28
+
29
+ After checking out the source, run:
30
+
31
+ $ rake newb
32
+
33
+ This task will install any missing dependencies, run the tests/specs,
34
+ and generate the RDoc.
35
+
36
+ == LICENSE:
37
+
38
+ (The MIT License)
39
+
40
+ Copyright (c) 2012 Stephan Kulow
41
+
42
+ Permission is hereby granted, free of charge, to any person obtaining
43
+ a copy of this software and associated documentation files (the
44
+ 'Software'), to deal in the Software without restriction, including
45
+ without limitation the rights to use, copy, modify, merge, publish,
46
+ distribute, sublicense, and/or sell copies of the Software, and to
47
+ permit persons to whom the Software is furnished to do so, subject to
48
+ the following conditions:
49
+
50
+ The above copyright notice and this permission notice shall be
51
+ included in all copies or substantial portions of the Software.
52
+
53
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
54
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
55
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
56
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
57
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
58
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
59
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,19 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+ require 'rake/extensiontask'
6
+
7
+ Hoe.spec 'xmlhash' do
8
+ developer('Stephan Kulow', 'coolo@suse.com')
9
+ self.readme_file = 'README.txt'
10
+ self.spec_extras = { :extensions => ["ext/xmlhash/extconf.rb"] }
11
+ self.extra_dev_deps << ['rake-compiler', '>= 0']
12
+ Rake::ExtensionTask.new('xmlhash', spec) do |ext|
13
+ ext.lib_dir = File.join('lib', 'xmlhash')
14
+ end
15
+ end
16
+
17
+ Rake::Task[:test].prerequisites << :compile
18
+
19
+ # vim: syntax=ruby
@@ -0,0 +1,5 @@
1
+ require 'mkmf'
2
+ unless find_library('xml2', 'xmlAddID')
3
+ abort "xml2 is missing. please install libxml2"
4
+ end
5
+ create_makefile('xmlhash/xmlhash')
@@ -0,0 +1,190 @@
1
+ #include <assert.h>
2
+ #include <ruby.h>
3
+ #include <st.h>
4
+ #include <libxml/parser.h>
5
+ #include <libxml/xmlreader.h>
6
+
7
+ static VALUE m_current = Qnil;
8
+ static VALUE m_stack = Qnil;
9
+ static VALUE m_cstring = Qnil;
10
+ static VALUE m_result = Qnil;
11
+
12
+ void init_XmlhashParserData()
13
+ {
14
+ m_current = Qnil;
15
+ rb_ary_clear(m_stack);
16
+ rb_ary_clear(m_cstring);
17
+ }
18
+
19
+ void xml_hash_start_element(const xmlChar *name)
20
+ {
21
+ // needed for further attributes
22
+ m_current = rb_hash_new();
23
+ VALUE pair = rb_ary_new();
24
+ rb_ary_push(pair, rb_str_new2((const char*)name));
25
+ rb_ary_push(pair, m_current);
26
+ rb_ary_push(m_stack, pair);
27
+ rb_ary_clear(m_cstring);
28
+ }
29
+
30
+ void xml_hash_end_element(const xmlChar *name)
31
+ {
32
+ assert(m_stack != Qnil);
33
+ VALUE pair = rb_ary_pop(m_stack);
34
+ assert(pair != Qnil);
35
+ VALUE cname = rb_ary_entry(pair, 0);
36
+ VALUE chash = rb_ary_entry(pair, 1);
37
+ assert(!strcmp((const char*)name, RSTRING_PTR(cname)));
38
+
39
+ if (rb_obj_is_kind_of(chash, rb_cHash) && RHASH_SIZE(chash) == 0) {
40
+ // now check if the cstring array contains non-empty string
41
+ VALUE string = rb_ary_join(m_cstring, Qnil);
42
+ const char *string_ptr = RSTRING_PTR(string);
43
+ long string_len = RSTRING_LEN(string);
44
+ while (string_len > 0 && (string_ptr[0] == ' ' || string_ptr[0] == '\t' || string_ptr[0] == '\n')) {
45
+ string_ptr++;
46
+ string_len--;
47
+ }
48
+ while (string_len > 0 && (string_ptr[string_len-1] == ' ' || string_ptr[string_len-1] == '\t' || string_ptr[string_len-1] == '\n')) {
49
+ string_len--;
50
+ }
51
+ // avoid overwriting empty hash with empty string
52
+ if (string_len > 0)
53
+ chash = string;
54
+ }
55
+ if (RARRAY_LEN(m_stack) == 0) {
56
+ m_result = chash;
57
+ return;
58
+ }
59
+
60
+ pair = rb_ary_entry(m_stack, RARRAY_LEN(m_stack)-1);
61
+ //VALUE pname = rb_ary_entry(pair, 0);
62
+ VALUE phash = rb_ary_entry(pair, 1);
63
+
64
+ VALUE obj = rb_hash_aref(phash, cname);
65
+ if (obj != Qnil) {
66
+ if (rb_obj_is_kind_of(obj, rb_cArray)) {
67
+ rb_ary_push(obj, chash);
68
+ } else {
69
+ VALUE nobj = rb_ary_new();
70
+ rb_ary_push(nobj, obj);
71
+ rb_ary_push(nobj, chash);
72
+ rb_hash_aset(phash, cname, nobj);
73
+ }
74
+ } else {
75
+ // implement force_array
76
+ rb_hash_aset(phash, cname, chash);
77
+ }
78
+ }
79
+
80
+ void xml_hash_add_attribute(const xmlChar *name, const xmlChar *value)
81
+ {
82
+ assert(m_current != Qnil);
83
+ rb_hash_aset(m_current, rb_str_new2((const char*)name), rb_str_new2((const char*)value));
84
+ }
85
+
86
+ void xml_hash_add_text(const xmlChar *text)
87
+ {
88
+ rb_ary_push(m_cstring, rb_str_new2((const char*)text));
89
+ }
90
+
91
+ void processAttribute(xmlTextReaderPtr reader)
92
+ {
93
+ const xmlChar *name = xmlTextReaderConstName(reader);
94
+ assert(xmlTextReaderNodeType(reader) == XML_READER_TYPE_ATTRIBUTE);
95
+ xml_hash_add_attribute(name, xmlTextReaderConstValue(reader));
96
+ }
97
+
98
+ void processNode(xmlTextReaderPtr reader)
99
+ {
100
+ const xmlChar *name;
101
+ const xmlChar *value;
102
+ int nodetype;
103
+
104
+ name = xmlTextReaderConstName(reader);
105
+ value = xmlTextReaderConstValue(reader);
106
+
107
+ nodetype = xmlTextReaderNodeType(reader);
108
+
109
+ if (nodetype == XML_READER_TYPE_END_ELEMENT) {
110
+ xml_hash_end_element(name);
111
+ assert(value == NULL);
112
+ return;
113
+ }
114
+
115
+ if (nodetype == XML_READER_TYPE_ELEMENT) {
116
+ xml_hash_start_element(name);
117
+ assert(value == NULL);
118
+
119
+ if (xmlTextReaderMoveToFirstAttribute(reader) == 1)
120
+ {
121
+ processAttribute(reader);
122
+ while (xmlTextReaderMoveToNextAttribute(reader) == 1)
123
+ processAttribute(reader);
124
+
125
+ xmlTextReaderMoveToElement(reader);
126
+ }
127
+
128
+ if (xmlTextReaderIsEmptyElement(reader) == 1) {
129
+ xml_hash_end_element(name);
130
+ }
131
+ return;
132
+ }
133
+
134
+ if (nodetype == XML_READER_TYPE_TEXT ||
135
+ nodetype == XML_READER_TYPE_WHITESPACE ||
136
+ nodetype == XML_READER_TYPE_SIGNIFICANT_WHITESPACE)
137
+ {
138
+ xml_hash_add_text(value);
139
+ return;
140
+ }
141
+
142
+ printf("%d %s\n",
143
+ nodetype,
144
+ name
145
+ );
146
+
147
+ }
148
+
149
+ static VALUE parse_xml_hash(VALUE self, VALUE rb_xml)
150
+ {
151
+ char *data;
152
+ xmlTextReaderPtr reader;
153
+ int ret;
154
+
155
+ Check_Type(rb_xml, T_STRING);
156
+
157
+ data = (char*)calloc(RSTRING_LEN(rb_xml), sizeof(char));
158
+ memcpy(data, StringValuePtr(rb_xml), RSTRING_LEN(rb_xml));
159
+
160
+ reader = xmlReaderForMemory(data, RSTRING_LEN(rb_xml),
161
+ NULL, NULL, XML_PARSE_NOENT);
162
+ init_XmlhashParserData();
163
+ if (reader != NULL) {
164
+ ret = xmlTextReaderRead(reader);
165
+ while (ret == 1) {
166
+ processNode(reader);
167
+ ret = xmlTextReaderRead(reader);
168
+ }
169
+ xmlFreeTextReader(reader);
170
+ if (ret != 0) {
171
+ printf("%s : failed to parse\n", data);
172
+ }
173
+ }
174
+
175
+ free(data);
176
+ return m_result;
177
+ }
178
+
179
+ void Init_xmlhash()
180
+ {
181
+ LIBXML_TEST_VERSION
182
+ VALUE mXmlhash = rb_define_module("Xmlhash");
183
+ rb_define_singleton_method(mXmlhash, "parse", &parse_xml_hash, 1);
184
+ m_stack = rb_ary_new();
185
+ rb_global_variable(&m_stack);
186
+ m_cstring = rb_ary_new();
187
+ rb_global_variable(&m_cstring);
188
+ rb_global_variable(&m_result);
189
+ rb_global_variable(&m_current);
190
+ }
Binary file
data/lib/xmlhash.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'xmlhash/xmlhash'
2
+
3
+ module Xmlhash
4
+ VERSION = '1.2.1'
5
+ end
@@ -0,0 +1,92 @@
1
+ require "test/unit"
2
+ require "xmlhash"
3
+ require 'json'
4
+
5
+ class TestXmlhash < Test::Unit::TestCase
6
+ def test_xml
7
+ xml = <<eos
8
+ <request id="93651">
9
+ <action type="submit">
10
+ <source project="server:dns" package="pdns" rev="65"/>
11
+ <target project="openSUSE:Factory" package="pdns"/>
12
+ </action>
13
+ <state name="revoked" who="coolo" when="2011-12-19T13:20:50">
14
+ <comment/>
15
+ </state>
16
+ <review state="accepted" by_group="legal-auto" who="licensedigger" when="2011-11-25T15:09:55">
17
+ <comment>{"approve": "preliminary, version number changed"} &lt;!-- {
18
+ "dest": {
19
+ "ldb": {
20
+ "review": "done",
21
+ "rpm_license": "GPLv2+",
22
+ "status": "production",
23
+ "version": "3.0.rc1"
24
+ },
25
+ "license": "GPLv2+",
26
+ "version": "2.9.22"
27
+ },
28
+ "hint": [
29
+ "src('3.0') and dest('2.9.22') version numbers differ"
30
+ ],
31
+ "plugin": "0.35",
32
+ "src": {
33
+ "auto-co": "/api.opensuse.org/server:dns/pdns%3.0%r65",
34
+ "license": "GPLv2+",
35
+ "rev": "65",
36
+ "version": "3.0"
37
+ }
38
+ } --&gt;</comment>
39
+ </review>
40
+ <review state="new" by_group="factory-auto"/>
41
+ <history name="review" who="coolo" when="2011-11-25T15:02:53"/>
42
+ <history name="declined" who="coolo" when="2011-11-25T16:17:30">
43
+ <comment>please make sure to wait before these depencencies are in openSUSE:Factory: libopendbx-devel, libopendbx1, libopendbxplus1, opendbx-backend-pgsql</comment>
44
+ </history>
45
+ <description>update and factory fix (forwarded request 86230 from -miska-)</description>
46
+ </request>
47
+ eos
48
+
49
+ rubyoutput = {"history"=>
50
+ [ {"name"=>"review", "when"=>"2011-11-25T15:02:53", "who"=>"coolo"},
51
+ {"comment"=>"please make sure to wait before these depencencies are in openSUSE:Factory: libopendbx-devel, libopendbx1, libopendbxplus1, opendbx-backend-pgsql",
52
+ "name"=>"declined", "when"=>"2011-11-25T16:17:30", "who"=>"coolo"}
53
+ ],
54
+ "review"=>
55
+ [
56
+ {"comment"=>"{\"approve\": \"preliminary, version number changed\"} <!-- {\n \"dest\": {\n \"ldb\": {\n \"review\": \"done\", \n \"rpm_license\": \"GPLv2+\", \n \"status\": \"production\", \n \"version\": \"3.0.rc1\"\n }, \n \"license\": \"GPLv2+\", \n \"version\": \"2.9.22\"\n }, \n \"hint\": [\n \"src('3.0') and dest('2.9.22') version numbers differ\"\n ], \n \"plugin\": \"0.35\", \n \"src\": {\n \"auto-co\": \"/api.opensuse.org/server:dns/pdns%3.0%r65\", \n \"license\": \"GPLv2+\", \n \"rev\": \"65\", \n \"version\": \"3.0\"\n }\n} -->", "by_group"=>"legal-auto", "when"=>"2011-11-25T15:09:55", "who"=>"licensedigger", "state"=>"accepted"}, {"by_group"=>"factory-auto", "state"=>"new"}], "action"=>{"type"=>"submit", "target"=>{"project"=>"openSUSE:Factory", "package"=>"pdns"}, "source"=>{"rev"=>"65", "project"=>"server:dns", "package"=>"pdns"}}, "id"=>"93651", "description"=>"update and factory fix (forwarded request 86230 from -miska-)", "state"=>{"comment"=>{}, "name"=>"revoked", "when"=>"2011-12-19T13:20:50", "who"=>"coolo"}}
57
+
58
+ 1000.times {
59
+ ret = Xmlhash.parse(xml)
60
+ GC.start
61
+ assert_equal ret, rubyoutput
62
+ }
63
+ 10000.times {
64
+ ret = Xmlhash.parse(xml)
65
+ assert_equal ret, rubyoutput
66
+ }
67
+
68
+ end
69
+
70
+ def test_entry
71
+ xml = <<eos
72
+ <?xml version='1.0' encoding='UTF-8'?>
73
+ <directory>
74
+ <entry name="Apache"/>
75
+ <entry name="Apache:APR_Pool_Debug"/>
76
+ <entry name="Apache:MirrorBrain"/>
77
+ <entry name="Apache:Modules"/>
78
+ </directory>
79
+ eos
80
+
81
+ rubyoutput = {"entry"=>
82
+ [{"name"=>"Apache"},
83
+ {"name"=>"Apache:APR_Pool_Debug"},
84
+ {"name"=>"Apache:MirrorBrain"},
85
+ {"name"=>"Apache:Modules"}]}
86
+
87
+ ret = Xmlhash.parse(xml)
88
+
89
+ assert_equal ret, rubyoutput
90
+ end
91
+
92
+ end
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: xmlhash
3
+ version: !ruby/object:Gem::Version
4
+ version: !binary |-
5
+ MS4yLjE=
6
+ prerelease:
7
+ platform: ruby
8
+ authors:
9
+ - Stephan Kulow
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2012-03-24 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rubyforge
17
+ requirement: &6853540 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: 2.0.4
23
+ type: :development
24
+ prerelease: false
25
+ version_requirements: *6853540
26
+ - !ruby/object:Gem::Dependency
27
+ name: rake-compiler
28
+ requirement: &6868560 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: *6868560
37
+ - !ruby/object:Gem::Dependency
38
+ name: hoe
39
+ requirement: &6864360 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: 2.6.1
45
+ type: :development
46
+ prerelease: false
47
+ version_requirements: *6864360
48
+ description: !binary |-
49
+ QSBzbWFsbCBDIG1vZHVsZSB0aGF0IHdyYXBzIGxpYnhtbDIncyB4bWxyZWFk
50
+ ZXIgdG8gcGFyc2UgYSBYTUwKc3RyaW5nIGludG8gYSBydWJ5IGhhc2g=
51
+ email:
52
+ - coolo@suse.com
53
+ executables: []
54
+ extensions:
55
+ - ext/xmlhash/extconf.rb
56
+ extra_rdoc_files:
57
+ - !binary |-
58
+ SGlzdG9yeS50eHQ=
59
+ - !binary |-
60
+ TWFuaWZlc3QudHh0
61
+ - !binary |-
62
+ UkVBRE1FLnR4dA==
63
+ files:
64
+ - !binary |-
65
+ LmF1dG90ZXN0
66
+ - !binary |-
67
+ SGlzdG9yeS50eHQ=
68
+ - !binary |-
69
+ TWFuaWZlc3QudHh0
70
+ - !binary |-
71
+ UkVBRE1FLnR4dA==
72
+ - !binary |-
73
+ UmFrZWZpbGU=
74
+ - !binary |-
75
+ ZXh0L3htbGhhc2gvZXh0Y29uZi5yYg==
76
+ - !binary |-
77
+ ZXh0L3htbGhhc2gveG1saGFzaC5j
78
+ - !binary |-
79
+ bGliL3htbGhhc2gucmI=
80
+ - !binary |-
81
+ bGliL3htbGhhc2gveG1saGFzaC5zbw==
82
+ - !binary |-
83
+ dGVzdC90ZXN0X3htbGhhc2gucmI=
84
+ homepage: !binary |-
85
+ aHR0cHM6Ly9naXRodWIuY29tL2Nvb2xvL3htbGhhc2g=
86
+ licenses: []
87
+ post_install_message:
88
+ rdoc_options:
89
+ - --main
90
+ - README.txt
91
+ require_paths:
92
+ - lib
93
+ required_ruby_version: !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ none: false
101
+ requirements:
102
+ - - ! '>='
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ requirements: []
106
+ rubyforge_project: xmlhash
107
+ rubygems_version: 1.8.11
108
+ signing_key:
109
+ specification_version: 3
110
+ summary: !binary |-
111
+ QSBzbWFsbCBDIG1vZHVsZSB0aGF0IHdyYXBzIGxpYnhtbDIncyB4bWxyZWFk
112
+ ZXIgdG8gcGFyc2UgYSBYTUwgc3RyaW5nIGludG8gYSBydWJ5IGhhc2g=
113
+ test_files:
114
+ - test/test_xmlhash.rb