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__