| 1 |
# PropertyList XML File Parser |
| 2 |
# 2009, Otto Linnemann |
| 3 |
|
| 4 |
require 'rexml/document' |
| 5 |
include REXML |
| 6 |
|
| 7 |
class PropertyParser |
| 8 |
|
| 9 |
private |
| 10 |
|
| 11 |
# Base Element with pointer to parent |
| 12 |
# element and dynamic content |
| 13 |
class PTreeBase |
| 14 |
|
| 15 |
attr_accessor :obj, :parent |
| 16 |
|
| 17 |
def initialize( obj, parent = nil ) |
| 18 |
@obj = obj |
| 19 |
@parent = parent |
| 20 |
end |
| 21 |
|
| 22 |
end |
| 23 |
|
| 24 |
|
| 25 |
# PList Dictionary object which is mapped to a Ruby hash |
| 26 |
class PTreeArray < PTreeBase |
| 27 |
|
| 28 |
def initialize( parent ) |
| 29 |
super( Array.new, parent ) |
| 30 |
end |
| 31 |
|
| 32 |
def store_value( value ) |
| 33 |
@obj << value |
| 34 |
end |
| 35 |
|
| 36 |
end |
| 37 |
|
| 38 |
|
| 39 |
# PList Dictionary object which is mapped to a Ruby hash |
| 40 |
class PTreeDict < PTreeBase |
| 41 |
|
| 42 |
def initialize( parent ) |
| 43 |
super( Hash.new, parent ) |
| 44 |
end |
| 45 |
|
| 46 |
end |
| 47 |
|
| 48 |
|
| 49 |
# PList KeyValue singleton object which is mapped to a Ruby hash |
| 50 |
# parent must be a PTreeDict object |
| 51 |
class PTreeKeyValue < PTreeBase |
| 52 |
|
| 53 |
def initialize( parent ) |
| 54 |
raise "wrong element eror" if ! parent.is_a?( PTreeDict ) |
| 55 |
super( Hash.new, parent ) |
| 56 |
@key = nil |
| 57 |
@value = nil |
| 58 |
end |
| 59 |
|
| 60 |
def store_key( string ) |
| 61 |
raise "wrong element eror" if ! string.is_a?( String ) |
| 62 |
@key = string |
| 63 |
end |
| 64 |
|
| 65 |
def store_value( value ) |
| 66 |
@value = value |
| 67 |
@obj[@key] = value |
| 68 |
self.parent.obj[@key] = value |
| 69 |
end |
| 70 |
|
| 71 |
end |
| 72 |
|
| 73 |
|
| 74 |
def tabs |
| 75 |
tabstr = "" |
| 76 |
i=0; while( i < @level ) do tabstr << "\t"; i+=1; end |
| 77 |
tabstr |
| 78 |
end |
| 79 |
|
| 80 |
|
| 81 |
# logging function for debugging purposes |
| 82 |
def log( str ) |
| 83 |
# puts str |
| 84 |
end |
| 85 |
|
| 86 |
|
| 87 |
|
| 88 |
public |
| 89 |
|
| 90 |
# intializes parse object |
| 91 |
def initialize() |
| 92 |
@root = nil |
| 93 |
@top = nil |
| 94 |
|
| 95 |
@tag = "" |
| 96 |
@state = :idle |
| 97 |
@level = 0 |
| 98 |
end |
| 99 |
|
| 100 |
|
| 101 |
def parse( xmldata ) |
| 102 |
Document.parse_stream( xmldata, self ) |
| 103 |
to_a |
| 104 |
end |
| 105 |
|
| 106 |
|
| 107 |
# delivers to level parsing result as array |
| 108 |
def to_a |
| 109 |
@root.obj |
| 110 |
end |
| 111 |
|
| 112 |
|
| 113 |
|
| 114 |
def tag_start( name, attributes ) |
| 115 |
log tabs + "> tagstart(" + name + " attr: " + attributes.to_s + ")" |
| 116 |
@tag = name |
| 117 |
|
| 118 |
case @tag |
| 119 |
when "plist" |
| 120 |
# for the DOM root element a create new instance is created |
| 121 |
@root = PTreeArray.new( nil ) |
| 122 |
@top = @root |
| 123 |
|
| 124 |
when "dict" |
| 125 |
@top = PTreeDict.new( @top ) |
| 126 |
|
| 127 |
when "array" |
| 128 |
@top = PTreeArray.new( @top ) |
| 129 |
|
| 130 |
when "key" |
| 131 |
@top = PTreeKeyValue.new( @top ) |
| 132 |
|
| 133 |
end |
| 134 |
|
| 135 |
@state = :tag_start |
| 136 |
@level += 1 |
| 137 |
end |
| 138 |
|
| 139 |
|
| 140 |
def text( str ) |
| 141 |
|
| 142 |
if @state != :tag_start |
| 143 |
@state = :text |
| 144 |
return |
| 145 |
end |
| 146 |
|
| 147 |
text = if( str.length > 3 ) then str else "" end |
| 148 |
log tabs + "> text(" + text + ")" |
| 149 |
|
| 150 |
case @tag |
| 151 |
|
| 152 |
when "key" |
| 153 |
@top.store_key( str ) |
| 154 |
|
| 155 |
when "string" |
| 156 |
@top.store_value( str ) |
| 157 |
|
| 158 |
when "integer" |
| 159 |
@top.store_value( str.to_i ) |
| 160 |
|
| 161 |
when "real" |
| 162 |
@top.store_value( str.to_f ) |
| 163 |
|
| 164 |
end |
| 165 |
|
| 166 |
@state = :text |
| 167 |
|
| 168 |
end |
| 169 |
|
| 170 |
|
| 171 |
def tag_end( name ) |
| 172 |
@tag = name |
| 173 |
|
| 174 |
case @tag |
| 175 |
when "plist" |
| 176 |
raise "parse error empty object" if @top.obj.empty? |
| 177 |
@top = @top.parent |
| 178 |
|
| 179 |
when "dict", "array" |
| 180 |
raise "parse error empty object" if @top.obj.empty? |
| 181 |
@top.parent.store_value( @top.obj ) |
| 182 |
@top = @top.parent |
| 183 |
@top = @top.parent if( @top.is_a? PTreeKeyValue ) |
| 184 |
|
| 185 |
when "string", "integer", "real" |
| 186 |
@top = @top.parent if( @top.is_a? PTreeKeyValue ) |
| 187 |
end |
| 188 |
|
| 189 |
@level -= 1 |
| 190 |
log tabs + "> tag_end(" + name + ")" |
| 191 |
@state = :tag_end |
| 192 |
end |
| 193 |
|
| 194 |
|
| 195 |
def xmldecl( version, encoding, options ) |
| 196 |
log tabs + ">>>> xmldecl(" + version + ", " + encoding + ", " + options.to_s + ")" |
| 197 |
end |
| 198 |
|
| 199 |
|
| 200 |
def doctype( type, scope, manufacturer, options ) |
| 201 |
log tabs + ">>>>> doctype(" + type.to_s + ", " + scope.to_s + ", " + manufacturer.to_s + ", " + options.to_s + ")" |
| 202 |
end |
| 203 |
|
| 204 |
|
| 205 |
end |
| 206 |
|
| 207 |
|
| 208 |
# Sample invocation |
| 209 |
|
| 210 |
=begin |
| 211 |
prop = PropertyParser.new |
| 212 |
data = "" |
| 213 |
File.open("Info.plist", "r" ) { |fp| data = fp.read } |
| 214 |
Document.parse_stream( data, prop ) |
| 215 |
log prop.to_a |
| 216 |
|
| 217 |
data = "" |
| 218 |
File.open("Info.plist", "r" ) { |fp| data = fp.read } |
| 219 |
collection = PropertyParser.new.parse( data ) |
| 220 |
puts collection |
| 221 |
|
| 222 |
=end |
| 223 |
|
| 224 |
|
| 225 |
__END__ |