plist_lite 1.1.0

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 81c314a0ff465217564b4040a14bfc7a8913e26d9be6b365c489f4117f07fd76
4
+ data.tar.gz: 0ae9856ea9b8b13c691a4459ea907140d32d99cafedc6b726c570649a4ea77f3
5
+ SHA512:
6
+ metadata.gz: c12c104cfd7c1daf21fc7813e7dacc6498ec14efce8e3950dafae565050ee6b6fa9488638a4fe9e254cdcfb4388776005ab22c6c3495ad8cc4e1f155212c3bc6
7
+ data.tar.gz: 49ef1de68941d7683ec69843dc9a607ca97b85de8f9b2a7fbf299ffd66d2f965ce445593405c492ef54f3a2d7ed080888ac8eb8d6a6d7522702607a4b06400e8
@@ -0,0 +1,233 @@
1
+ #include "ruby.h"
2
+ #include "ruby/encoding.h"
3
+ #include <stdint.h>
4
+ #include <stdlib.h>
5
+
6
+ typedef struct
7
+ {
8
+ char *head;
9
+ size_t size;
10
+ size_t length;
11
+ } string;
12
+
13
+ string *string_new(size_t size)
14
+ {
15
+ string *ptr = malloc(sizeof(string));
16
+ ptr->size = size;
17
+ ptr->head = malloc(size);
18
+ ptr->head[0] = '\0';
19
+ ptr->length = 0;
20
+ return ptr;
21
+ }
22
+
23
+ void string_concat(string *recv, char *src)
24
+ {
25
+ size_t len_src = strlen(src);
26
+ size_t new_size = recv->size;
27
+ while ((recv->length + len_src + 1) > new_size)
28
+ new_size <<= 1;
29
+ if (new_size > recv->size)
30
+ recv->head = realloc(recv->head, new_size);
31
+
32
+ char *ptr = recv->head + recv->length;
33
+ while ((*ptr++ = *src++))
34
+ ;
35
+ recv->length += len_src;
36
+ }
37
+
38
+ void string_free(string *recv)
39
+ {
40
+ free(recv->head);
41
+ free(recv);
42
+ }
43
+
44
+ void time_to_plist_date(VALUE time, string *output)
45
+ {
46
+ rb_funcall(time, rb_intern("utc"), 0);
47
+ VALUE str = rb_funcall(time, rb_intern("iso8601"), 0);
48
+ string_concat(output, (char *)"<date>");
49
+ string_concat(output, StringValueCStr(str));
50
+ string_concat(output, (char *)"</date>");
51
+ }
52
+
53
+ void dump_node(VALUE obj, string *output);
54
+
55
+ VALUE to_utf8_xml_text(VALUE obj)
56
+ {
57
+ int idx = rb_enc_get_index(obj);
58
+ if (idx < 0)
59
+ rb_raise(rb_eTypeError, "unknown encoding");
60
+ if (idx == rb_utf8_encindex())
61
+ {
62
+ VALUE options = rb_hash_new();
63
+ rb_hash_aset(options, ID2SYM(rb_intern("xml")), ID2SYM(rb_intern("text")));
64
+ return rb_funcallv_kw(obj, rb_intern("encode"), 1, &options, RB_PASS_KEYWORDS);
65
+ }
66
+ else
67
+ {
68
+ VALUE options = rb_hash_new();
69
+ rb_hash_aset(options, ID2SYM(rb_intern("xml")), ID2SYM(rb_intern("text")));
70
+ VALUE args[] = {rb_enc_from_encoding(rb_utf8_encoding()), options};
71
+ return rb_funcallv_kw(obj, rb_intern("encode"), 2, args, RB_PASS_KEYWORDS);
72
+ }
73
+ }
74
+
75
+ VALUE array_each_block(VALUE obj, string *output)
76
+ {
77
+ dump_node(obj, output);
78
+ return Qnil;
79
+ }
80
+
81
+ VALUE hash_each_block(VALUE pair, string *output)
82
+ {
83
+ VALUE value = rb_ary_pop(pair);
84
+ VALUE key = rb_ary_pop(pair);
85
+ string_concat(output, (char *)"<key>");
86
+ switch (TYPE(key))
87
+ {
88
+ case T_STRING:
89
+ {
90
+ VALUE encoded = to_utf8_xml_text(key);
91
+ string_concat(output, StringValueCStr(encoded));
92
+ break;
93
+ }
94
+ case T_SYMBOL:
95
+ {
96
+ VALUE str = rb_funcall(key, rb_intern("to_s"), 0);
97
+ string_concat(output, StringValueCStr(str));
98
+ break;
99
+ }
100
+ default:
101
+ {
102
+ VALUE encoded = to_utf8_xml_text(rb_funcall(key, rb_intern("to_s"), 0));
103
+ string_concat(output, StringValueCStr(encoded));
104
+ break;
105
+ }
106
+ }
107
+ string_concat(output, (char *)"</key>");
108
+ dump_node(value, output);
109
+ return Qnil;
110
+ }
111
+
112
+ void dump_node(VALUE obj, string *output)
113
+ {
114
+ switch (TYPE(obj))
115
+ {
116
+ case T_ARRAY:
117
+ string_concat(output, (char *)"<array>");
118
+ rb_block_call(obj, rb_intern("each"), 0, NULL, (rb_block_call_func_t)array_each_block, (VALUE)output);
119
+ string_concat(output, (char *)"</array>");
120
+ break;
121
+ case T_HASH:
122
+ string_concat(output, (char *)"<dict>");
123
+ rb_block_call(obj, rb_intern("each"), 0, NULL, (rb_block_call_func_t)hash_each_block, (VALUE)output);
124
+ string_concat(output, (char *)"</dict>");
125
+ break;
126
+ case T_TRUE:
127
+ string_concat(output, (char *)"<true/>");
128
+ break;
129
+ case T_FALSE:
130
+ {
131
+ string_concat(output, (char *)"<false/>");
132
+ break;
133
+ }
134
+ case T_BIGNUM:
135
+ case T_FIXNUM:
136
+ {
137
+ VALUE str = rb_funcall(obj, rb_intern("to_s"), 0);
138
+ string_concat(output, (char *)"<integer>");
139
+ string_concat(output, StringValueCStr(str));
140
+ string_concat(output, (char *)"</integer>");
141
+ break;
142
+ }
143
+ case T_FLOAT:
144
+ {
145
+ VALUE str = rb_funcall(obj, rb_intern("to_s"), 0);
146
+ string_concat(output, (char *)"<real>");
147
+ string_concat(output, StringValueCStr(str));
148
+ string_concat(output, (char *)"</real>");
149
+ break;
150
+ }
151
+ case T_STRING:
152
+ {
153
+ int idx = rb_enc_get_index(obj);
154
+ if (idx == rb_ascii8bit_encindex())
155
+ {
156
+ string_concat(output, (char *)"<data>");
157
+ VALUE data = rb_funcall(
158
+ rb_ary_new_from_values(1, &obj),
159
+ rb_intern("pack"),
160
+ 1,
161
+ rb_str_new_literal("m"));
162
+ string_concat(output, StringValueCStr(data));
163
+ string_concat(output, (char *)"</data>");
164
+ }
165
+ else
166
+ {
167
+ string_concat(output, (char *)"<string>");
168
+ VALUE encoded = to_utf8_xml_text(obj);
169
+ string_concat(output, StringValueCStr(encoded));
170
+ string_concat(output, (char *)"</string>");
171
+ }
172
+
173
+ break;
174
+ }
175
+ case T_SYMBOL:
176
+ {
177
+ VALUE str = rb_funcall(obj, rb_intern("to_s"), 0);
178
+ string_concat(output, (char *)"<string>");
179
+ string_concat(output, StringValueCStr(str));
180
+ string_concat(output, (char *)"</string>");
181
+ break;
182
+ }
183
+ default:
184
+ {
185
+ if (rb_obj_is_kind_of(obj, rb_cTime) == Qtrue)
186
+ {
187
+ VALUE time = rb_funcall(rb_cTime, rb_intern("at"), 1, obj);
188
+ time_to_plist_date(time, output);
189
+ }
190
+ else if (rb_obj_is_kind_of(obj, rb_const_get(rb_cObject, rb_intern("DateTime"))) == Qtrue)
191
+ {
192
+ VALUE time = rb_funcall(obj, rb_intern("to_time"), 0);
193
+ time_to_plist_date(time, output);
194
+ }
195
+ else if (rb_obj_is_kind_of(obj, rb_const_get(rb_cObject, rb_intern("Date"))) == Qtrue)
196
+ {
197
+ rb_warn("Consider not using Date object because it does not contain time zone information");
198
+ VALUE str = rb_funcall(obj, rb_intern("iso8601"), 0);
199
+ string_concat(output, (char *)"<date>");
200
+ string_concat(output, StringValueCStr(str));
201
+ string_concat(output, (char *)"T00:00:00Z</date>");
202
+ }
203
+ else
204
+ {
205
+ VALUE msg = rb_funcall(rb_obj_class(obj), rb_intern("to_s"), 0);
206
+ rb_raise(rb_eArgError, "Unsupported type: %s", StringValueCStr(msg));
207
+ }
208
+ break;
209
+ }
210
+ }
211
+ }
212
+
213
+ VALUE dump(VALUE recv, VALUE obj)
214
+ {
215
+ string *output = string_new(2048);
216
+ string_concat(
217
+ output,
218
+ (char *)"<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
219
+ "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">"
220
+ "<plist version=\"1.0\">");
221
+ dump_node(obj, output);
222
+ string_concat(output, (char *)"</plist>");
223
+ VALUE ret = rb_utf8_str_new(output->head, output->length);
224
+ string_free(output);
225
+ return ret;
226
+ }
227
+
228
+ void Init_ext(void)
229
+ {
230
+ rb_require("time");
231
+ VALUE cPlistLite = rb_define_module("PlistLite");
232
+ rb_define_singleton_method(cPlistLite, "dump", dump, 1);
233
+ }
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mkmf'
4
+ create_makefile('plist_lite/ext')
data/lib/minimal.plist ADDED
@@ -0,0 +1 @@
1
+ <?xml version="1.0"?><!DOCTYPE plist SYSTEM "plist.dtd"><_/>
data/lib/plist.dtd ADDED
@@ -0,0 +1,19 @@
1
+ <!ENTITY % plistObject "(array | data | date | dict | real | integer | string | true | false )" >
2
+ <!ELEMENT plist %plistObject;>
3
+ <!ATTLIST plist version CDATA "1.0" >
4
+
5
+ <!-- Collections -->
6
+ <!ELEMENT array (%plistObject;)*>
7
+ <!ELEMENT dict (key, %plistObject;)*>
8
+ <!ELEMENT key (#PCDATA)>
9
+
10
+ <!--- Primitive types -->
11
+ <!ELEMENT string (#PCDATA)>
12
+ <!ELEMENT data (#PCDATA)> <!-- Contents interpreted as Base-64 encoded -->
13
+ <!ELEMENT date (#PCDATA)> <!-- Contents should conform to a subset of ISO 8601 (in particular, YYYY '-' MM '-' DD 'T' HH ':' MM ':' SS 'Z'. Smaller units may be omitted with a loss of precision) -->
14
+
15
+ <!-- Numerical primitives -->
16
+ <!ELEMENT true EMPTY> <!-- Boolean constant true -->
17
+ <!ELEMENT false EMPTY> <!-- Boolean constant false -->
18
+ <!ELEMENT real (#PCDATA)> <!-- Contents should represent a floating point number matching ("+" | "-")? d+ ("."d*)? ("E" ("+" | "-") d+)? where d is a digit 0-9. -->
19
+ <!ELEMENT integer (#PCDATA)> <!-- Contents should represent a (possibly signed) integer number in base 10 -->
data/lib/plist_lite.rb ADDED
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'nokogiri'
4
+ require 'time'
5
+ module PlistLite
6
+ DTD = Dir.chdir(__dir__) do
7
+ Nokogiri::XML::Document.parse(
8
+ IO.read("#{__dir__}/minimal.plist"), nil, nil,
9
+ Nokogiri::XML::ParseOptions.new(Nokogiri::XML::ParseOptions::DTDLOAD)
10
+ )
11
+ end.external_subset
12
+
13
+ class << self
14
+ def load(source)
15
+ doc = Nokogiri::XML::Document.parse(
16
+ source, nil, nil,
17
+ Nokogiri::XML::ParseOptions.new(Nokogiri::XML::ParseOptions::STRICT)
18
+ )
19
+ raise doc.errors.first unless doc.errors.empty?
20
+
21
+ errors = DTD.validate(doc)
22
+ raise errors.first unless errors.empty?
23
+
24
+ load_node(doc.root.elements.first)
25
+ end
26
+
27
+ def dump(obj)
28
+ output = +'<?xml version="1.0" encoding="UTF-8"?>' \
29
+ '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">' \
30
+ '<plist version="1.0">'
31
+ dump_node(obj, output)
32
+ output << '</plist>'
33
+ end
34
+
35
+ private
36
+
37
+ def load_node(node)
38
+ case node.name
39
+ when 'dict'
40
+ hash = {}
41
+ node.elements.each_slice(2) do |key_node, value_node|
42
+ hash[key_node.text] = load_node(value_node)
43
+ end
44
+ hash
45
+ when 'array'
46
+ array = []
47
+ node.elements.each { |element| array << load_node(element) }
48
+ array
49
+ when 'integer' then node.text.to_i
50
+ when 'real' then node.text.to_f
51
+ when 'date' then Time.iso8601(node.text)
52
+ when 'string' then node.text
53
+ when 'data' then node.text.unpack1('m')
54
+ when 'true' then true
55
+ when 'false' then false
56
+ end
57
+ end
58
+
59
+ def dump_node(obj, output)
60
+ case obj
61
+ when Hash
62
+ output << '<dict>'
63
+ obj.each do |key, value|
64
+ case key
65
+ when String then output << "<key>#{key.encode(xml: :text)}</key>"
66
+ when Symbol then output << "<key>#{key}</key>"
67
+ else
68
+ raise TypeError, 'Hash key should be String or Symbol'
69
+ end
70
+ dump_node(value, output)
71
+ end
72
+ output << '</dict>'
73
+ when Array
74
+ output << '<array>'
75
+ obj.each { |i| dump_node(i, output) }
76
+ output << '</array>'
77
+ when Symbol then output << "<string>#{obj}</string>"
78
+ when String
79
+ output <<
80
+ case obj.encoding
81
+ when Encoding::ASCII_8BIT then "<data>#{[obj].pack('m')}</data>"
82
+ when Encoding::UTF_8 then "<string>#{obj.encode(xml: :text)}</string>"
83
+ else "<string>#{obj.encode(Encoding::UTF_8, xml: :text)}</string>"
84
+ end
85
+ when Integer then output << "<integer>#{obj}</integer>"
86
+ when Float then output << "<real>#{obj}</real>"
87
+ when true then output << '<true/>'
88
+ when false then output << '<false/>'
89
+ when Time then output << "<date>#{Time.at(obj).utc.iso8601}</date>"
90
+ when DateTime then output << "<date>#{obj.to_time.utc.iso8601}</date>"
91
+ when Date
92
+ warn 'Consider not using Date object because it does not contain time zone information'
93
+ output << "<date>#{obj.iso8601}T00:00:00Z</date>"
94
+ else raise ArgumentError, "unknown type: #{obj.class}"
95
+ end
96
+ end
97
+ end
98
+ end
99
+
100
+ require 'plist_lite/ext'
Binary file
metadata ADDED
@@ -0,0 +1,120 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: plist_lite
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Weihang Jian
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-06-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: nokogiri
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '5.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '13.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '13.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake-compiler
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.0'
83
+ description: plist_lite is the fastest plist processor for Ruby written in C.It can
84
+ convert Ruby object to XML plist (a.k.a. property list), vice versa.
85
+ email: tonytonyjan@gmail.com
86
+ executables: []
87
+ extensions:
88
+ - ext/plist_lite/ext/extconf.rb
89
+ extra_rdoc_files: []
90
+ files:
91
+ - ext/plist_lite/ext/ext.c
92
+ - ext/plist_lite/ext/extconf.rb
93
+ - lib/minimal.plist
94
+ - lib/plist.dtd
95
+ - lib/plist_lite.rb
96
+ - lib/plist_lite/ext.bundle
97
+ homepage: https://github.com/tonytonyjan/plist_lite
98
+ licenses:
99
+ - MIT
100
+ metadata: {}
101
+ post_install_message:
102
+ rdoc_options: []
103
+ require_paths:
104
+ - lib
105
+ required_ruby_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '2.7'
110
+ required_rubygems_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ requirements: []
116
+ rubygems_version: 3.2.15
117
+ signing_key:
118
+ specification_version: 4
119
+ summary: plist_lite is the fastest plist processor for Ruby written in C.
120
+ test_files: []