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.
@@ -0,0 +1,3 @@
1
+ module PropertyList
2
+ VERSION = '1.0'.freeze
3
+ end
@@ -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 &lt;?xml&gt; and &lt;plist&gt; 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 = {"&" => "&amp;", "<" => "&lt;", ">" => "&gt;"}.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: []