plist_lite 1.1.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
+ 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: []