1: <?php
2: //
3: /*******************************************************************************
4: * Location: <strong>xml/SaxParser.class</strong><br>
5: * <br>
6: * Provides basic functionality to read and parse XML documents. Subclasses
7: * must implement all the their custom handlers by using add* function methods.
8: * They may also use the handle*() methods to parse a specific XML begin and end
9: * tags, but this is not recommended as it is more difficult.<br>
10: * <br>
11: * Copyright © 2001 eXtremePHP. All rights reserved.<br>
12: * <br>
13: * @author Ken Egervari<br>
14: *******************************************************************************/
15: class SaxParser
16: {
17: public $level;
18: public $parser;
19:
20: public $isCaseFolding;
21: public $targetEncoding;
22:
23: /* Custom Handler Variables */
24: public $tagHandlers = array();
25:
26: /* Tag stack */
27: public $tags = array();
28:
29: /* Xml Source Input */
30: public $xmlInput;
31:
32: public $errors = array();
33:
34: /****************************************************************************
35: * Creates a SaxParser object using a FileInput to represent the stream
36: * of XML data to parse. Use the static methods createFileInput or
37: * createStringInput to construct xml input source objects to supply
38: * to the constructor, or the implementor can construct them individually.
39: ***************************************************************************
40: * @param $input
41: */
42: public function __construct($input)
43: {
44: $this->level = 0;
45: $this->parser = xml_parser_create('UTF-8');
46: xml_set_object($this->parser, $this);
47: $this->input = $input;
48: $this->setCaseFolding(false);
49: $this->useUtfEncoding();
50: xml_set_element_handler($this->parser, 'handleBeginElement', 'handleEndElement');
51: xml_set_character_data_handler($this->parser, 'handleCharacterData');
52: xml_set_processing_instruction_handler($this->parser, 'handleProcessingInstruction');
53: xml_set_default_handler($this->parser, 'handleDefault');
54: xml_set_unparsed_entity_decl_handler($this->parser, 'handleUnparsedEntityDecl');
55: xml_set_notation_decl_handler($this->parser, 'handleNotationDecl');
56: xml_set_external_entity_ref_handler($this->parser, 'handleExternalEntityRef');
57: }
58:
59: /*---------------------------------------------------------------------------
60: Property Methods
61: ---------------------------------------------------------------------------*/
62:
63: /**
64: * @return int
65: */
66: public function getCurrentLevel()
67: {
68: return $this->level;
69: }
70:
71: /****************************************************************************
72: * @param $isCaseFolding
73: * @returns void
74: ****************************************************************************/
75: public function setCaseFolding($isCaseFolding)
76: {
77: assert(is_bool($isCaseFolding));
78:
79: $this->isCaseFolding = $isCaseFolding;
80: xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, $this->isCaseFolding);
81: }
82:
83: /****************************************************************************
84: * @returns void
85: ****************************************************************************/
86: public function useIsoEncoding()
87: {
88: $this->targetEncoding = 'ISO-8859-1';
89: xml_parser_set_option($this->parser, XML_OPTION_TARGET_ENCODING, $this->targetEncoding);
90: }
91:
92: /****************************************************************************
93: * @returns void
94: ****************************************************************************/
95: public function useAsciiEncoding()
96: {
97: $this->targetEncoding = 'US-ASCII';
98: xml_parser_set_option($this->parser, XML_OPTION_TARGET_ENCODING, $this->targetEncoding);
99: }
100:
101: /****************************************************************************
102: * @returns void
103: ****************************************************************************/
104: public function useUtfEncoding()
105: {
106: $this->targetEncoding = 'UTF-8';
107: xml_parser_set_option($this->parser, XML_OPTION_TARGET_ENCODING, $this->targetEncoding);
108: }
109:
110: /****************************************************************************
111: * Returns the name of the xml tag being parsed
112: * @returns string
113: ****************************************************************************/
114: public function getCurrentTag()
115: {
116: return $this->tags[count($this->tags) - 1];
117: }
118:
119: /**
120: * @return bool
121: */
122: public function getParentTag()
123: {
124: if (isset($this->tags[count($this->tags) - 2])) {
125: return $this->tags[count($this->tags) - 2];
126: }
127:
128: return false;
129: }
130:
131: /*---------------------------------------------------------------------------
132: Parser methods
133: ---------------------------------------------------------------------------*/
134:
135: /****************************************************************************
136: ****************************************************************************/
137: public function parse()
138: {
139: if (!is_resource($this->input)) {
140: if (!xml_parse($this->parser, $this->input)) {
141: $this->setErrors($this->getXmlError());
142:
143: return false;
144: }
145: //if (!$fp = fopen($this->input, 'r')) {
146: // $this->setErrors('Could not open file: '.$this->input);
147: // return false;
148: //}
149: } else {
150: while ($data = fread($this->input, 4096)) {
151: if (!xml_parse($this->parser, str_replace("'", ''', $data), feof($this->input))) {
152: $this->setErrors($this->getXmlError());
153: fclose($this->input);
154:
155: return false;
156: }
157: }
158: fclose($this->input);
159: }
160:
161: return true;
162: }
163:
164: /****************************************************************************
165: * @returns void
166: ****************************************************************************/
167: public function free()
168: {
169: xml_parser_free($this->parser);
170: }
171:
172: /****************************************************************************
173: * @private
174: * @returns string
175: ****************************************************************************/
176: public function getXmlError()
177: {
178: return sprintf('XmlParse error: %s at line %d', xml_error_string(xml_get_error_code($this->parser)), xml_get_current_line_number($this->parser));
179: }
180:
181: /*---------------------------------------------------------------------------
182: Custom Handler Methods
183: ---------------------------------------------------------------------------*/
184:
185: /*
186: * Adds a callback function to be called when a tag is encountered.<br>
187: * Functions that are added must be of the form:<br>
188: * <strong>functionName( $attributes )</strong>
189: * @param $tagHandler
190: *
191: * @param $tagName string. The name of the tag currently being parsed.
192: * @param $functionName string. The name of the function in XmlDocument's subclass.
193: * @returns void
194: *
195: */
196: /**
197: * @param $tagHandler
198: */
199: public function addTagHandler($tagHandler)
200: {
201: $name = $tagHandler->getName();
202: if (is_array($name)) {
203: foreach ($name as $n) {
204: $this->tagHandlers[$n] = $tagHandler;
205: }
206: } else {
207: $this->tagHandlers[$name] = $tagHandler;
208: }
209: }
210:
211: /*---------------------------------------------------------------------------
212: Private Handler Methods
213: ---------------------------------------------------------------------------*/
214:
215: /****************************************************************************
216: * Callback function that executes whenever a the start of a tag
217: * occurs when being parsed.
218: * @param int $parser The handle to the parser.
219: * @param string $tagName The name of the tag currently being parsed.
220: * @param array $attributesArray The list of attributes associated with the tag.
221: * @private
222: * @returns void
223: */
224: public function handleBeginElement($parser, $tagName, $attributesArray)
225: {
226: $this->tags[] = $tagName;
227: $this->level++;
228: if (isset($this->tagHandlers[$tagName]) && is_subclass_of($this->tagHandlers[$tagName], 'xmltaghandler')) {
229: $this->tagHandlers[$tagName]->handleBeginElement($this, $attributesArray);
230: } else {
231: $this->handleBeginElementDefault($parser, $tagName, $attributesArray);
232: }
233: }
234:
235: /****************************************************************************
236: * Callback function that executes whenever the end of a tag
237: * occurs when being parsed.
238: * @param int $parser The handle to the parser.
239: * @param string $tagName The name of the tag currently being parsed.
240: * @private
241: * @returns void
242: */
243: public function handleEndElement($parser, $tagName)
244: {
245: array_pop($this->tags);
246: if (isset($this->tagHandlers[$tagName]) && is_subclass_of($this->tagHandlers[$tagName], 'xmltaghandler')) {
247: $this->tagHandlers[$tagName]->handleEndElement($this);
248: } else {
249: $this->handleEndElementDefault($parser, $tagName);
250: }
251: $this->level--;
252: }
253:
254: /****************************************************************************
255: * Callback function that executes whenever character data is encountered
256: * while being parsed.
257: * @param int $parser The handle to the parser.
258: * @param string $data Character data inside the tag
259: * @returns void
260: */
261: public function handleCharacterData($parser, $data)
262: {
263: $tagHandler =& $this->tagHandlers[$this->getCurrentTag()];
264: if (isset($tagHandler) && is_subclass_of($tagHandler, 'xmltaghandler')) {
265: $tagHandler->handleCharacterData($this, $data);
266: } else {
267: $this->handleCharacterDataDefault($parser, $data);
268: }
269: }
270:
271: /****************************************************************************
272: * @param int $parser The handle to the parser.
273: * @param $target
274: * @param $data
275: * @returns void
276: */
277: public function handleProcessingInstruction($parser, &$target, &$data)
278: {
279: // if ($target == 'php') {
280: // eval($data);
281: // }
282: }
283:
284: /****************************************************************************
285: * @param $parser int. The handle to the parser.
286: * @param $data
287: * @returns void
288: */
289: public function handleDefault($parser, $data)
290: {
291: }
292:
293: /****************************************************************************
294: * @param int $parser The handle to the parser.
295: * @param $entityName
296: * @param $base
297: * @param $systemId
298: * @param $publicId
299: * @param $notationName
300: * @returns void
301: *
302: */
303: public function handleUnparsedEntityDecl($parser, $entityName, $base, $systemId, $publicId, $notationName)
304: {
305: }
306:
307: /****************************************************************************
308: * @param int $parser The handle to the parser.
309: * @param $notationName
310: * @param $base
311: * @param $systemId
312: * @param $publicId
313: * @returns void
314: */
315: public function handleNotationDecl($parser, $notationName, $base, $systemId, $publicId)
316: {
317: }
318:
319: /****************************************************************************
320: * @param int $parser The handle to the parser.
321: * @param $openEntityNames
322: * @param $base
323: * @param $systemId
324: * @param $publicId
325: * @returns void
326: */
327: public function handleExternalEntityRef($parser, $openEntityNames, $base, $systemId, $publicId)
328: {
329: }
330:
331: /**
332: * The default tag handler method for a tag with no handler
333: *
334: * @abstract
335: * @param $parser
336: * @param $tagName
337: * @param $attributesArray
338: */
339: public function handleBeginElementDefault($parser, $tagName, $attributesArray)
340: {
341: }
342:
343: /**
344: * The default tag handler method for a tag with no handler
345: *
346: * @abstract
347: * @param $parser
348: * @param $tagName
349: */
350: public function handleEndElementDefault($parser, $tagName)
351: {
352: }
353:
354: /**
355: * The default tag handler method for a tag with no handler
356: *
357: * @abstract
358: * @param $parser
359: * @param $data
360: */
361: public function handleCharacterDataDefault($parser, $data)
362: {
363: }
364:
365: /**
366: * Sets error messages
367: *
368: * @param $error string an error message
369: */
370: public function setErrors($error)
371: {
372: $this->errors[] = trim($error);
373: }
374:
375: /**
376: * Gets all the error messages
377: *
378: * @param bool $ashtml return as html?
379: * @return mixed
380: */
381: public function &getErrors($ashtml = true)
382: {
383: if (!$ashtml) {
384: return $this->errors;
385: } else {
386: $ret = '';
387: if (count($this->errors) > 0) {
388: foreach ($this->errors as $error) {
389: $ret .= $error . '<br>';
390: }
391: }
392:
393: return $ret;
394: }
395: }
396: }
397: