-
Notifications
You must be signed in to change notification settings - Fork 0
/
plist_lite.rb
100 lines (91 loc) · 3.1 KB
/
plist_lite.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# frozen_string_literal: true
require 'nokogiri'
require 'time'
module PlistLite
DTD = Dir.chdir(__dir__) do
Nokogiri::XML::Document.parse(
IO.read("#{__dir__}/minimal.plist"), nil, nil,
Nokogiri::XML::ParseOptions.new(Nokogiri::XML::ParseOptions::DTDLOAD)
)
end.external_subset
class << self
def load(source)
doc = Nokogiri::XML::Document.parse(
source, nil, nil,
Nokogiri::XML::ParseOptions.new(Nokogiri::XML::ParseOptions::STRICT)
)
raise doc.errors.first unless doc.errors.empty?
errors = DTD.validate(doc)
raise errors.first unless errors.empty?
load_node(doc.root.elements.first)
end
def dump(obj)
output = +'<?xml version="1.0" encoding="UTF-8"?>' \
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">' \
'<plist version="1.0">'
dump_node(obj, output)
output << '</plist>'
end
private
def load_node(node)
case node.name
when 'dict'
hash = {}
node.elements.each_slice(2) do |key_node, value_node|
hash[key_node.text] = load_node(value_node)
end
hash
when 'array'
array = []
node.elements.each { |element| array << load_node(element) }
array
when 'integer' then node.text.to_i
when 'real' then node.text.to_f
when 'date' then Time.iso8601(node.text)
when 'string' then node.text
when 'data' then node.text.unpack1('m')
when 'true' then true
when 'false' then false
end
end
def dump_node(obj, output)
case obj
when Hash
output << '<dict>'
obj.each do |key, value|
case key
when String then output << "<key>#{key.encode(xml: :text)}</key>"
when Symbol then output << "<key>#{key}</key>"
else
raise TypeError, 'Hash key should be String or Symbol'
end
dump_node(value, output)
end
output << '</dict>'
when Array
output << '<array>'
obj.each { |i| dump_node(i, output) }
output << '</array>'
when Symbol then output << "<string>#{obj}</string>"
when String
output <<
case obj.encoding
when Encoding::ASCII_8BIT then "<data>#{[obj].pack('m')}</data>"
when Encoding::UTF_8 then "<string>#{obj.encode(xml: :text)}</string>"
else "<string>#{obj.encode(Encoding::UTF_8, xml: :text)}</string>"
end
when Integer then output << "<integer>#{obj}</integer>"
when Float then output << "<real>#{obj}</real>"
when true then output << '<true/>'
when false then output << '<false/>'
when Time then output << "<date>#{Time.at(obj).utc.iso8601}</date>"
when DateTime then output << "<date>#{obj.to_time.utc.iso8601}</date>"
when Date
warn 'Consider not using Date object because it does not contain time zone information'
output << "<date>#{obj.iso8601}T00:00:00Z</date>"
else raise ArgumentError, "unknown type: #{obj.class}"
end
end
end
end
require 'plist_lite/ext'