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 / UniqueNode.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
UniqueNode.php
274 lines
1 <?php
2 /**
3 * xml-string-streamer UniqueNode parser
4 *
5 * @package xml-string-streamer
6 * @author Oskar Thornblad <oskar.thornblad@gmail.com>
7 * @author Roman Voloboev <animir@ya.ru>
8 */
9
10 namespace Prewk\XmlStringStreamer\Parser;
11
12 use Exception;
13 use Prewk\XmlStringStreamer\ParserInterface;
14 use Prewk\XmlStringStreamer\StreamInterface;
15
16 /**
17 * The unique node parser starts at a given element name and flushes when its corresponding closing tag is found
18 */
19 class UniqueNode implements ParserInterface
20 {
21 /**
22 * Current working XML blob
23 * @var string
24 */
25 private $workingBlob = "";
26 /**
27 * The flushed node
28 * @var string
29 */
30 private $flushed = "";
31
32 /**
33 * Start position of the given element in the workingBlob
34 * @var integer
35 */
36 private $startPos = 0;
37 /**
38 * Records how far we've searched in the XML blob so far
39 * @var integer
40 */
41 private $hasSearchedUntilPos = -1;
42
43 const FIND_OPENING_TAG_ACTION = 0;
44 const FIND_CLOSING_TAG_ACTION = 1;
45
46 /**
47 * Next action to perform
48 * @var integer
49 */
50 private $nextAction = 0;
51
52 /**
53 * Indicates short closing tag
54 * @var bool
55 */
56
57 private $shortClosedTagNow = false;
58
59 /**
60 * If extractContainer is true, this will grow with the XML captured before and after the specified capture depth
61 * @var string
62 */
63 protected $containerXml = "";
64
65 /**
66 * Whether we're found our first capture target or not
67 * @var bool
68 */
69 protected $preCapture = true;
70
71 /**
72 * Parser constructor
73 * @param array $options An options array
74 * @throws Exception if the required option uniqueNode isn't set
75 */
76 public function __construct(array $options = array())
77 {
78 $this->options = array_merge(array(
79 "extractContainer" => false,
80 ), $options);
81
82 if (!isset($this->options["uniqueNode"])) {
83 throw new Exception("Required option 'uniqueNode' not set");
84 }
85 }
86
87 /**
88 * Search the blob for our unique node's opening tag
89 * @return bool|int Either returns the char position of the opening tag or false
90 */
91 protected function getOpeningTagPos()
92 {
93 $startPositionInBlob = false;
94 if (preg_match("/<" . preg_quote($this->options["uniqueNode"]) . "(>| )/", $this->workingBlob, $matches, PREG_OFFSET_CAPTURE) === 1) {
95 $startPositionInBlob = $matches[0][1];
96 }
97
98
99 if ($startPositionInBlob === false) {
100 $this->hasSearchedUntilPos = strlen($this->workingBlob) - 1;
101 }
102
103 return $startPositionInBlob;
104 }
105
106 /**
107 * Search short closing tag in $workingBlob before
108 *
109 * @param string $workingBlob
110 * @param int $len
111 * @return bool|int Either returns the char position of the short closing tag or false
112 */
113 private function checkShortClosingTag($workingBlob, $len) {
114 $resultEndPositionInBlob = false;
115 while ($len = strpos($workingBlob, "/>", $len + 1)) {
116 $subBlob = substr($workingBlob, $this->startPos, $len + strlen("/>") - $this->startPos);
117 $cntOpen = substr_count($subBlob, "<");
118 $cntClose = substr_count($subBlob, "/>");
119 if ($cntOpen === $cntClose && $cntOpen === 1) {
120 $resultEndPositionInBlob = $len + strlen("/>");
121 break; // end while. so $endPositionInBlob correct now
122 }
123 }
124 return $resultEndPositionInBlob;
125 }
126
127 /**
128 * Search the blob for our unique node's closing tag
129 * @return bool|int Either returns the char position of the closing tag or false
130 */
131 protected function getClosingTagPos()
132 {
133 $endPositionInBlob = strpos($this->workingBlob, "</" . $this->options["uniqueNode"] . ">");
134 if ($endPositionInBlob === false) {
135
136 if (isset($this->options["checkShortClosing"]) && $this->options["checkShortClosing"] === true) {
137 $endPositionInBlob = $this->checkShortClosingTag($this->workingBlob, $this->startPos);
138 }
139
140 if ($endPositionInBlob === false) {
141 $this->hasSearchedUntilPos = strlen($this->workingBlob) - 1;
142 } else {
143 $this->shortClosedTagNow = true;
144 }
145 } else {
146 if (isset($this->options["checkShortClosing"]) && $this->options["checkShortClosing"] === true) {
147 $tmpEndPositionInBlob = $this->checkShortClosingTag(substr($this->workingBlob, 0, $endPositionInBlob), $this->startPos);
148 if ($tmpEndPositionInBlob !== false) {
149 $this->shortClosedTagNow = true;
150 $endPositionInBlob = $tmpEndPositionInBlob;
151 }
152 }
153 }
154
155 return $endPositionInBlob;
156 }
157
158 /**
159 * Set the start position in the workingBlob from where we should start reading when the closing tag is found
160 * @param int $startPositionInBlob Position of starting tag
161 */
162 protected function startSalvaging($startPositionInBlob)
163 {
164 $this->startPos = $startPositionInBlob;
165 }
166
167 /**
168 * Cut everything from the start position to the end position in the workingBlob (+ tag length) and flush it out for later return in getNodeFrom
169 * @param int $endPositionInBlob Position of the closing tag
170 */
171 protected function flush($endPositionInBlob) {
172 $endTagLen = $this->shortClosedTagNow ? 0 : strlen("</" . $this->options["uniqueNode"] . ">");
173 $realEndPosition = $endPositionInBlob + $endTagLen;
174 $this->flushed = substr($this->workingBlob, $this->startPos, $realEndPosition - $this->startPos);
175 $this->workingBlob = substr($this->workingBlob, $realEndPosition);
176 $this->hasSearchedUntilPos = 0;
177 $this->shortClosedTagNow = false;
178 }
179
180 /**
181 * Decides whether we're to fetch more chunks from the stream or keep working with what we have.
182 * @param StreamInterface $stream The stream provider
183 * @return bool Keep working?
184 */
185 protected function prepareChunk(StreamInterface $stream)
186 {
187 if ($this->hasSearchedUntilPos > -1 && $this->hasSearchedUntilPos < (strlen($this->workingBlob) - 1)) {
188 // More work to do
189 return true;
190 }
191
192 $chunk = $stream->getChunk();
193
194 if ($chunk === false) {
195 // EOF
196 if ($this->hasSearchedUntilPos === -1) {
197 // EOF, but we haven't even started searching, special case that probably means we're dealing with a file of less size than the stream buffer
198 // Therefore, keep looping
199 return true;
200 }
201 return false;
202 } else {
203 // New chunk fetched
204 $this->workingBlob .= $chunk;
205 return true;
206 }
207 }
208
209 /**
210 * Tries to retrieve the next node or returns false
211 * @param StreamInterface $stream The stream to use
212 * @return string|bool The next xml node or false if one could not be retrieved
213 */
214 public function getNodeFrom(StreamInterface $stream)
215 {
216 while ($this->prepareChunk($stream)) {
217 // What's our next course of action?
218 if ($this->nextAction === self::FIND_OPENING_TAG_ACTION) {
219 // Try to find an opening tag
220 $positionInBlob = $this->getOpeningTagPos();
221
222 if ($positionInBlob !== false) {
223 // We found it, start salvaging
224 if ($this->options["extractContainer"] && $this->preCapture) {
225 $this->containerXml .= substr($this->workingBlob, 0, $positionInBlob);
226 $this->preCapture = false;
227 }
228
229
230 $this->startSalvaging($positionInBlob);
231
232 // The next course of action will be to find a closing tag
233 $this->nextAction = self::FIND_CLOSING_TAG_ACTION;
234 }
235 } else if ($this->nextAction === self::FIND_CLOSING_TAG_ACTION) {
236 // Try to find a closing tag
237 $positionInBlob = $this->getClosingTagPos();
238 if ($positionInBlob !== false) {
239 // We found it, we now have a full node to flush out
240 $this->flush($positionInBlob);
241
242 // The next course of action will be to find an opening tag
243 $this->nextAction = self::FIND_OPENING_TAG_ACTION;
244
245 // Get the flushed node and make way for the next node
246 $flushed = $this->flushed;
247 $this->flushed = "";
248
249 return $flushed;
250 }
251 }
252 }
253
254 if ($this->options["extractContainer"]) {
255 $this->containerXml .= $this->workingBlob;
256 }
257
258 return false;
259 }
260
261 /**
262 * Get the extracted container XML, if called before the whole stream is parsed, the XML returned can be invalid due to missing closing tags
263 * @return string XML string
264 * @throws Exception if the extractContainer option isn't true
265 */
266 public function getExtractedContainer()
267 {
268 if (!$this->options["extractContainer"]) {
269 throw new Exception("This method requires the 'extractContainer' option to be true");
270 }
271
272 return $this->containerXml;
273 }
274 }