CWIS Developer Documentation
XMLParser.php
Go to the documentation of this file.
1 <?PHP
2 #
3 # FILE: XMLParser.php
4 #
5 # Part of the ScoutLib application support library
6 # Copyright 2005-2016 Edward Almasy and Internet Scout Research Group
7 # http://scout.wisc.edu
8 #
9 
10 class XMLParser
11 {
12  # ---- PUBLIC INTERFACE --------------------------------------------------
13 
18  public function __construct($Encoding="UTF-8")
19  {
20  # set default debug output level
21  $this->DebugLevel = 0;
22 
23  # create XML parser and tell it about our methods
24  $this->Parser = xml_parser_create($Encoding);
25  xml_set_object($this->Parser, $this);
26  xml_set_element_handler($this->Parser, "OpenTag", "CloseTag");
27  xml_set_character_data_handler($this->Parser, "ReceiveData");
28 
29  # initialize tag storage arrays
30  $this->TagNames = array();
31  $this->TagAttribs = array();
32  $this->TagData = array();
33  $this->TagParents = array();
34 
35  # initialize indexes for parsing and retrieving data
36  $this->CurrentParseIndex = -1;
37  $this->CurrentSeekIndex = -1;
38  $this->NameKeyCache = array();
39  }
40 
46  public function ParseText($Text, $LastTextToParse = TRUE)
47  {
48  # pass text to PHP XML parser
49  xml_parse($this->Parser, $Text, $LastTextToParse);
50  }
51 
57  public function SeekTo()
58  {
59  # perform seek based on arguments passed by caller
60  $SeekResult = $this->PerformSeek(func_get_args(), TRUE);
61 
62  # if seek successful
63  if ($SeekResult !== NULL)
64  {
65  # retrieve item count at seek location
66  $ItemCount = count($this->CurrentItemList);
67  }
68  else
69  {
70  # return null value to indicate that seek failed
71  $ItemCount = NULL;
72  }
73 
74  # return count of tags found at requested location
75  if ($this->DebugLevel > 0)
76  {
77  print("XMLParser->SeekTo(");
78  $Sep = "";
79  $DbugArgList = "";
80  foreach (func_get_args() as $Arg)
81  {
82  $DbugArgList .= $Sep."\"".$Arg."\"";
83  $Sep = ", ";
84  }
85  print($DbugArgList.") returned ".intval($ItemCount)
86  ." items starting at index ".$this->CurrentSeekIndex."\n");
87  }
88  return $ItemCount;
89  }
90 
95  public function SeekToParent()
96  {
97  # if we are not at the root of the tree
98  if ($this->CurrentSeekIndex >= 0)
99  {
100  # move up one level in tree
101  $this->CurrentSeekIndex = $this->TagParents[$this->CurrentSeekIndex];
102 
103  # clear item list
104  unset($this->CurrentItemList);
105 
106  # return name of new tag to caller
107  $Result = $this->TagNames[$this->CurrentSeekIndex];
108  }
109  else
110  {
111  # return NULL indicating that no parent was found
112  $Result = NULL;
113  }
114 
115  # return result to caller
116  if ($this->DebugLevel > 0)
117  {
118  print("XMLParser->SeekToParent() returned ".$Result."<br>\n");
119  }
120  return $Result;
121  }
122 
128  public function SeekToChild($ChildIndex = 0)
129  {
130  # look for tags with current tag as parent
131  $ChildTags = array_keys($this->TagParents, $this->CurrentSeekIndex);
132 
133  # if child tag was found with requested index
134  if (isset($ChildTags[$ChildIndex]))
135  {
136  # set current seek index to child
137  $this->CurrentSeekIndex = $ChildTags[$ChildIndex];
138 
139  # clear item list info
140  unset($this->CurrentItemList);
141 
142  # return name of new tag to caller
143  $Result = $this->TagNames[$this->CurrentSeekIndex];
144  }
145  else
146  {
147  # return NULL indicating that no children were found
148  $Result = NULL;
149  }
150 
151  # return result to caller
152  if ($this->DebugLevel > 0)
153  {
154  print("XMLParser->SeekToChild() returned ".$Result."<br>\n");
155  }
156  return $Result;
157  }
158 
162  public function SeekToRoot()
163  {
164  $this->CurrentSeekIndex = -1;
165  }
166 
171  public function NextTag()
172  {
173  # get list of tags with same parent as this tag
174  $LevelTags = array_keys($this->TagParents,
175  $this->TagParents[$this->CurrentSeekIndex]);
176 
177  # find position of next tag in list
178  $NextTagPosition = array_search($this->CurrentSeekIndex, $LevelTags) + 1;
179 
180  # if there is a next tag
181  if (count($LevelTags) > $NextTagPosition)
182  {
183  # move seek pointer to next tag at this level
184  $this->CurrentSeekIndex = $LevelTags[$NextTagPosition];
185 
186  # rebuild item list
187 
188  # return name of tag at new position to caller
189  return $this->TagNames[$this->CurrentSeekIndex];
190  }
191  else
192  {
193  # return NULL to caller to indicate no next tag
194  return NULL;
195  }
196  }
197 
202  public function NextItem()
203  {
204  # set up item list if necessary
205  if (!isset($this->CurrentItemList)) { $this->RebuildItemList(); }
206 
207  # if there are items left to move to
208  if ($this->CurrentItemIndex < ($this->CurrentItemCount - 1))
209  {
210  # move item pointer to next item
211  $this->CurrentItemIndex++;
212 
213  # set current seek pointer to next item
214  $this->CurrentSeekIndex =
215  $this->CurrentItemList[$this->CurrentItemIndex];
216 
217  # return new item index to caller
218  $Result = $this->CurrentItemIndex;
219  }
220  else
221  {
222  # return NULL value to caller to indicate failure
223  $Result = NULL;
224  }
225 
226  # return result to caller
227  return $Result;
228  }
229 
234  public function PreviousItem()
235  {
236  # set up item list if necessary
237  if (!isset($this->CurrentItemList)) { $this->RebuildItemList(); }
238 
239  # if we are not at the first item
240  if ($this->CurrentItemIndex > 0)
241  {
242  # move item pointer to previous item
243  $this->CurrentItemIndex--;
244 
245  # set current seek pointer to next item
246  $this->CurrentSeekIndex =
247  $this->CurrentItemList[$this->CurrentItemIndex];
248 
249  # return new item index to caller
250  return $this->CurrentItemIndex;
251  }
252  else
253  {
254  # return NULL value to caller to indicate failure
255  return NULL;
256  }
257  }
258 
263  public function GetTagName()
264  {
265  if (isset($this->TagNames[$this->CurrentSeekIndex]))
266  {
267  return $this->TagNames[$this->CurrentSeekIndex];
268  }
269  else
270  {
271  return NULL;
272  }
273  }
274 
280  public function GetData()
281  {
282  # assume that we will not be able to retrieve data
283  $Data = NULL;
284 
285  # if arguments were supplied
286  if (func_num_args())
287  {
288  # retrieve index for specified point
289  $Index = $this->PerformSeek(func_get_args(), FALSE);
290 
291  # if valid index was found
292  if ($Index !== NULL)
293  {
294  # retrieve data at index to be returned to caller
295  $Data = $this->TagData[$Index];
296  }
297  }
298  else
299  {
300  # if current seek index points to valid tag
301  if ($this->CurrentSeekIndex >= 0)
302  {
303  # retrieve data to be returned to caller
304  $Data = $this->TagData[$this->CurrentSeekIndex];
305  }
306  }
307 
308  # return data to caller
309  if ($this->DebugLevel > 0)
310  {
311  print("XMLParser->GetData(");
312  if (func_num_args())
313  {
314  $ArgString = "";
315  foreach (func_get_args() as $Arg)
316  {
317  $ArgString .= "\"".$Arg."\", ";
318  }
319  $ArgString = substr($ArgString, 0, strlen($ArgString) - 2);
320  print($ArgString);
321  }
322  print(") returned ".($Data ? "\"".$Data."\"" : "NULL")."<br>\n");
323  }
324  return $Data;
325  }
326 
333  public function GetAttribute()
334  {
335  # retrieve attribute
336  $Args = func_get_args();
337  $Attrib = $this->PerformGetAttribute($Args, FALSE);
338 
339  # return requested attribute to caller
340  if ($this->DebugLevel > 0)
341  {
342  print("XMLParser->GetAttribute() returned ".$Attrib."<br>\n");
343  }
344  return $Attrib;
345  }
346 
353  public function GetAttributes()
354  {
355  # retrieve attribute
356  $Args = func_get_args();
357  $Attribs = $this->PerformGetAttribute($Args, TRUE);
358 
359  # return requested attribute to caller
360  if ($this->DebugLevel > 0)
361  {
362  print("XMLParser->GetAttributes() returned ".count($Attribs)
363  ." attributes<br>\n");
364  }
365  return $Attribs;
366  }
367 
368 
369  # ---- PRIVATE INTERFACE -------------------------------------------------
370 
371  private $TagNames;
372  private $TagAttribs;
373  private $TagData;
374  private $TagParents;
375  private $CurrentParseIndex;
376  private $CurrentSeekIndex;
377  private $CurrentItemIndex;
378  private $CurrentItemList;
379  private $CurrentItemCount;
380  private $DebugLevel;
381  private $NameKeyCache;
382 
387  private function SetDebugLevel($NewLevel)
388  {
389  $this->DebugLevel = $NewLevel;
390  }
391 
398  private function OpenTag($Parser, $ElementName, $ElementAttribs)
399  {
400  # add new tag to list
401  $NewTagIndex = count($this->TagNames);
402  $this->TagNames[$NewTagIndex] = $ElementName;
403  $this->TagAttribs[$NewTagIndex] = $ElementAttribs;
404  $this->TagParents[$NewTagIndex] = $this->CurrentParseIndex;
405  $this->TagData[$NewTagIndex] = NULL;
406 
407  # set current tag to new tag
408  $this->CurrentParseIndex = $NewTagIndex;
409  }
410 
416  private function ReceiveData($Parser, $Data)
417  {
418  # add data to currently open tag
419  $this->TagData[$this->CurrentParseIndex] .= $Data;
420  }
421 
427  private function CloseTag($Parser, $ElementName)
428  {
429  # if we have an open tag and closing tag matches currently open tag
430  if (($this->CurrentParseIndex >= 0)
431  && ($ElementName == $this->TagNames[$this->CurrentParseIndex]))
432  {
433  # set current tag to parent tag
434  $this->CurrentParseIndex = $this->TagParents[$this->CurrentParseIndex];
435  }
436  }
437 
444  private function PerformSeek($SeekArgs, $MoveSeekPointer)
445  {
446  # for each tag name or index in argument list
447  $NewSeekIndex = $this->CurrentSeekIndex;
448  foreach ($SeekArgs as $Arg)
449  {
450  # if argument is string
451  if (is_string($Arg))
452  {
453  # look for tags with given name and current tag as parent
454  $Arg = strtoupper($Arg);
455  if (!isset($this->NameKeyCache[$Arg]))
456  {
457  $this->NameKeyCache[$Arg] = array_keys($this->TagNames, $Arg);
458  $TestArray = array_keys($this->TagNames, $Arg);
459  }
460  $ChildTags = array_keys($this->TagParents, $NewSeekIndex);
461  $NewItemList = array_values(
462  array_intersect($this->NameKeyCache[$Arg], $ChildTags));
463  $NewItemCount = count($NewItemList);
464 
465  # if matching tag found
466  if ($NewItemCount > 0)
467  {
468  # update local seek index
469  $NewSeekIndex = $NewItemList[0];
470 
471  # save new item index
472  $NewItemIndex = 0;
473  }
474  else
475  {
476  # report seek failure to caller
477  return NULL;
478  }
479  }
480  else
481  {
482  # look for tags with same name and same parent as current tag
483  $NameTags = array_keys($this->TagNames,
484  $this->TagNames[$NewSeekIndex]);
485  $ChildTags = array_keys($this->TagParents,
486  $this->TagParents[$NewSeekIndex]);
487  $NewItemList = array_values(array_intersect($NameTags, $ChildTags));
488  $NewItemCount = count($NewItemList);
489 
490  # if enough matching tags were found to contain requested index
491  if ($NewItemCount > $Arg)
492  {
493  # update local seek index
494  $NewSeekIndex = $NewItemList[$Arg];
495 
496  # save new item index
497  $NewItemIndex = $Arg;
498  }
499  else
500  {
501  # report seek failure to caller
502  return NULL;
503  }
504  }
505  }
506 
507  # if caller requested that seek pointer be moved to reflect seek
508  if ($MoveSeekPointer)
509  {
510  # update seek index
511  $this->CurrentSeekIndex = $NewSeekIndex;
512 
513  # update item index and list
514  $this->CurrentItemIndex = $NewItemIndex;
515  $this->CurrentItemList = $NewItemList;
516  $this->CurrentItemCount = $NewItemCount;
517  }
518 
519  # return index of found seek
520  return $NewSeekIndex;
521  }
522 
530  private function PerformGetAttribute($Args, $GetMultiple)
531  {
532  # assume that we will not be able to retrieve attribute
533  $ReturnVal = NULL;
534 
535  # retrieve attribute name and (possibly) seek arguments
536  if (!$GetMultiple)
537  {
538  $AttribName = strtoupper(array_shift($Args));
539  }
540 
541  # if arguments were supplied
542  if (count($Args))
543  {
544  # retrieve index for specified point
545  $Index = $this->PerformSeek($Args, FALSE);
546 
547  # if valid index was found
548  if ($Index !== NULL)
549  {
550  # if specified attribute exists
551  if (isset($this->TagAttribs[$Index][$AttribName]))
552  {
553  # retrieve attribute(s) at index to be returned to caller
554  if ($GetMultiple)
555  {
556  $ReturnVal = $this->TagAttribs[$Index];
557  }
558  else
559  {
560  $ReturnVal = $this->TagAttribs[$Index][$AttribName];
561  }
562  }
563  }
564  }
565  else
566  {
567  # if current seek index points to valid tag
568  $SeekIndex = $this->CurrentSeekIndex;
569  if ($SeekIndex >= 0)
570  {
571  # if specified attribute exists
572  if (isset($this->TagAttribs[$SeekIndex][$AttribName]))
573  {
574  # retrieve attribute(s) to be returned to caller
575  if ($GetMultiple)
576  {
577  $ReturnVal = $this->TagAttribs[$SeekIndex];
578  }
579  else
580  {
581  $ReturnVal = $this->TagAttribs[$SeekIndex][$AttribName];
582  }
583  }
584  }
585  }
586 
587  # return requested attribute to caller
588  return $ReturnVal;
589  }
590 
595  private function RebuildItemList()
596  {
597  # get list of tags with the same parent as current tag
598  $SameParentTags = array_keys($this->TagParents,
599  $this->TagParents[$this->CurrentSeekIndex]);
600 
601  # get list of tags with the same name as current tag
602  $SameNameTags = array_keys($this->TagNames,
603  $this->TagNames[$this->CurrentSeekIndex]);
604 
605  # intersect lists to get tags with both same name and same parent as current
606  $this->CurrentItemList = array_values(
607  array_intersect($SameNameTags, $SameParentTags));
608 
609  # find and save index of current tag within item list
610  $this->CurrentItemIndex = array_search(
611  $this->CurrentSeekIndex, $this->CurrentItemList);
612 
613  # save length of item list
614  $this->CurrentItemCount = count($this->CurrentItemList);
615  }
616 
620  private function DumpInternalArrays()
621  {
622  foreach ($this->TagNames as $Index => $Name)
623  {
624  printf("[%03d] %-12.12s %03d %-30.30s \n", $Index, $Name,
625  $this->TagParents[$Index], trim($this->TagData[$Index]));
626  }
627  }
628 }
629 
SeekToRoot()
Move seek pointer to root of tree.
Definition: XMLParser.php:162
__construct($Encoding="UTF-8")
Object constructor.
Definition: XMLParser.php:18
NextItem()
Move to next instance of current tag.
Definition: XMLParser.php:202
GetTagName()
Retrieve tag name from current seek point.
Definition: XMLParser.php:263
ParseText($Text, $LastTextToParse=TRUE)
Parse text stream and store result.
Definition: XMLParser.php:46
GetAttributes()
Retrieve specified attributes from current seek point or specified point below.
Definition: XMLParser.php:353
GetData()
Retrieve data from current seek point.
Definition: XMLParser.php:280
SeekTo()
Move current tag pointer to specified item.
Definition: XMLParser.php:57
SeekToParent()
Move seek pointer up one level.
Definition: XMLParser.php:95
GetAttribute()
Retrieve specified attribute from current seek point or specified point below.
Definition: XMLParser.php:333
SeekToChild($ChildIndex=0)
Move seek pointer to first child of current tag.
Definition: XMLParser.php:128
PreviousItem()
Move to previous instance of current tag.
Definition: XMLParser.php:234
NextTag()
Move to next tag at current level.
Definition: XMLParser.php:171