PluginProbe ʕ •ᴥ•ʔ
WP All Import – Drag & Drop Import for CSV, XML, Excel & Google Sheets / trunk
WP All Import – Drag & Drop Import for CSV, XML, Excel & Google Sheets vtrunk
4.1.1 3.9.5 3.9.6 4.0.0 4.0.1 4.1.0 trunk 2.12 2.13 2.14 3.0 3.0.1 3.0.2 3.0.3 3.0.4 3.1.0 3.1.1 3.1.2 3.1.3 3.1.4 3.1.5 3.2.0 3.2.1 3.2.2 3.2.3 3.2.4 3.2.5 3.2.6 3.2.7 3.2.8 3.2.9 3.3.0 3.3.1 3.3.2 3.3.3 3.3.4 3.3.5 3.3.6 3.3.7 3.3.8 3.3.9 3.4.0 3.4.1 3.4.2 3.4.3 3.4.4 3.4.5 3.4.6 3.4.7 3.4.8 3.4.9 3.5.0 3.5.1 3.5.2 3.5.3 3.5.4 3.5.5 3.5.6 3.5.7 3.5.8 3.5.9 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.6.5 3.6.6 3.6.7 3.6.8 3.6.9 3.7.0 3.7.1 3.7.2 3.7.3 3.7.3-beta-1.0 3.7.4 3.7.4-beta-1.0 3.7.5 3.7.6 3.7.7 3.7.8 3.7.9 3.8.0 3.9.0 3.9.1 3.9.2 3.9.3 3.9.4
wp-all-import / classes / XmlStreamReader / prewk / xml-string-streamer / src / XmlStringStreamer / Parser / StringWalker.php
wp-all-import / classes / XmlStreamReader / prewk / xml-string-streamer / src / XmlStringStreamer / Parser Last commit date
StringWalker.php 2 years ago UniqueNode.php 9 years ago
StringWalker.php
275 lines
1 <?php
2 /**
3 * xml-string-streamer StringWalker parser
4 *
5 * @package xml-string-streamer
6 * @author Oskar Thornblad <oskar.thornblad@gmail.com>
7 */
8
9 namespace Prewk\XmlStringStreamer\Parser;
10
11 use Exception;
12 use Prewk\XmlStringStreamer\ParserInterface;
13 use Prewk\XmlStringStreamer\StreamInterface;
14
15 /**
16 * The string walker parser builds the XML nodes by fetching one element at a time until a certain depth is re-reached
17 */
18 class StringWalker implements ParserInterface
19 {
20 /**
21 * Holds the parser configuration
22 * @var array
23 */
24 protected $options;
25
26 /**
27 * Is this the first run?
28 * @var boolean
29 */
30 protected $firstRun = true;
31
32 /**
33 * What depth are we currently at?
34 * @var integer
35 */
36 protected $depth = 0;
37
38 /**
39 * The latest chunk from the stream
40 * @var string
41 */
42 protected $chunk;
43
44 /**
45 * Last XML node in the making, used for anti-freeze detection
46 * @var null|string
47 */
48 protected $lastChunk;
49
50 /**
51 * XML node in the making
52 * @var null|string
53 */
54 protected $shaved = null;
55
56 /**
57 * Whether to capture or not
58 * @var boolean
59 */
60 protected $capture = false;
61
62 /**
63 * If extractContainer is true, this will grow with the XML captured before and after the specified capture depth
64 * @var string
65 */
66 protected $containerXml = "";
67
68 public $cloud = array();
69
70 /**
71 * Parser contructor
72 * @param array $options An options array
73 */
74 public function __construct(array $options = array())
75 {
76 $this->options = array_merge(array(
77 "captureDepth" => 2,
78 "expectGT" => false,
79 "tags" => array(
80 array("<?", "?>", 0),
81 array("<!--", "-->", 0),
82 array("<![CDATA[", "]]>", 0),
83 array("<!", ">", 0),
84 array("</", ">", -1),
85 array("<", "/>", 0),
86 array("<", ">", 1),
87 ),
88 "tagsWithAllowedGT" => array(
89 array("<!--", "-->"),
90 array("<![CDATA[", "]]>"),
91 ),
92 "extractContainer" => false,
93 ), $options);
94 }
95
96 /**
97 * Shaves off the next element from the chunk
98 * @return string[]|bool Either a shaved off element array(0 => Captured element, 1 => Data from last shaving point up to and including captured element) or false if one could not be obtained
99 */
100 protected function shave()
101 {
102 preg_match("/<[^>]+>/", $this->chunk, $matches, PREG_OFFSET_CAPTURE);
103
104 if (isset($matches[0], $matches[0][0], $matches[0][1])) {
105 list($captured, $offset) = $matches[0];
106
107 if ($this->options["expectGT"]) {
108 // Some elements support > inside
109 foreach ($this->options["tagsWithAllowedGT"] as $tag) {
110 list($opening, $closing) = $tag;
111
112 if (substr($captured, 0, strlen($opening)) === $opening) {
113 // We have a match, our preg_match may have ended too early
114 // Most often, this isn't the case
115 if (substr($captured, -1 * strlen($closing)) !== $closing) {
116 // In this case, the preg_match ended too early, let's find the real end
117 $position = strpos($this->chunk, $closing);
118 if ($position === false) {
119 // We need more XML!
120
121 return false;
122 }
123
124 // We found the end, modify $captured
125 $captured = substr($this->chunk, $offset, $position + strlen($closing) - $offset);
126 }
127 }
128 }
129 }
130
131 // Data in between
132 $data = substr($this->chunk, 0, $offset);
133
134 // Shave from chunk
135 $this->chunk = substr($this->chunk, (int)$offset + strlen($captured));
136
137 return array($captured, $data . $captured);
138 }
139
140 return false;
141 }
142
143 /**
144 * Extract XML compatible tag head and tail
145 * @param string $element XML element
146 * @return string[] 0 => Opening tag, 1 => Closing tag
147 */
148 protected function getEdges($element)
149 {
150 // TODO: Performance tuning possible here by not looping
151
152 foreach ($this->options["tags"] as $tag) {
153 list($opening, $closing, $depth) = $tag;
154
155 if (substr($element, 0, strlen($opening)) === $opening
156 && substr($element, -1 * strlen($closing)) === $closing) {
157
158 return $tag;
159 }
160 }
161 }
162
163 /**
164 * The shave method must be able to request more data even though there isn't any more to fetch from the stream, this method wraps the getChunk call so that it returns true as long as there is XML data left
165 * @param StreamInterface $stream The stream to read from
166 * @return bool Returns whether there is more XML data or not
167 */
168 protected function prepareChunk(StreamInterface $stream)
169 {
170 if (!$this->firstRun && is_null($this->shaved)) {
171 // We're starting again after a flush
172 $this->shaved = "";
173
174 return true;
175 } else if (is_null($this->shaved)) {
176 $this->shaved = "";
177 }
178
179 $newChunk = $stream->getChunk();
180
181 if ($newChunk !== false) {
182 $this->chunk .= $newChunk;
183
184 return true;
185 } else {
186 if (trim($this->chunk) !== "" && $this->chunk !== $this->lastChunk) {
187 // Update anti-freeze protection chunk
188 $this->lastChunk = $this->chunk;
189 // Continue
190 return true;
191 }
192 }
193
194 return false;
195 }
196
197 /**
198 * Get the extracted container XML, if called before the whole stream is parsed, the XML returned will most likely be invalid due to missing closing tags
199 * @return string XML string
200 * @throws Exception if the extractContainer option isn't true
201 */
202 public function getExtractedContainer()
203 {
204 if (!$this->options["extractContainer"]) {
205 throw new Exception("This method requires the 'extractContainer' option to be true");
206 }
207
208 return $this->containerXml;
209 }
210
211 /**
212 * Tries to retrieve the next node or returns false
213 * @param StreamInterface $stream The stream to use
214 * @return string|bool The next xml node or false if one could not be retrieved
215 */
216 public function getNodeFrom(StreamInterface $stream)
217 {
218 // Iterate and append to $this->chunk
219 while ($this->prepareChunk($stream)) {
220 $this->firstRun = false;
221 // Shave off elements
222 while ($shaved = $this->shave()) {
223 list($element, $data) = $shaved;
224
225 // Analyze element
226 list($opening, $closing, $depth) = $this->getEdges($element);
227
228 if ( strpos($element, "<![CDATA[") === false && strpos($element, "<?xml") === false && strpos($element, "</") === false && strpos($element, "/>") === false )
229 {
230 $cloud_element = str_replace(array("<", ">"), "", preg_replace("%\s.*%", "", $element));
231 if ( strpos($cloud_element, ":") === false )
232 {
233 if ( isset($this->cloud[$cloud_element])) $this->cloud[$cloud_element]++; else $this->cloud[$cloud_element] = 1;
234 }
235 }
236
237 // Update depth
238 $this->depth += (int)$depth;
239
240 $flush = false;
241
242 // Capture or don't?
243 if ($this->depth === $this->options["captureDepth"] && $depth > 0) {
244 // Yes, we've just entered capture depth, start capturing
245 $this->capture = true;
246 } else if ($this->depth === $this->options["captureDepth"] - 1 && $depth < 0) {
247 // No, we've just exited capture depth, stop capturing and prepare for flush
248 $flush = true;
249 $this->capture = false;
250
251 // ..but include this last node
252 $this->shaved .= $data;
253 } else if ($this->options["extractContainer"] && $this->depth < $this->options["captureDepth"]) {
254 // We're outside of our capture scope, save to the special buffer if extractContainer is true
255 $this->containerXml .= $element;
256 }
257
258 // Capture the last retrieved node
259 if ($this->capture) {
260 $this->shaved .= $data;
261 }
262
263 if ($flush) {
264 // Flush the whole node and start over on the next
265 $flush = $this->shaved;
266 $this->shaved = null;
267
268 return $flush;
269 }
270 }
271 }
272
273 return false;
274 }
275 }