property-list 1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []