CWIS Developer Documentation
XMLParser.php
Go to the documentation of this file.
1 <?PHP
2 #
3 # FILE: SPT--XMLParser.php
4 #
5 # METHODS PROVIDED:
6 # XMLParser()
7 # - constructor
8 # SomeMethod($SomeParameter, $AnotherParameter)
9 # - short description of method
10 #
11 # AUTHOR: Edward Almasy
12 #
13 # Part of the Scout Portal Toolkit
14 # Copyright 2005 Internet Scout Project
15 # http://scout.wisc.edu
16 #
17 
18 class XMLParser {
19 
20  # ---- PUBLIC INTERFACE --------------------------------------------------
21 
22  # object constructor
23  function XMLParser($Encoding="UTF-8")
24  {
25  # set default debug output level
26  $this->DebugLevel = 0;
27 
28  # create XML parser and tell it about our methods
29  $this->Parser = xml_parser_create($Encoding);
30  xml_set_object($this->Parser, $this);
31  xml_set_element_handler($this->Parser, "OpenTag", "CloseTag");
32  xml_set_character_data_handler($this->Parser, "ReceiveData");
33 
34  # initialize tag storage arrays
35  $this->TagNames = array();
36  $this->TagAttribs = array();
37  $this->TagData = array();
38  $this->TagParents = array();
39 
40  # initialize indexes for parsing and retrieving data
41  $this->CurrentParseIndex = -1;
42  $this->CurrentSeekIndex = -1;
43  $this->NameKeyCache = array();
44  }
45 
46  # parse text stream and store result
47  function ParseText($Text, $LastTextToParse = TRUE)
48  {
49  # pass text to PHP XML parser
50  xml_parse($this->Parser, $Text, $LastTextToParse);
51  }
52 
53  # move current tag pointer to specified item (returns NULL on failure)
54  function SeekTo() # (args may be tag names or indexes)
55  {
56  # perform seek based on arguments passed by caller
57  $SeekResult = $this->PerformSeek(func_get_args(), TRUE);
58 
59  # if seek successful
60  if ($SeekResult !== NULL)
61  {
62  # retrieve item count at seek location
63  $ItemCount = count($this->CurrentItemList);
64  }
65  else
66  {
67  # return null value to indicate that seek failed
68  $ItemCount = NULL;
69  }
70 
71  # return count of tags found at requested location
72  if ($this->DebugLevel > 0)
73  {
74  print("XMLParser->SeekTo(");
75  $Sep = "";
76  $DbugArgList = "";
77  foreach (func_get_args() as $Arg)
78  {
79  $DbugArgList .= $Sep."\"".$Arg."\"";
80  $Sep = ", ";
81  }
82  print($DbugArgList.") returned ".intval($ItemCount)." items starting at index ".$this->CurrentSeekIndex."\n");
83  }
84  return $ItemCount;
85  }
86 
87  # move seek pointer up one level (returns tag name or NULL if no parent)
88  function SeekToParent()
89  {
90  # if we are not at the root of the tree
91  if ($this->CurrentSeekIndex >= 0)
92  {
93  # move up one level in tree
94  $this->CurrentSeekIndex = $this->TagParents[$this->CurrentSeekIndex];
95 
96  # clear item list
97  unset($this->CurrentItemList);
98 
99  # return name of new tag to caller
100  $Result = $this->TagNames[$this->CurrentSeekIndex];
101  }
102  else
103  {
104  # return NULL indicating that no parent was found
105  $Result = NULL;
106  }
107 
108  # return result to caller
109  if ($this->DebugLevel > 0) { print("XMLParser->SeekToParent() returned ".$Result."<br>\n"); }
110  return $Result;
111  }
112 
113  # move seek pointer to first child of current tag (returns tag name or NULL if no children)
114  function SeekToChild($ChildIndex = 0)
115  {
116  # look for tags with current tag as parent
117  $ChildTags = array_keys($this->TagParents, $this->CurrentSeekIndex);
118 
119  # if child tag was found with requested index
120  if (isset($ChildTags[$ChildIndex]))
121  {
122  # set current seek index to child
123  $this->CurrentSeekIndex = $ChildTags[$ChildIndex];
124 
125  # clear item list info
126  unset($this->CurrentItemList);
127 
128  # return name of new tag to caller
129  $Result = $this->TagNames[$this->CurrentSeekIndex];
130  }
131  else
132  {
133  # return NULL indicating that no children were found
134  $Result = NULL;
135  }
136 
137  # return result to caller
138  if ($this->DebugLevel > 0) { print("XMLParser->SeekToChild() returned ".$Result."<br>\n"); }
139  return $Result;
140  }
141 
142  # move seek pointer to root of tree
143  function SeekToRoot()
144  {
145  $this->CurrentSeekIndex = -1;
146  }
147 
148  # move to next tag at current level (returns tag name or NULL if no next)
149  function NextTag()
150  {
151  # get list of tags with same parent as this tag
152  $LevelTags = array_keys($this->TagParents,
153  $this->TagParents[$this->CurrentSeekIndex]);
154 
155  # find position of next tag in list
156  $NextTagPosition = array_search($this->CurrentSeekIndex, $LevelTags) + 1;
157 
158  # if there is a next tag
159  if (count($LevelTags) > $NextTagPosition)
160  {
161  # move seek pointer to next tag at this level
162  $this->CurrentSeekIndex = $LevelTags[$NextTagPosition];
163 
164  # rebuild item list
165 
166  # return name of tag at new position to caller
167  return $this->TagNames[$this->CurrentSeekIndex];
168  }
169  else
170  {
171  # return NULL to caller to indicate no next tag
172  return NULL;
173  }
174  }
175 
176  # move to next instance of current tag (returns index or NULL if no next)
177  function NextItem()
178  {
179  # set up item list if necessary
180  if (!isset($this->CurrentItemList)) { $this->RebuildItemList(); }
181 
182  # if there are items left to move to
183  if ($this->CurrentItemIndex < ($this->CurrentItemCount - 1))
184  {
185  # move item pointer to next item
186  $this->CurrentItemIndex++;
187 
188  # set current seek pointer to next item
189  $this->CurrentSeekIndex =
190  $this->CurrentItemList[$this->CurrentItemIndex];
191 
192  # return new item index to caller
193  $Result = $this->CurrentItemIndex;
194  }
195  else
196  {
197  # return NULL value to caller to indicate failure
198  $Result = NULL;
199  }
200 
201  # return result to caller
202  return $Result;
203  }
204 
205  # move to previous instance of current tag (returns index or NULL on fail)
206  function PreviousItem()
207  {
208  # set up item list if necessary
209  if (!isset($this->CurrentItemList)) { $this->RebuildItemList(); }
210 
211  # if we are not at the first item
212  if ($this->CurrentItemIndex > 0)
213  {
214  # move item pointer to previous item
215  $this->CurrentItemIndex--;
216 
217  # set current seek pointer to next item
218  $this->CurrentSeekIndex =
219  $this->CurrentItemList[$this->CurrentItemIndex];
220 
221  # return new item index to caller
223  }
224  else
225  {
226  # return NULL value to caller to indicate failure
227  return NULL;
228  }
229  }
230 
231  # retrieve tag name from current seek point
232  function GetTagName()
233  {
234  if (isset($this->TagNames[$this->CurrentSeekIndex]))
235  {
236  return $this->TagNames[$this->CurrentSeekIndex];
237  }
238  else
239  {
240  return NULL;
241  }
242  }
243 
244  # retrieve data from current seek point
245  function GetData()
246  {
247  # assume that we will not be able to retrieve data
248  $Data = NULL;
249 
250  # if arguments were supplied
251  if (func_num_args())
252  {
253  # retrieve index for specified point
254  $Index = $this->PerformSeek(func_get_args(), FALSE);
255 
256  # if valid index was found
257  if ($Index !== NULL)
258  {
259  # retrieve data at index to be returned to caller
260  $Data = $this->TagData[$Index];
261  }
262  }
263  else
264  {
265  # if current seek index points to valid tag
266  if ($this->CurrentSeekIndex >= 0)
267  {
268  # retrieve data to be returned to caller
269  $Data = $this->TagData[$this->CurrentSeekIndex];
270  }
271  }
272 
273  # return data to caller
274  if ($this->DebugLevel > 0)
275  {
276  print("XMLParser->GetData(");
277  if (func_num_args()) { $ArgString = ""; foreach (func_get_args() as $Arg) { $ArgString .= "\"".$Arg."\", "; } $ArgString = substr($ArgString, 0, strlen($ArgString) - 2); print($ArgString); }
278  print(") returned ".($Data ? "\"".$Data."\"" : "NULL")."<br>\n");
279  }
280  return $Data;
281  }
282 
283  # retrieve specified attribute(s) from current seek point or specified point below
284  # (first arg is attribute name and optional subsequent args tell where to seek to)
285  # (returns NULL if no such attribute for current or specified tag)
286  function GetAttribute()
287  {
288  # retrieve attribute
289  $Args = func_get_args();
290  $Attrib = $this->PerformGetAttribute($Args, FALSE);
291 
292  # return requested attribute to caller
293  if ($this->DebugLevel > 0) { print("XMLParser->GetAttribute() returned ".$Attrib."<br>\n"); }
294  return $Attrib;
295  }
296  function GetAttributes()
297  {
298  # retrieve attribute
299  $Args = func_get_args();
300  $Attribs = $this->PerformGetAttribute($Args, TRUE);
301 
302  # return requested attribute to caller
303  if ($this->DebugLevel > 0) { print("XMLParser->GetAttributes() returned ".count($Attribs)." attributes<br>\n"); }
304  return $Attribs;
305  }
306 
307 
308  # ---- PRIVATE INTERFACE -------------------------------------------------
309 
312  var $TagData;
321 
322  # set current debug output level (0-9)
323  function SetDebugLevel($NewLevel)
324  {
325  $this->DebugLevel = $NewLevel;
326  }
327 
328  # callback function for handling open tags
329  function OpenTag($Parser, $ElementName, $ElementAttribs)
330  {
331  # add new tag to list
332  $NewTagIndex = count($this->TagNames);
333  $this->TagNames[$NewTagIndex] = $ElementName;
334  $this->TagAttribs[$NewTagIndex] = $ElementAttribs;
335  $this->TagParents[$NewTagIndex] = $this->CurrentParseIndex;
336  $this->TagData[$NewTagIndex] = NULL;
337 
338  # set current tag to new tag
339  $this->CurrentParseIndex = $NewTagIndex;
340  }
341 
342  # callback function for receiving data between tags
343  function ReceiveData($Parser, $Data)
344  {
345  # add data to currently open tag
346  $this->TagData[$this->CurrentParseIndex] .= $Data;
347  }
348 
349  # callback function for handling close tags
350  function CloseTag($Parser, $ElementName)
351  {
352  # if we have an open tag and closing tag matches currently open tag
353  if (($this->CurrentParseIndex >= 0)
354  && ($ElementName == $this->TagNames[$this->CurrentParseIndex]))
355  {
356  # set current tag to parent tag
357  $this->CurrentParseIndex = $this->TagParents[$this->CurrentParseIndex];
358  }
359  }
360 
361  # perform seek to point in tag tree and update seek pointer (if requested)
362  function PerformSeek($SeekArgs, $MoveSeekPointer)
363  {
364  # for each tag name or index in argument list
365  $NewSeekIndex = $this->CurrentSeekIndex;
366  foreach ($SeekArgs as $Arg)
367  {
368  # if argument is string
369  if (is_string($Arg))
370  {
371  # look for tags with given name and current tag as parent
372  $Arg = strtoupper($Arg);
373  if (!isset($this->NameKeyCache[$Arg]))
374  {
375  $this->NameKeyCache[$Arg] = array_keys($this->TagNames, $Arg);
376  $TestArray = array_keys($this->TagNames, $Arg);
377  }
378  $ChildTags = array_keys($this->TagParents, $NewSeekIndex);
379  $NewItemList = array_values(
380  array_intersect($this->NameKeyCache[$Arg], $ChildTags));
381  $NewItemCount = count($NewItemList);
382 
383  # if matching tag found
384  if ($NewItemCount > 0)
385  {
386  # update local seek index
387  $NewSeekIndex = $NewItemList[0];
388 
389  # save new item index
390  $NewItemIndex = 0;
391  }
392  else
393  {
394  # report seek failure to caller
395  return NULL;
396  }
397  }
398  else
399  {
400  # look for tags with same name and same parent as current tag
401  $NameTags = array_keys($this->TagNames, $this->TagNames[$NewSeekIndex]);
402  $ChildTags = array_keys($this->TagParents, $this->TagParents[$NewSeekIndex]);
403  $NewItemList = array_values(array_intersect($NameTags, $ChildTags));
404  $NewItemCount = count($NewItemList);
405 
406  # if enough matching tags were found to contain requested index
407  if ($NewItemCount > $Arg)
408  {
409  # update local seek index
410  $NewSeekIndex = $NewItemList[$Arg];
411 
412  # save new item index
413  $NewItemIndex = $Arg;
414  }
415  else
416  {
417  # report seek failure to caller
418  return NULL;
419  }
420  }
421  }
422 
423  # if caller requested that seek pointer be moved to reflect seek
424  if ($MoveSeekPointer)
425  {
426  # update seek index
427  $this->CurrentSeekIndex = $NewSeekIndex;
428 
429  # update item index and list
430  $this->CurrentItemIndex = $NewItemIndex;
431  $this->CurrentItemList = $NewItemList;
432  $this->CurrentItemCount = $NewItemCount;
433  }
434 
435  # return index of found seek
436  return $NewSeekIndex;
437  }
438 
439  function PerformGetAttribute($Args, $GetMultiple)
440  {
441  # assume that we will not be able to retrieve attribute
442  $ReturnVal = NULL;
443 
444  # retrieve attribute name and (possibly) seek arguments
445  if (!$GetMultiple)
446  {
447  $AttribName = strtoupper(array_shift($Args));
448  }
449 
450  # if arguments were supplied
451  if (count($Args))
452  {
453  # retrieve index for specified point
454  $Index = $this->PerformSeek($Args, FALSE);
455 
456  # if valid index was found
457  if ($Index !== NULL)
458  {
459  # if specified attribute exists
460  if (isset($this->TagAttribs[$Index][$AttribName]))
461  {
462  # retrieve attribute(s) at index to be returned to caller
463  if ($GetMultiple)
464  {
465  $ReturnVal = $this->TagAttribs[$Index];
466  }
467  else
468  {
469  $ReturnVal = $this->TagAttribs[$Index][$AttribName];
470  }
471  }
472  }
473  }
474  else
475  {
476  # if current seek index points to valid tag
477  if ($this->CurrentSeekIndex >= 0)
478  {
479  # if specified attribute exists
480  if (isset($this->TagAttribs[$this->CurrentSeekIndex][$AttribName]))
481  {
482  # retrieve attribute(s) to be returned to caller
483  if ($GetMultiple)
484  {
485  $ReturnVal = $this->TagAttribs[$this->CurrentSeekIndex];
486  }
487  else
488  {
489  $ReturnVal = $this->TagAttribs[$this->CurrentSeekIndex][$AttribName];
490  }
491  }
492  }
493  }
494 
495  # return requested attribute to caller
496  return $ReturnVal;
497  }
498 
499  # rebuild internal list of tags with the same tag name and same parent as current
500  function RebuildItemList()
501  {
502  # get list of tags with the same parent as current tag
503  $SameParentTags = array_keys($this->TagParents,
504  $this->TagParents[$this->CurrentSeekIndex]);
505 
506  # get list of tags with the same name as current tag
507  $SameNameTags = array_keys($this->TagNames,
508  $this->TagNames[$this->CurrentSeekIndex]);
509 
510  # intersect lists to get tags with both same name and same parent as current
511  $this->CurrentItemList = array_values(
512  array_intersect($SameNameTags, $SameParentTags));
513 
514  # find and save index of current tag within item list
515  $this->CurrentItemIndex = array_search(
516  $this->CurrentSeekIndex, $this->CurrentItemList);
517 
518  # save length of item list
519  $this->CurrentItemCount = count($this->CurrentItemList);
520  }
521 
522  # internal method for debugging
524  {
525  foreach ($this->TagNames as $Index => $Name)
526  {
527  printf("[%03d] %-12.12s %03d %-30.30s \n", $Index, $Name, $this->TagParents[$Index], trim($this->TagData[$Index]));
528  }
529  }
530 }
531 
532 
533 ?>