root/trunk/SimpleXML.pas

Revision 15, 11.0 kB (checked in by DavidM, 7 years ago)

--

Line 
1 unit SimpleXML;
2
3 {
4   Simple XML reader/writer
5   (c) David Medlock, 2005
6 }
7 interface
8
9 uses classes;
10
11 type
12   Node = class
13     parent  : Node;
14     name    : string;
15     data    : string;
16     children : TList;
17
18     function Add( nam : string ) : Node; overload;
19     function Add( nam, dat : string ) : Node ; overload;
20     function AddBool( nam : string ; val : boolean ) : Node ;
21     function AddChild( newnode : Node ) : Integer;
22     function GetChild( N: Integer ) : Node;
23
24     function FindNode( format : string ) : Node;
25     function FindText( format : string ) : string;
26     function Find( S : string ) : Node;
27
28     function Count : Integer;
29     property Child[ N : Integer ]:Node read GetChild ; default;
30
31     function ToString : String;
32
33     constructor Create( NodeName : string );
34     destructor Free;
35   end;
36
37
38 function LoadXML( fname : string ) : Node ;
39 procedure SaveXML( root : Node ; fname : string  );
40 function ParseXML( data : string ) : Node;
41
42
43 implementation
44
45 uses SysUtils, Contnrs, Math;
46
47 const
48   LF  = Char($D);
49   CR  = Char($A);
50   CRLF = '' + CR + '' + LF;
51   TAB = Char($F);
52   Whitespace = ' ' + CR + LF + TAB;
53
54 var
55   stack : TStack; // stack of nodes under construction
56   text  : string; // text we are parsing
57
58
59
60 function EncodeAll( data : string ) : string; forward;
61 function DecodeAll( data : string ) : string; forward;
62
63 function Look( S : string ) : boolean; forward;
64 function Skip( S : String ): boolean ; forward;
65
66 function FormatNode( root : Node ; indent : string ) : string; forward;
67 function FormatChildren( N : Node ; indent : string ) : string; forward;
68
69 function Decode( var src : string ) : char ; forward;
70 procedure Encode( const C : char ; var dest : string ); forward;
71
72
73 function Done : boolean ;
74 begin
75   result:= Length( text ) < 1;
76 end;
77
78 function Cur: char;
79 begin
80   if Done then result:=Char(0)
81   else result:=text[1];
82 end;
83
84
85
86 { Version of Cur which decodes the text, and consumes input }
87 function NextDecodedChar : Char;
88 begin
89   if Done then begin result:=Char(0); Exit; end;
90  
91   result:= Decode( text );
92 end;
93
94 procedure Next ;
95 begin
96   if Done then Exit;
97   text := Copy( text,2,Length(text)-1 );
98 end;
99
100
101 procedure Consume( N:Integer );
102 begin
103   while N > 0 do
104   begin
105     Next;
106     Dec(N); 
107     if Done then Exit;
108   end
109 end;
110
111
112 function IsAlNum : boolean ;
113 begin
114   result := (not Done) and
115     ( Cur in [ 'a' .. 'z', 'A' .. 'Z', '0' .. '9', '-', '_', ':' ] );
116 end;
117
118 function IsSpace : boolean ;
119 begin
120   result := (not Done) and ( Cur in [ ' ', TAB, LF, CR ] );
121 end;
122
123
124 procedure SkipWhite;
125 begin
126   while not Done do if IsSpace then Next Else Exit;
127 end;
128
129
130 function Look( S : string ) : boolean;
131 begin
132   result:= Pos( S, text )=1;
133 end;
134
135
136 function Skip( S : String ): boolean;
137 begin
138   result:= Look( S ) ;
139   if result then  Consume( Length(S) );
140 end;
141
142
143
144 function LoadXML( fname : string ) : Node ;
145 var
146   infile : TextFile;
147   line,data   : string;
148 begin
149   result:= nil;
150   if not FileExists( fname ) then Exit;
151   AssignFile( infile, fname );
152   Reset( infile );
153
154   try
155     while not Eof(InFile) do
156     begin
157       Readln(InFile, line);
158       line:=Trim(line);
159       data := data + line;
160     end;
161   finally
162     CloseFile(InFile);
163   end ;
164    
165   result:= ParseXML( data );
166 end;
167
168
169 function FormatChildren( N : Node ; indent : string ) : string;
170 var
171   I : Integer;
172   child : Node;
173 begin
174   result := '';
175   if N.Count = 0 then Exit;
176   result := CR;
177   for I:=0 to N.children.Count-1 do
178   begin
179     child:= N.children[I];
180     result := result + FormatNode( child, indent + '  ' );
181   end;
182 end;
183
184
185 function FormatNode( root : Node ; indent : string ) : string;
186 var
187   S : string;
188   E : string;
189 begin
190   result:='';
191   if not Assigned( root ) then Exit;
192   if (Length(root.Data)<1) and (root.Count = 0) then
193   begin
194     result:= indent + '<' + root.Name + '/>' + CR ;
195     Exit;
196   end;
197
198   S:= '<' + root.name + '>' + EncodeAll(root.data) ;
199   E:= '</' + root.name + '>' ;
200
201   if root.Count>0 then
202     result:= indent + S + FormatChildren( root, indent ) +
203               indent + E + CR
204   else
205     result:= indent + S + E + CR;
206 end;
207
208
209
210 procedure SaveXML( root : Node ; fname : string  );
211 var
212   outfile : TextFile ;
213   xml : string;
214 begin
215   if not Assigned( root ) then Exit;
216   AssignFile( outfile, fname );
217   Rewrite( outfile );
218   xml := root.ToString;
219   Write( outfile, xml );
220   CloseFile( outfile );
221 end;
222
223
224 function Begins( var S : string ; sub : string ) : boolean;
225 var
226   N, I : Integer;
227 begin
228   result:=false;
229   N:= Length(sub);
230   if N>Length(S) then Exit
231   else begin
232     for I:=1 to N do if S[I] <> sub[I] then Exit;
233   end;
234   S := Copy( S, N+1, Length(S)-N );
235   result:=true;
236 end;
237
238
239 function Decode( var src : string  ) : char;
240 begin
241   result:= Char(0);
242   if Length(src)<1 then Exit;
243
244   if src[1] <> '&' then
245   begin
246     result:= src[1];
247     src:=Copy( src,2, Length(src)-1 );
248     Exit;
249   end;
250
251   if Begins( src, '&amp;' ) then result:='&'
252   else if Begins( src, '&quot' ) then result:='"'
253   else if Begins( src, '&lt;' ) then result:='<'
254   else if Begins( src, '&gt;' ) then result:='>'
255   else if Begins( src, '&nbsp;' ) then result:=' '
256   else raise Exception.Create( 'Unknown Character encoding!' ) ;
257 end;
258
259
260 procedure Encode( const c : char ; var dest : string );
261 begin
262   Case C of
263     ' ': dest:= dest + '&nbsp;';
264     '&': dest:= dest + '&amp;' ;
265     '<': dest:= dest + '&lt;' ;
266     '>': dest:= dest + '&gt;' ;
267     '"': dest:= dest + '&quot;' ;
268     else dest:= dest + c;
269   end;
270 end;
271
272
273
274 function EncodeAll( data : string ) : string;
275 var
276   N : Integer;
277 begin
278   result := '';
279   for N:=1 to Length(data) do Encode( data[N], result );
280 end;
281
282 function DecodeAll( data: string ) : string ;
283 var
284   tmp : string;
285 begin
286   tmp := data;
287   while Length(tmp) > 0 do result:= result + Decode( tmp );
288 end;
289
290
291
292 function ParseName : string ;
293 begin
294   result:='';
295   while not Done and IsAlnum do
296   begin
297     result:= result + Cur;
298     Next;
299   end
300 end;
301
302
303 function ParseText : string;
304 begin
305   result:='';
306   while not Done do
307     while not (Cur = '<') do begin
308       result:= result + Cur;
309       Next;
310     end;
311 end;
312
313
314 procedure TagBody;
315 var
316   N : Node;
317   buf : string;
318 begin
319   buf := '';
320   while Not Done do
321   begin
322     if Cur in ['<'] then Break;
323     buf := buf + NextDecodedChar;
324   end;
325
326   if stack.count<1 then Exit;
327
328   buf:= Trim(buf);
329   if Length( buf )= 0 then Exit;
330
331   N:= Node( stack.Peek );
332   N.data := N.data + buf;
333 end;
334
335
336 procedure CloseTag;
337 var
338   Prev : Node; // Most recently Opened tag
339 begin
340   Assert( stack.Count>0 , 'Open Tag expected.' );
341   Prev:= Node(Stack.Peek);
342   if not Skip( '</' ) then raise Exception.Create( '</' + Prev.Name + '> Expected!');
343   SkipWhite;
344   Assert( Skip( Prev.Name ) , 'Expected Closing tag for node:' + Prev.Name );
345   SkipWhite;
346   Assert( Skip( '>' ) );
347   if stack.count<2 then Exit;
348   stack.pop;
349   Node(Stack.Peek).AddChild( Prev );
350 end;
351
352
353 procedure CommentTag;
354 var
355   E: Integer;
356 begin
357   Assert( Skip('<!--') );
358   E := Pos( '-->' , text );
359   if (E=0) then raise Exception.Create( 'Unclosed Comment' )
360   else
361     text:=Copy( text, E+3, Length(text)-(E+3)+1 );
362   SkipWhite;
363 end;
364
365 procedure OpenTag;
366 var
367   n : Node;
368 begin
369   Assert( Skip('<' ) );
370   n := Node.Create( ParseName );
371   { Used for a debugging breakpoint
372   if n.name = 'ProjectFiles' then begin
373     n.data:='';
374   end;}
375  
376   SkipWhite;
377   if Skip( '/>' ) then begin
378     if stack.Count>0 then Node(stack.Peek).AddChild( n )
379     else stack.push( n );
380     Exit;
381   end;
382   Assert( Skip('>' ) );
383   stack.Push( n );
384 end;
385
386
387 procedure Parse ;
388 begin
389   while not Done do begin
390     if Cur <> '<' then TagBody
391     else if Look( '<!--' ) then CommentTag
392     else if Look( '</' ) then CloseTag
393     else if Look( '<' ) then OpenTag
394     else
395       raise Exception.Create( 'Unknown Tag type:' +
396                   Copy( text, 1, Max( 10, Length(text) )));
397   end;
398 end;
399
400
401 function ParseXML( data : string ) : Node;
402 begin
403   stack := TStack.Create;
404   text := data ;
405   SkipWhite;
406   Parse;
407   result:=nil;
408   if stack.Count=0 then Exit;
409   result:=stack.pop;
410   while stack.Count>0 do  Node(stack.Pop).Free;
411   stack.free; 
412 end;
413
414
415 // ----------------------------------
416 // Node Functions Below
417 // ----------------------------------
418
419 function Node.Add( nam : string ) : Node;
420 begin
421   result:= Node.Create( nam );
422   Self.Children.Add( result );
423   result.parent := self;
424 end;
425
426 function Node.Add( nam, dat : string ) : Node;
427 begin
428   result:= Node.Create( nam );
429   result.data := dat;
430   Self.Children.Add( result );
431   result.parent := self;
432 end;
433
434 function Node.AddBool( nam : string ; val : boolean ) : Node ;
435 begin
436   result:= Self.Add( nam );
437   if val then result.data := 'true' else result.data := 'false';
438   result.parent := self;
439 end; 
440
441
442
443 function Node.AddChild( newnode : Node ) : Integer;
444 begin
445   result:= children.Add( newnode );
446   newnode.parent := self;
447 end;
448
449 function Node.GetChild( N: Integer ) : Node;
450 begin
451   result:=nil;
452   if N >= children.Count then Exit;
453   result:= children[N];
454 end;
455
456 function Node.Count : Integer;
457 begin
458   result:= children.Count;
459 end;
460
461
462
463 constructor Node.Create( NodeName : string );
464 begin
465   NodeName := Trim( NodeName );
466   Assert( Length(NodeName)>0, 'Node Name cannot be empty!' ) ;
467   self.Name := NodeName;
468   self.Children := TList.Create;
469 end;
470
471
472 destructor Node.Free;
473 var
474   N : Integer;
475 begin
476   for N:=0 to children.Count-1 do
477   begin
478     Node(children[N]).Free;
479   end;
480   children.Free;
481 end;
482
483 function Node.ToString : String;
484 begin
485   result:= FormatNode( Self, '' );
486 end;
487
488
489
490 { Finds next item from | separated list, used by FindNodes }
491 function NextName( var S : String ) : string;
492 var
493   N : Integer;
494 begin
495   while Length(S) > 0 do
496   begin
497     N := Pos( '|', S );
498     if N=0 then
499     begin
500       result:= S;
501       S:='';
502       Exit;
503     end;
504
505     if N=1 then
506     begin
507       S:= Copy( S, 2, Length(S)-1 );
508       Continue;
509     end;
510
511     result:= Copy( S, 1, N-1 );
512     S:= Copy( S, N+1, Length(S) - (N+1) );
513   end;
514 end;
515
516
517 { Finds first Node Under this Node matching the passed format
518   uses '|' to separate the node names
519   node := Node.Find( 'AddressBook|Names|Address|HomePhone' );
520 }
521 function Node.FindNode( format : string ) : Node;
522 var
523   cur : Node;
524   tok : string;
525 begin
526   result:= nil;
527   cur := Self;
528   while Length(format)>0 do
529   begin
530     tok := NextName( format );
531     if Length(tok) = 0 then Exit;
532     cur := cur.Find( tok );
533     if Not Assigned(cur) then Exit;
534   end;
535
536   result:= cur;
537 end;
538
539 function Node.FindText( format : string ) : string;
540 var
541   tmp : Node;
542 begin
543   result:='';
544   tmp := Self.FindNode( format );
545   if Assigned(tmp) then result:= tmp.data;
546 end;
547
548
549 function Node.Find( S : string ) : Node;
550 var
551   N : Integer;
552 begin
553   result:=nil;
554   for N:=0 to children.Count-1 do
555   begin
556     if Node(children[N]).Name <> S then Continue;
557     result:=children[N];
558     Break;
559   end;
560 end;
561    
562
563 end.
Note: See TracBrowser for help on using the browser.