property-list 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 +7 -0
- data/.gitignore +50 -0
- data/LICENSE +29 -0
- data/README.md +52 -0
- data/Rakefile +7 -0
- data/gems.locked +41 -0
- data/gems.rb +3 -0
- data/lib/property-list.rb +83 -0
- data/lib/property-list/ascii_generator.rb +148 -0
- data/lib/property-list/ascii_parser.rb +217 -0
- data/lib/property-list/binary_generator.rb +306 -0
- data/lib/property-list/binary_markers.rb +34 -0
- data/lib/property-list/binary_parser.rb +169 -0
- data/lib/property-list/version.rb +3 -0
- data/lib/property-list/xml_generator.rb +147 -0
- data/lib/property-list/xml_parser.rb +121 -0
- data/property-list.gemspec +22 -0
- metadata +131 -0
@@ -0,0 +1,147 @@
|
|
1
|
+
module PropertyList
|
2
|
+
|
3
|
+
# options can be:
|
4
|
+
#
|
5
|
+
# [:segment] whether wrap the xml output is a segment or wrapped with <?xml> and <plist> tags. default is <code>false</code>.
|
6
|
+
#
|
7
|
+
# [:xml_version] you can also specify <code>"1.1"</code> for https://www.w3.org/TR/xml11/, default is <code>"1.0"</code>, no effect if <code>:segment</code> is set to <code>true</code>
|
8
|
+
#
|
9
|
+
# [:indent_unit] the indent unit, default value is <code>"\t"</code>, set to <code>nil</code> or <code>''</code> if you don't need indent
|
10
|
+
#
|
11
|
+
# [:initial_indent] initial indent space, default is <code>''</code>, the indentation per line equals to <code>initial_indent + indent * current_indent_level</code>
|
12
|
+
#
|
13
|
+
# [:base64_width] the width of characters per line when serializing data with Base64, default value is <code>68</code>, must be multiple of <code>4</code>
|
14
|
+
#
|
15
|
+
# [:base64_indent] whether indent the Base64 encoded data, you can use <code>false</code> for compatibility to generate same output for other frameworks, default value is <code>true</code>
|
16
|
+
#
|
17
|
+
def self.dump_xml obj, segment: false, xml_version: '1.0', base64_width: 68, base64_indent: true, indent_unit: "\t", initial_indent: ''
|
18
|
+
if !base64_width.is_a?(Integer) or base64_width <= 0 or base64_width % 4 != 0
|
19
|
+
raise ArgumentError, "option :base64_width must be a positive integer and a multiple of 4"
|
20
|
+
end
|
21
|
+
|
22
|
+
generator = XmlGenerator.new base64_width: base64_width, base64_indent: base64_indent, indent_unit: indent_unit, initial_indent: initial_indent
|
23
|
+
if segment
|
24
|
+
generator.generate obj
|
25
|
+
else
|
26
|
+
generator.output << %|<?xml version="#{xml_version}" encoding="UTF-8"?>\n|
|
27
|
+
generator.output << %|<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n|
|
28
|
+
generator.output << %|<plist version="1.0">\n|
|
29
|
+
generator.generate obj
|
30
|
+
generator.output << %|</plist>\n|
|
31
|
+
end
|
32
|
+
generator.output.join
|
33
|
+
end
|
34
|
+
|
35
|
+
class XmlGenerator #:nodoc:
|
36
|
+
def initialize base64_width: 68, base64_indent: true, indent_unit: "\t", initial_indent: ''
|
37
|
+
@indent_unit = indent_unit
|
38
|
+
@indent_level = 0
|
39
|
+
@initial_indent = initial_indent
|
40
|
+
@indent = @initial_indent + @indent_unit * @indent_level
|
41
|
+
@base64_bytes_per_line = (base64_width * 6) / 8
|
42
|
+
@base64_indent = base64_indent
|
43
|
+
@output = []
|
44
|
+
end
|
45
|
+
attr_reader :output
|
46
|
+
|
47
|
+
def generate obj
|
48
|
+
if obj.respond_to? :to_plist_xml
|
49
|
+
xml = obj.to_plist_xml
|
50
|
+
if !xml.start_with?(@indent)
|
51
|
+
xml = @indent + xml
|
52
|
+
end
|
53
|
+
if !xml.end_with?("\n")
|
54
|
+
xml += "\n"
|
55
|
+
end
|
56
|
+
@output << xml
|
57
|
+
return
|
58
|
+
end
|
59
|
+
|
60
|
+
case obj
|
61
|
+
when Array
|
62
|
+
if obj.empty?
|
63
|
+
empty_tag 'array'
|
64
|
+
else
|
65
|
+
tag 'array' do
|
66
|
+
obj.each {|e| generate e }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
when Hash
|
70
|
+
if obj.empty?
|
71
|
+
empty_tag 'dict'
|
72
|
+
else
|
73
|
+
tag 'dict' do
|
74
|
+
obj.keys.sort_by(&:to_s).each do |k|
|
75
|
+
v = obj[k]
|
76
|
+
tag 'key', escape_string(k.to_s)
|
77
|
+
generate v
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
when true, false
|
82
|
+
empty_tag obj
|
83
|
+
when Time
|
84
|
+
tag 'date', obj.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
|
85
|
+
when Date # also catches DateTime
|
86
|
+
tag 'date', obj.strftime('%Y-%m-%dT%H:%M:%SZ')
|
87
|
+
when String
|
88
|
+
tag 'string', escape_string(obj)
|
89
|
+
when Symbol
|
90
|
+
tag 'string', escape_string(obj.to_s)
|
91
|
+
when Float
|
92
|
+
if obj.to_i == obj
|
93
|
+
tag 'real', obj.to_i
|
94
|
+
else
|
95
|
+
tag 'real', obj
|
96
|
+
end
|
97
|
+
when Integer
|
98
|
+
tag 'integer', obj
|
99
|
+
when IO, StringIO
|
100
|
+
obj.rewind
|
101
|
+
contents = obj.read
|
102
|
+
data_tag contents
|
103
|
+
else
|
104
|
+
raise "Unsupported class: #{obj.class}"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def empty_tag name
|
111
|
+
@output << "#@indent<#{name}/>\n"
|
112
|
+
end
|
113
|
+
|
114
|
+
def data_tag contents
|
115
|
+
# m51 means: 51 bytes for each base64 encode run length, which is (51 * 8 / 6 = 68) chars per line after base64
|
116
|
+
base64 = [contents].pack "m#@base64_bytes_per_line"
|
117
|
+
if @base64_indent
|
118
|
+
base64.gsub! /^/, @indent
|
119
|
+
end
|
120
|
+
@output << "#@indent<data>\n#{base64}#@indent</data>\n"
|
121
|
+
end
|
122
|
+
|
123
|
+
def comment_tag content
|
124
|
+
@output << "#@indent<!-- #{content} -->\n"
|
125
|
+
end
|
126
|
+
|
127
|
+
def tag name, contents=nil
|
128
|
+
if block_given?
|
129
|
+
@output << "#@indent<#{name}>\n"
|
130
|
+
@indent = @initial_indent + @indent_unit * (@indent_level += 1)
|
131
|
+
yield
|
132
|
+
@indent = @initial_indent + @indent_unit * (@indent_level -= 1)
|
133
|
+
@output << "#@indent</#{name}>\n"
|
134
|
+
else
|
135
|
+
@output << "#@indent<#{name}>#{contents}</#{name}>\n"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
TABLE_FOR_ESCAPE = {"&" => "&", "<" => "<", ">" => ">"}.freeze
|
140
|
+
def escape_string s
|
141
|
+
# Likes CGI.escapeHTML but leaves `'` or `"` as mac plist does
|
142
|
+
s.gsub /[&<>]/ do |c|
|
143
|
+
TABLE_FOR_ESCAPE[c]
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
module PropertyList
|
2
|
+
def self.load_xml xml
|
3
|
+
XmlParser.new(xml).parse
|
4
|
+
end
|
5
|
+
|
6
|
+
class XmlParser
|
7
|
+
def initialize src
|
8
|
+
@lexer = StringScanner.new src
|
9
|
+
end
|
10
|
+
|
11
|
+
def skip_space_and_comments
|
12
|
+
@lexer.skip(%r{(?:
|
13
|
+
[\x0A\x0D\u2028\u2029\x09\x0B\x0C\x20]+ # newline and space
|
14
|
+
|
|
15
|
+
<!--(?:.*?)-->
|
16
|
+
)+}mx)
|
17
|
+
end
|
18
|
+
|
19
|
+
def parse
|
20
|
+
@lexer.skip(/<\?xml\s+.*?\?>*/m)
|
21
|
+
# TODO xml_encoding = xml_declaration.match(/(?:\A|\s)encoding=(?:"(.*?)"|'(.*?)')(?:\s|\Z)/)
|
22
|
+
|
23
|
+
skip_space_and_comments
|
24
|
+
# TODO doctype should co-exist with xml
|
25
|
+
@lexer.skip(/\s*<!DOCTYPE\s+.*?>/m)
|
26
|
+
skip_space_and_comments
|
27
|
+
|
28
|
+
if @lexer.scan(/<plist\s*\b[^\/\>]*\/>/)
|
29
|
+
skip_space_and_comments
|
30
|
+
if !@lexer.eos?
|
31
|
+
syntax_error "unrecognized code after plist tag end"
|
32
|
+
end
|
33
|
+
return
|
34
|
+
end
|
35
|
+
|
36
|
+
plist_open = !!@lexer.scan(/<plist\s*\b.*?(?<!\/)>/m)
|
37
|
+
|
38
|
+
res = parse_object
|
39
|
+
|
40
|
+
skip_space_and_comments
|
41
|
+
plist_close = !!@lexer.scan(/<\/plist\s*>/m)
|
42
|
+
skip_space_and_comments
|
43
|
+
if !@lexer.eos?
|
44
|
+
syntax_error "unrecognized code after plist tag end"
|
45
|
+
end
|
46
|
+
|
47
|
+
if plist_open ^ plist_close
|
48
|
+
syntax_error "mismatched <plist> tag"
|
49
|
+
end
|
50
|
+
|
51
|
+
res
|
52
|
+
end
|
53
|
+
|
54
|
+
# pushes obj into stack
|
55
|
+
def parse_object
|
56
|
+
skip_space_and_comments
|
57
|
+
if e = @lexer.scan(/<(true|false|array|dict|data)\s*\/>/m)
|
58
|
+
case e[/\w+/]
|
59
|
+
when 'true'; true
|
60
|
+
when 'false'; false
|
61
|
+
when 'array'; []
|
62
|
+
when 'dict'; {}
|
63
|
+
else; '' # data
|
64
|
+
end
|
65
|
+
|
66
|
+
elsif e = @lexer.scan(/<(integer|real|string|date|data)\s*>[^<>]*<\/\1\s*>/m)
|
67
|
+
tag = e[/\w+/]
|
68
|
+
content = tag_content e
|
69
|
+
|
70
|
+
case tag
|
71
|
+
when 'integer'
|
72
|
+
content.to_i
|
73
|
+
when 'real'
|
74
|
+
content.to_f
|
75
|
+
when 'string'
|
76
|
+
CGI.unescape_html content
|
77
|
+
when 'date'
|
78
|
+
DateTime.parse content
|
79
|
+
else # data
|
80
|
+
StringIO.new Base64.decode64 content
|
81
|
+
end
|
82
|
+
|
83
|
+
elsif e = @lexer.scan(/<array\s*>/m)
|
84
|
+
res = []
|
85
|
+
until (skip_space_and_comments; @lexer.scan(/<\/array\s*>/m))
|
86
|
+
e = parse_object
|
87
|
+
syntax_error 'failed to parse array element' if e.nil?
|
88
|
+
res << e
|
89
|
+
end
|
90
|
+
res
|
91
|
+
|
92
|
+
elsif e = @lexer.scan(/<dict\s*>/)
|
93
|
+
res = {}
|
94
|
+
until (skip_space_and_comments; @lexer.scan(/<\/dict\s*>/m))
|
95
|
+
key_tag = @lexer.scan(/<key\s*>[^<>]*<\/key\s*>/m)
|
96
|
+
syntax_error 'failed to parse dict key' if !key_tag
|
97
|
+
|
98
|
+
key = CGI.unescape_html tag_content key_tag
|
99
|
+
value = parse_object
|
100
|
+
syntax_error 'failed to parse dict value' if value.nil?
|
101
|
+
res[key] = value
|
102
|
+
end
|
103
|
+
res
|
104
|
+
|
105
|
+
else
|
106
|
+
nil
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def tag_content e
|
111
|
+
e[(e.index('>') + 1)...(e.rindex '<')]
|
112
|
+
end
|
113
|
+
|
114
|
+
def syntax_error msg
|
115
|
+
pre = @lexer.string[0...@lexer.pos]
|
116
|
+
line = pre.count("\n") + 1
|
117
|
+
col = pre.size - pre.rindex("\n")
|
118
|
+
raise SyntaxError, msg + " at line: #{line} col: #{col} #{@lexer.inspect}", caller
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require_relative 'lib/property-list/version'
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "property-list"
|
5
|
+
spec.version = PropertyList::VERSION
|
6
|
+
spec.authors = ["Luikore"]
|
7
|
+
spec.email = 'no@email'
|
8
|
+
|
9
|
+
spec.summary = "Property List (plist) library with all formats support"
|
10
|
+
spec.description = "Full-featured property list library. Supports XML/ASCII/Binary plist generate and parse. Finely-tuned formatting options. No line-ending bug like plist gem. Cross platform. No dependency."
|
11
|
+
spec.homepage = "https://github.com/luikore/property-list"
|
12
|
+
spec.license = "BSD-3-Clause"
|
13
|
+
|
14
|
+
spec.files = `git ls-files -z`.split("\x0").grep_v %r{^(test|spec|features)/}
|
15
|
+
spec.require_paths = ["lib"]
|
16
|
+
|
17
|
+
spec.add_development_dependency "bundler", "~> 1.15"
|
18
|
+
spec.add_development_dependency "rake", "~> 12.0"
|
19
|
+
spec.add_development_dependency "test-unit", "~> 1.2"
|
20
|
+
spec.add_development_dependency "pry", "~> 0.10"
|
21
|
+
spec.add_development_dependency "simplecov", "~> 0.14"
|
22
|
+
end
|
metadata
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: property-list
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '1.0'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Luikore
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-09-13 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.15'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.15'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '12.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '12.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: test-unit
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.2'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.2'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pry
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.10'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.10'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: simplecov
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.14'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.14'
|
83
|
+
description: Full-featured property list library. Supports XML/ASCII/Binary plist
|
84
|
+
generate and parse. Finely-tuned formatting options. No line-ending bug like plist
|
85
|
+
gem. Cross platform. No dependency.
|
86
|
+
email: no@email
|
87
|
+
executables: []
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- ".gitignore"
|
92
|
+
- LICENSE
|
93
|
+
- README.md
|
94
|
+
- Rakefile
|
95
|
+
- gems.locked
|
96
|
+
- gems.rb
|
97
|
+
- lib/property-list.rb
|
98
|
+
- lib/property-list/ascii_generator.rb
|
99
|
+
- lib/property-list/ascii_parser.rb
|
100
|
+
- lib/property-list/binary_generator.rb
|
101
|
+
- lib/property-list/binary_markers.rb
|
102
|
+
- lib/property-list/binary_parser.rb
|
103
|
+
- lib/property-list/version.rb
|
104
|
+
- lib/property-list/xml_generator.rb
|
105
|
+
- lib/property-list/xml_parser.rb
|
106
|
+
- property-list.gemspec
|
107
|
+
homepage: https://github.com/luikore/property-list
|
108
|
+
licenses:
|
109
|
+
- BSD-3-Clause
|
110
|
+
metadata: {}
|
111
|
+
post_install_message:
|
112
|
+
rdoc_options: []
|
113
|
+
require_paths:
|
114
|
+
- lib
|
115
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
116
|
+
requirements:
|
117
|
+
- - ">="
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: '0'
|
120
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
requirements: []
|
126
|
+
rubyforge_project:
|
127
|
+
rubygems_version: 2.6.11
|
128
|
+
signing_key:
|
129
|
+
specification_version: 4
|
130
|
+
summary: Property List (plist) library with all formats support
|
131
|
+
test_files: []
|