fast-xml 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d5c3255eaacfe69128a428f67ab670d680e05bf2
4
+ data.tar.gz: 65b1e5898db7b64bd1b58ec0b23fedb1c3692a1c
5
+ SHA512:
6
+ metadata.gz: 8a290a9676f388f342c63e2fdef2d2445f2db91df0d58b13cb64515f019c6595ce18cfef7466c2b932de527dca1230ef690280ec1ef7598ce6a25e9038a8d727
7
+ data.tar.gz: b0c95264bf47245e3d53e28844cee4fffe468b365098a45881f0869be4c01f9533ba9420527fc24c7ef8359303a3ee0779efae06d1a18073bde7336fc104a684
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Yuriy Ustushenko
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,164 @@
1
+ # FastXML
2
+
3
+ Fast Ruby Hash to XML and XML to Ruby Hash converter written in pure C.
4
+
5
+ [![Build Status](https://secure.travis-ci.org/yoreek/fastxml.png?branch=master)](http://travis-ci.org/yoreek/fastxml)
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'fastxml'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install fast-xml
22
+
23
+ ## Release Notes
24
+
25
+ See [CHANGELOG.md](CHANGELOG.md)
26
+
27
+ ## Usage
28
+
29
+ ```ruby
30
+ require 'fastxml'
31
+
32
+ # convert hash to XML
33
+ FastXML.hash2xml({ tag1: { tag2: 'content' } }, indent: 2)
34
+ # =>
35
+ # <?xml version="1.0" encoding="utf-8"?>
36
+ # <root>
37
+ # <tag1>
38
+ # <tag2>content</tag2>
39
+ # </tag1>
40
+ # </root>
41
+
42
+ # use enumerator
43
+ FastXML.hash2xml({ enumerator: 3.times }, indent: 2)
44
+ # =>
45
+ # <?xml version="1.0" encoding="utf-8"?>
46
+ # <root>
47
+ # <enumerator>0</enumerator>
48
+ # <enumerator>1</enumerator>
49
+ # <enumerator>2</enumerator>
50
+ # </root>
51
+
52
+ # output to file handle
53
+ # fh = StringIO.new
54
+ FastXML.hash2xml({ enumerator: 3.times }, indent: 2, output: fh)
55
+ fh.string
56
+ # =>
57
+ # <?xml version="1.0" encoding="utf-8"?>
58
+ # <root>
59
+ # <enumerator>0</enumerator>
60
+ # <enumerator>1</enumerator>
61
+ # <enumerator>2</enumerator>
62
+ # </root>
63
+
64
+ ```
65
+
66
+ ## Options
67
+
68
+ The following options are available to pass to FastXML.hash2xml(hash, options = {}).
69
+
70
+ * **:root** => 'root'
71
+ * Root node name.
72
+
73
+ * **:version** => '1.0'
74
+ * XML document version
75
+
76
+ * **:encoding** => 'utf-8'
77
+ * XML input/output encoding
78
+
79
+ * **:indent** => 0
80
+ * if indent great than "0", XML output should be indented according to
81
+ its hierarchic structure. This value determines the number of
82
+ spaces.
83
+
84
+ * if indent is "0", XML output will all be on one line.
85
+
86
+ * **:output** => nil
87
+ * XML output method
88
+
89
+ * if output is nil, XML document dumped into string.
90
+
91
+ * if output is filehandle, XML document writes directly to a filehandle or a
92
+ stream.
93
+
94
+ * **:canonical** => false
95
+ * if canonical is "true", converter will be write hashes sorted by key.
96
+
97
+ * if canonical is "false", order of the element will be pseudo-randomly.
98
+
99
+ * **:use_attr** => false
100
+ * if use_attr is "true", converter will be use the attributes.
101
+
102
+ * if use_attr is "fale", converter will be use tags only.
103
+
104
+ * **:content** => nil
105
+ * if defined that the key name for the text content(used only if
106
+ use_attr = true).
107
+
108
+ * **:force_array** => nil
109
+ * This option is similar to "ForceArray" from [XMl::Simple module]:
110
+ (https://metacpan.org/pod/XML::Simple#ForceArray-1-in---important).
111
+
112
+ * **:force_content** => nil
113
+ * This option is similar to "ForceContent" from [XMl::Simple module]:
114
+ (https://metacpan.org/pod/XML::Simple#ForceContent-1-in---seldom-used).
115
+
116
+ * **:merge_text** => false
117
+ * Setting this option to "true" will cause merge adjacent text nodes.
118
+
119
+ * **:xml_decl** => true
120
+ * if xml_decl is "true", output will start with the XML declaration
121
+ '<?xml version="1.0" encoding="utf-8"?>'.
122
+
123
+ * if xml_decl is "false", XML declaration will not be output.
124
+
125
+ * **:trim** => false
126
+ * Trim leading and trailing whitespace from text nodes.
127
+
128
+ * **:utf8** => true
129
+ * Turn on utf8 flag for strings if enabled.
130
+
131
+ * **:max_depth** => 1024
132
+ * Maximum recursion depth.
133
+
134
+ * **:buf_size** => 4096
135
+ * Buffer size for reading end encoding data.
136
+
137
+ * **:keep_root** => false
138
+ * Keep root element.
139
+
140
+ ## Configuration
141
+ ```
142
+ FastXML.configure do |config|
143
+ config.trim = true
144
+ end
145
+ ```
146
+
147
+ ## Benchmarks
148
+
149
+ Performance benchmark in comparison with some popular gems:
150
+
151
+ ```
152
+ Converting Hash to XML:
153
+ user system total real
154
+ activesupport(rexml) 11.020000 0.000000 11.020000 ( 11.058084)
155
+ activesupport(libxml) 10.690000 0.000000 10.690000 ( 10.731521)
156
+ activesupport(nokogiri) 10.730000 0.010000 10.740000 ( 10.769866)
157
+ xmlsimple 1.470000 0.000000 1.470000 ( 1.477457)
158
+ fastxml 0.010000 0.000000 0.010000 ( 0.018434)
159
+ ```
160
+
161
+ ## License
162
+
163
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
164
+
@@ -0,0 +1,17 @@
1
+ require 'mkmf'
2
+
3
+ extension_name = 'fastxml'
4
+ dir_config(extension_name)
5
+
6
+ puts ">>>>> Creating Makefile for #{RUBY_DESCRIPTION} <<<<<"
7
+
8
+ OPTS = Hash[
9
+ ARGV.map { |a| a =~ /^--with-(.+)(?:=(.*))?$/ ? [$1.to_sym, $2 || true] : nil }.compact
10
+ ].freeze
11
+
12
+ if OPTS[:debug]
13
+ $CPPFLAGS += ' -g -Wall -Werror -Wextra -pedantic -std=c99 -DWITH_DEBUG -O0'
14
+ $CPPFLAGS += ' -DWITH_TRACE' if OPTS[:trace]
15
+ end
16
+
17
+ create_makefile(extension_name)
@@ -0,0 +1,67 @@
1
+ #include "fastxml.h"
2
+
3
+ #include "xh_config.h"
4
+ #include "xh_core.h"
5
+
6
+ void Init_fastxml();
7
+
8
+ VALUE xh_module;
9
+ VALUE xh_parse_error_class;
10
+ ID xh_id_next;
11
+
12
+ typedef struct {
13
+ int argc;
14
+ VALUE *argv;
15
+ xh_h2x_ctx_t *ctx;
16
+ } xh_h2x_arg_t;
17
+
18
+ static VALUE
19
+ hash2xml_exec(VALUE a) {
20
+ xh_h2x_arg_t *arg = (xh_h2x_arg_t *) a;
21
+
22
+ xh_h2x_init_ctx(arg->ctx, arg->argc, arg->argv);
23
+
24
+ return xh_h2x(arg->ctx);
25
+ }
26
+
27
+ static VALUE
28
+ hash2xml(int argc, VALUE *argv, VALUE self) {
29
+ xh_h2x_ctx_t ctx;
30
+ VALUE result;
31
+ int state;
32
+ xh_h2x_arg_t arg;
33
+
34
+ arg.argc = argc;
35
+ arg.argv = argv;
36
+ arg.ctx = &ctx;
37
+
38
+ result = rb_protect(hash2xml_exec, (VALUE) &arg, &state);
39
+
40
+ if (state) {
41
+ xh_h2x_destroy_ctx(&ctx);
42
+ rb_exc_raise(rb_errinfo());
43
+ }
44
+
45
+ if (ctx.opts.output != Qnil) result = Qnil;
46
+
47
+ xh_h2x_destroy_ctx(&ctx);
48
+
49
+ return result;
50
+ }
51
+
52
+ static VALUE
53
+ xml2hash(int argc, VALUE *argv, VALUE self) {
54
+ VALUE obj = Qnil;
55
+
56
+ return obj;
57
+ }
58
+
59
+ void Init_fastxml(void) {
60
+ xh_module = rb_define_module("FastXML");
61
+ xh_parse_error_class = rb_const_get_at(xh_module, rb_intern("ParseError"));
62
+
63
+ rb_define_module_function(xh_module, "hash2xml", hash2xml, -1);
64
+ rb_define_module_function(xh_module, "xml2hash", xml2hash, -1);
65
+
66
+ xh_id_next = rb_intern("next");
67
+ }
@@ -0,0 +1,14 @@
1
+ #ifndef __FASTXML_H__
2
+ #define __FASTXML_H__
3
+
4
+ #if defined(__cplusplus)
5
+ extern "C" {
6
+ #endif
7
+
8
+ #include "ruby.h"
9
+
10
+ #if defined(__cplusplus)
11
+ } /* extern "C" { */
12
+ #endif
13
+
14
+ #endif /* __FASTXML_H__ */
data/ext/fastxml/xh.c ADDED
@@ -0,0 +1,338 @@
1
+ #include "xh_config.h"
2
+ #include "xh_core.h"
3
+
4
+ xh_bool_t
5
+ xh_init_opts(xh_opts_t *opts)
6
+ {
7
+ xh_char_t method[XH_PARAM_LEN];
8
+
9
+ XH_PARAM_READ_INIT
10
+
11
+ /* native options */
12
+ XH_PARAM_READ_STRING (opts->root, "@root");
13
+ XH_PARAM_READ_STRING (opts->version, "@version");
14
+ XH_PARAM_READ_STRING (opts->encoding, "@encoding");
15
+ XH_PARAM_READ_INT (opts->indent, "@indent");
16
+ XH_PARAM_READ_BOOL (opts->canonical, "@canonical");
17
+ XH_PARAM_READ_STRING (opts->content, "@content");
18
+ XH_PARAM_READ_BOOL (opts->utf8, "@utf8");
19
+ XH_PARAM_READ_BOOL (opts->xml_decl, "@xml_decl");
20
+ XH_PARAM_READ_BOOL (opts->keep_root, "@keep_root");
21
+ #ifdef XH_HAVE_DOM
22
+ XH_PARAM_READ_BOOL (opts->doc, "@doc");
23
+ #endif
24
+ XH_PARAM_READ_INT (opts->max_depth, "@max_depth");
25
+ XH_PARAM_READ_INT (opts->buf_size, "@buf_size");
26
+ XH_PARAM_READ_PATTERN(opts->force_array, "@force_array");
27
+ XH_PARAM_READ_BOOL (opts->force_content, "@force_content");
28
+ XH_PARAM_READ_BOOL (opts->merge_text, "@merge_text");
29
+
30
+ /* XML::Hash::LX options */
31
+ XH_PARAM_READ_STRING (opts->attr, "@attr");
32
+ opts->attr_len = xh_strlen(opts->attr);
33
+ XH_PARAM_READ_STRING (opts->text, "@text");
34
+ XH_PARAM_READ_BOOL (opts->trim, "@trim");
35
+ XH_PARAM_READ_STRING (opts->cdata, "@cdata");
36
+ XH_PARAM_READ_STRING (opts->comm, "@comm");
37
+
38
+ /* method */
39
+ XH_PARAM_READ_STRING (method, "@method");
40
+ XH_PARAM_READ_BOOL (opts->use_attr, "@use_attr");
41
+ if (xh_strcmp(method, XH_CHAR_CAST "LX") == 0) {
42
+ opts->method = XH_METHOD_LX;
43
+ }
44
+ else {
45
+ opts->method = XH_METHOD_NATIVE;
46
+ }
47
+
48
+ /* output, NULL - to string */
49
+ XH_PARAM_READ_REF (opts->output, "@output");
50
+
51
+ return TRUE;
52
+ }
53
+
54
+ xh_opts_t *
55
+ xh_create_opts(void)
56
+ {
57
+ xh_opts_t *opts;
58
+
59
+ if ((opts = malloc(sizeof(xh_opts_t))) == NULL) {
60
+ return NULL;
61
+ }
62
+ memset(opts, 0, sizeof(xh_opts_t));
63
+
64
+ if (! xh_init_opts(opts)) {
65
+ xh_destroy_opts(opts);
66
+ return NULL;
67
+ }
68
+
69
+ return opts;
70
+ }
71
+
72
+ void
73
+ xh_destroy_opts(xh_opts_t *opts)
74
+ {
75
+ /* nothing */
76
+ }
77
+
78
+ void
79
+ xh_copy_opts(xh_opts_t *dst, xh_opts_t *src)
80
+ {
81
+ memcpy(dst, src, sizeof(xh_opts_t));
82
+ }
83
+
84
+ static int
85
+ xh_parse_arg(VALUE key, VALUE value, VALUE ctx)
86
+ {
87
+ xh_opts_t *opts = (xh_opts_t *) ctx;
88
+ xh_char_t *keyptr, *valueptr;
89
+ size_t keylen, valuelen;
90
+
91
+ if (SYMBOL_P(key)) {
92
+ key = rb_sym2str(key);
93
+ }
94
+
95
+ keyptr = XH_CHAR_CAST RSTRING_PTR(key);
96
+ keylen = RSTRING_LEN(key);
97
+
98
+ switch (keylen) {
99
+ case 2:
100
+ if (xh_str_equal2(keyptr, 'c', 'b')) {
101
+ VALUE v = rb_inspect(value);
102
+ rb_warn("cb: %s\n", StringValueCStr(v));
103
+ opts->cb = xh_param_assign_cb(value);
104
+ break;
105
+ }
106
+ goto error;
107
+ #ifdef XH_HAVE_DOM
108
+ case 3:
109
+ if (xh_str_equal3(keyptr, 'd', 'o', 'c')) {
110
+ opts->doc = xh_param_assign_bool(value);
111
+ break;
112
+ }
113
+ goto error;
114
+ #endif
115
+ case 4:
116
+ if (xh_str_equal4(keyptr, 'a', 't', 't', 'r')) {
117
+ xh_param_assign_string(opts->attr, value);
118
+ if (opts->attr[0] == '\0') {
119
+ opts->attr_len = 0;
120
+ }
121
+ else {
122
+ opts->attr_len = xh_strlen(opts->attr);
123
+ }
124
+ break;
125
+ }
126
+ if (xh_str_equal4(keyptr, 'c', 'o', 'm', 'm')) {
127
+ xh_param_assign_string(opts->comm, value);
128
+ break;
129
+ }
130
+ if (xh_str_equal4(keyptr, 'r', 'o', 'o', 't')) {
131
+ xh_param_assign_string(opts->root, value);
132
+ break;
133
+ }
134
+ if (xh_str_equal4(keyptr, 't', 'r', 'i', 'm')) {
135
+ opts->trim = xh_param_assign_bool(value);
136
+ break;
137
+ }
138
+ if (xh_str_equal4(keyptr, 't', 'e', 'x', 't')) {
139
+ xh_param_assign_string(opts->text, value);
140
+ break;
141
+ }
142
+ if (xh_str_equal4(keyptr, 'u', 't', 'f', '8')) {
143
+ opts->utf8 = xh_param_assign_bool(value);
144
+ break;
145
+ }
146
+ goto error;
147
+ case 5:
148
+ if (xh_str_equal5(keyptr, 'c', 'd', 'a', 't', 'a')) {
149
+ xh_param_assign_string(opts->cdata, value);
150
+ break;
151
+ }
152
+ goto error;
153
+ case 6:
154
+ if (xh_str_equal6(keyptr, 'i', 'n', 'd', 'e', 'n', 't')) {
155
+ xh_param_assign_int(keyptr, &opts->indent, value);
156
+ break;
157
+ }
158
+ if (xh_str_equal6(keyptr, 'm', 'e', 't', 'h', 'o', 'd')) {
159
+ if (value == Qnil) {
160
+ rb_raise(rb_eArgError, "Parameter '%s' is undefined", StringValueCStr(key));
161
+ }
162
+ valueptr = XH_CHAR_CAST RSTRING_PTR(value);
163
+ valuelen = RSTRING_LEN(value);
164
+ switch (valuelen) {
165
+ case 6:
166
+ if (xh_str_equal6(valueptr, 'N', 'A', 'T', 'I', 'V', 'E')) {
167
+ opts->method = XH_METHOD_NATIVE;
168
+ break;
169
+ }
170
+ goto error_value;
171
+ case 2:
172
+ if (valueptr[0] == 'L' && valueptr[1] == 'X') {
173
+ opts->method = XH_METHOD_LX;
174
+ break;
175
+ }
176
+ goto error_value;
177
+ default:
178
+ goto error_value;
179
+ }
180
+ break;
181
+ }
182
+ if (xh_str_equal6(keyptr, 'o', 'u', 't', 'p', 'u', 't')) {
183
+ if ( RTEST(value) ) {
184
+ opts->output = value;
185
+ }
186
+ else {
187
+ opts->output = Qnil;
188
+ }
189
+ break;
190
+ }
191
+ if (xh_str_equal6(keyptr, 'f', 'i', 'l', 't', 'e', 'r')) {
192
+ xh_param_assign_filter(&opts->filter, value);
193
+ break;
194
+ }
195
+ goto error;
196
+ case 7:
197
+ if (xh_str_equal7(keyptr, 'c', 'o', 'n', 't', 'e', 'n', 't')) {
198
+ xh_param_assign_string(opts->content, value);
199
+ break;
200
+ }
201
+ if (xh_str_equal7(keyptr, 'v', 'e', 'r', 's', 'i', 'o', 'n')) {
202
+ xh_param_assign_string(opts->version, value);
203
+ break;
204
+ }
205
+ goto error;
206
+ case 8:
207
+ if (xh_str_equal8(keyptr, 'e', 'n', 'c', 'o', 'd', 'i', 'n', 'g')) {
208
+ xh_param_assign_string(opts->encoding, value);
209
+ break;
210
+ }
211
+ if (xh_str_equal8(keyptr, 'u', 's', 'e', '_', 'a', 't', 't', 'r')) {
212
+ opts->use_attr = xh_param_assign_bool(value);
213
+ break;
214
+ }
215
+ if (xh_str_equal8(keyptr, 'x', 'm', 'l', '_', 'd', 'e', 'c', 'l')) {
216
+ opts->xml_decl = xh_param_assign_bool(value);
217
+ break;
218
+ }
219
+ if (xh_str_equal8(keyptr, 'b', 'u', 'f', '_', 's', 'i', 'z', 'e')) {
220
+ xh_param_assign_int(keyptr, &opts->buf_size, value);
221
+ break;
222
+ }
223
+ goto error;
224
+ case 9:
225
+ if (xh_str_equal9(keyptr, 'c', 'a', 'n', 'o', 'n', 'i', 'c', 'a', 'l')) {
226
+ opts->canonical = xh_param_assign_bool(value);
227
+ break;
228
+ }
229
+ if (xh_str_equal9(keyptr, 'm', 'a', 'x', '_', 'd', 'e', 'p', 't', 'h')) {
230
+ xh_param_assign_int(keyptr, &opts->max_depth, value);
231
+ break;
232
+ }
233
+ if (xh_str_equal9(keyptr, 'k', 'e', 'e', 'p', '_', 'r', 'o', 'o', 't')) {
234
+ opts->keep_root = xh_param_assign_bool(value);
235
+ break;
236
+ }
237
+ goto error;
238
+ case 10:
239
+ if (xh_str_equal10(keyptr, 'm', 'e', 'r', 'g', 'e', '_', 't', 'e', 'x', 't')) {
240
+ opts->merge_text = xh_param_assign_bool(value);
241
+ break;
242
+ }
243
+ case 11:
244
+ if (xh_str_equal11(keyptr, 'f', 'o', 'r', 'c', 'e', '_', 'a', 'r', 'r', 'a', 'y')) {
245
+ xh_param_assign_pattern(&opts->force_array, value);
246
+ break;
247
+ }
248
+ case 13:
249
+ if (xh_str_equal13(keyptr, 'f', 'o', 'r', 'c', 'e', '_', 'c', 'o', 'n', 't', 'e', 'n', 't')) {
250
+ opts->force_content = xh_param_assign_bool(value);
251
+ break;
252
+ }
253
+ default:
254
+ goto error;
255
+ }
256
+
257
+ return ST_CONTINUE;
258
+
259
+ error_value:
260
+ rb_raise(rb_eArgError, "Invalid parameter value for '%s': %s", StringValueCStr(key), StringValueCStr(value));
261
+
262
+ error:
263
+ rb_raise(rb_eArgError, "Invalid parameter '%s'", StringValueCStr(key));
264
+ }
265
+
266
+ void
267
+ xh_parse_args(xh_opts_t *opts, xh_int_t *nparam, xh_int_t argc, VALUE *argv)
268
+ {
269
+ VALUE hash;
270
+
271
+ if (*nparam >= argc)
272
+ return;
273
+
274
+ hash = argv[*nparam];
275
+ if (rb_cHash != rb_obj_class(hash))
276
+ rb_raise(rb_eArgError, "Parameter is not a hash");
277
+
278
+ (*nparam)++;
279
+
280
+ rb_hash_foreach(hash, xh_parse_arg, (VALUE) opts);
281
+ }
282
+
283
+ void *
284
+ xh_get_obj_param(xh_int_t *nparam, xh_int_t argc, VALUE *argv, const char *class)
285
+ {
286
+ void *obj = NULL;
287
+ /*
288
+ SV *param;
289
+
290
+ if (*nparam >= items)
291
+ rb_raise(rb_eArgError, "Invalid parameters");
292
+
293
+ param = ST(*nparam);
294
+ if ( sv_derived_from(param, class) ) {
295
+ if ( sv_isobject(param) ) {
296
+ // reference to object
297
+ IV tmp = SvIV((SV *) SvRV(param));
298
+ obj = INT2PTR(xh_opts_t *, tmp);
299
+ }
300
+ (*nparam)++;
301
+ }
302
+ */
303
+ return obj;
304
+ }
305
+
306
+ VALUE
307
+ xh_get_hash_param(xh_int_t *nparam, xh_int_t argc, VALUE *argv)
308
+ {
309
+ VALUE param;
310
+
311
+ if (*nparam >= argc)
312
+ rb_raise(rb_eArgError, "Invalid parameters");
313
+
314
+ param = argv[*nparam];
315
+ if (rb_cHash != rb_obj_class(param))
316
+ rb_raise(rb_eArgError, "Parameter is not a hash");
317
+
318
+ (*nparam)++;
319
+
320
+ return param;
321
+ }
322
+
323
+ void
324
+ xh_merge_opts(xh_opts_t *ctx_opts, xh_opts_t *opts, xh_int_t *nparam, xh_int_t argc, VALUE *argv)
325
+ {
326
+ if (opts == NULL) {
327
+ /* read global options */
328
+ xh_init_opts(ctx_opts);
329
+ }
330
+ else {
331
+ /* copy options from object */
332
+ xh_copy_opts(ctx_opts, opts);
333
+ }
334
+ if (*nparam < argc) {
335
+ xh_parse_args(ctx_opts, nparam, argc, argv);
336
+ }
337
+ }
338
+