Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: KML Driver
4 : * Purpose: Class for reading, parsing and handling a kmlfile.
5 : * Author: Jens Oberender, j.obi@troja.net
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2007, Jens Oberender
9 : * Copyright (c) 2008-2013, Even Rouault <even dot rouault at spatialys.com>
10 : *
11 : * SPDX-License-Identifier: MIT
12 : ****************************************************************************/
13 : #include "kmlnode.h"
14 : #include "kml.h"
15 :
16 : #include <cstring>
17 : #include <cstdio>
18 : #include <exception>
19 : #include <iostream>
20 : #include <string>
21 :
22 : #include "cpl_conv.h"
23 : #include "cpl_error.h"
24 : #ifdef HAVE_EXPAT
25 : #include "expat.h"
26 : #endif
27 :
28 : constexpr int PARSER_BUF_SIZE = 8192;
29 :
30 : KML::KML() = default;
31 :
32 26 : KML::~KML()
33 : {
34 26 : if (nullptr != pKMLFile_)
35 26 : VSIFCloseL(pKMLFile_);
36 26 : CPLFree(papoLayers_);
37 :
38 26 : delete poTrunk_;
39 26 : }
40 :
41 26 : bool KML::open(const char *pszFilename)
42 : {
43 26 : if (nullptr != pKMLFile_)
44 0 : VSIFCloseL(pKMLFile_);
45 :
46 26 : pKMLFile_ = VSIFOpenL(pszFilename, "r");
47 26 : return pKMLFile_ != nullptr;
48 : }
49 :
50 25 : bool KML::parse()
51 : {
52 25 : if (nullptr == pKMLFile_)
53 : {
54 0 : sError_ = "No file given";
55 0 : return false;
56 : }
57 :
58 25 : if (poTrunk_ != nullptr)
59 : {
60 0 : delete poTrunk_;
61 0 : poTrunk_ = nullptr;
62 : }
63 :
64 25 : if (poCurrent_ != nullptr)
65 : {
66 0 : delete poCurrent_;
67 0 : poCurrent_ = nullptr;
68 : }
69 :
70 25 : XML_Parser oParser = OGRCreateExpatXMLParser();
71 25 : XML_SetUserData(oParser, this);
72 25 : XML_SetElementHandler(oParser, startElement, endElement);
73 25 : XML_SetCharacterDataHandler(oParser, dataHandler);
74 25 : oCurrentParser = oParser;
75 25 : nWithoutEventCounter = 0;
76 :
77 25 : int nDone = 0;
78 25 : unsigned nLen = 0;
79 50 : std::vector<char> aBuf(PARSER_BUF_SIZE);
80 25 : bool bError = false;
81 :
82 45 : do
83 : {
84 70 : nDataHandlerCounter = 0;
85 70 : nLen = static_cast<unsigned>(
86 70 : VSIFReadL(aBuf.data(), 1, aBuf.size(), pKMLFile_));
87 70 : nDone = nLen < aBuf.size();
88 70 : if (XML_Parse(oParser, aBuf.data(), nLen, nDone) == XML_STATUS_ERROR)
89 : {
90 1 : CPLError(CE_Failure, CPLE_AppDefined,
91 : "XML parsing of KML file failed : %s at line %d, "
92 : "column %d",
93 : XML_ErrorString(XML_GetErrorCode(oParser)),
94 1 : static_cast<int>(XML_GetCurrentLineNumber(oParser)),
95 1 : static_cast<int>(XML_GetCurrentColumnNumber(oParser)));
96 1 : bError = true;
97 1 : break;
98 : }
99 69 : nWithoutEventCounter++;
100 69 : } while (!nDone && nLen > 0 && nWithoutEventCounter < 10);
101 :
102 25 : XML_ParserFree(oParser);
103 25 : VSIRewindL(pKMLFile_);
104 :
105 25 : if (nWithoutEventCounter == 10)
106 : {
107 0 : CPLError(CE_Failure, CPLE_AppDefined,
108 : "Too much data inside one element. File probably corrupted");
109 0 : bError = true;
110 : }
111 :
112 25 : if (bError)
113 : {
114 1 : if (poCurrent_ != nullptr)
115 : {
116 0 : while (poCurrent_)
117 : {
118 0 : KMLNode *poTemp = poCurrent_->getParent();
119 0 : delete poCurrent_;
120 0 : poCurrent_ = poTemp;
121 : }
122 : // No need to destroy poTrunk_ : it has been destroyed in
123 : // the last iteration
124 : }
125 : else
126 : {
127 : // Case of invalid content after closing element matching
128 : // first <kml> element
129 1 : delete poTrunk_;
130 : }
131 1 : poTrunk_ = nullptr;
132 1 : return false;
133 : }
134 :
135 24 : poCurrent_ = nullptr;
136 24 : return true;
137 : }
138 :
139 26 : void KML::checkValidity()
140 : {
141 26 : if (poTrunk_ != nullptr)
142 : {
143 0 : delete poTrunk_;
144 0 : poTrunk_ = nullptr;
145 : }
146 :
147 26 : if (poCurrent_ != nullptr)
148 : {
149 0 : delete poCurrent_;
150 0 : poCurrent_ = nullptr;
151 : }
152 :
153 26 : if (pKMLFile_ == nullptr)
154 : {
155 0 : sError_ = "No file given";
156 1 : return;
157 : }
158 :
159 26 : XML_Parser oParser = OGRCreateExpatXMLParser();
160 26 : XML_SetUserData(oParser, this);
161 26 : XML_SetElementHandler(oParser, startElementValidate, nullptr);
162 26 : XML_SetCharacterDataHandler(oParser, dataHandlerValidate);
163 26 : int nCount = 0;
164 :
165 26 : oCurrentParser = oParser;
166 :
167 26 : int nDone = 0;
168 26 : unsigned nLen = 0;
169 52 : std::vector<char> aBuf(PARSER_BUF_SIZE);
170 :
171 : // Parses the file until we find the first element.
172 0 : do
173 : {
174 26 : nDataHandlerCounter = 0;
175 26 : nLen = static_cast<unsigned>(
176 26 : VSIFReadL(aBuf.data(), 1, aBuf.size(), pKMLFile_));
177 26 : nDone = nLen < aBuf.size();
178 26 : if (XML_Parse(oParser, aBuf.data(), nLen, nDone) == XML_STATUS_ERROR)
179 : {
180 1 : if (nLen <= PARSER_BUF_SIZE - 1)
181 1 : aBuf[nLen] = 0;
182 : else
183 0 : aBuf[PARSER_BUF_SIZE - 1] = 0;
184 2 : if (strstr(aBuf.data(), "<?xml") &&
185 1 : (strstr(aBuf.data(), "<kml") ||
186 0 : (strstr(aBuf.data(), "<Document") &&
187 0 : strstr(aBuf.data(), "/kml/2."))))
188 : {
189 1 : CPLError(
190 : CE_Failure, CPLE_AppDefined,
191 : "XML parsing of KML file failed : %s at line %d, column %d",
192 : XML_ErrorString(XML_GetErrorCode(oParser)),
193 1 : static_cast<int>(XML_GetCurrentLineNumber(oParser)),
194 1 : static_cast<int>(XML_GetCurrentColumnNumber(oParser)));
195 : }
196 :
197 1 : validity = KML_VALIDITY_INVALID;
198 1 : XML_ParserFree(oParser);
199 1 : VSIRewindL(pKMLFile_);
200 1 : return;
201 : }
202 :
203 25 : nCount++;
204 : /* After reading 50 * PARSER_BUF_SIZE bytes, and not finding whether the file */
205 : /* is KML or not, we give up and fail silently */
206 25 : } while (!nDone && nLen > 0 && validity == KML_VALIDITY_UNKNOWN &&
207 : nCount < 50);
208 :
209 25 : XML_ParserFree(oParser);
210 25 : VSIRewindL(pKMLFile_);
211 25 : poCurrent_ = nullptr;
212 : }
213 :
214 5680 : void XMLCALL KML::startElement(void *pUserData, const char *pszName,
215 : const char **ppszAttr)
216 : {
217 5680 : KML *poKML = static_cast<KML *>(pUserData);
218 : try
219 : {
220 5680 : poKML->nWithoutEventCounter = 0;
221 :
222 5680 : const char *pszColumn = strchr(pszName, ':');
223 5680 : if (pszColumn)
224 5 : pszName = pszColumn + 1;
225 :
226 11335 : if (poKML->poTrunk_ == nullptr ||
227 11310 : (poKML->poCurrent_ != nullptr &&
228 5655 : poKML->poCurrent_->getName().compare("description") != 0))
229 : {
230 5675 : if (poKML->nDepth_ == 1024)
231 : {
232 0 : CPLError(CE_Failure, CPLE_AppDefined,
233 : "Too big depth level (%d) while parsing KML.",
234 : poKML->nDepth_);
235 0 : XML_StopParser(poKML->oCurrentParser, XML_FALSE);
236 0 : return;
237 : }
238 :
239 5675 : KMLNode *poMynew = new KMLNode();
240 5675 : poMynew->setName(pszName);
241 5675 : poMynew->setLevel(poKML->nDepth_);
242 :
243 7126 : for (int i = 0; ppszAttr[i]; i += 2)
244 : {
245 1451 : Attribute *poAtt = new Attribute();
246 1451 : poAtt->sName = ppszAttr[i];
247 1451 : poAtt->sValue = ppszAttr[i + 1];
248 1451 : poMynew->addAttribute(poAtt);
249 : }
250 :
251 5675 : if (poKML->poTrunk_ == nullptr)
252 25 : poKML->poTrunk_ = poMynew;
253 5675 : if (poKML->poCurrent_ != nullptr)
254 5650 : poMynew->setParent(poKML->poCurrent_);
255 5675 : poKML->poCurrent_ = poMynew;
256 :
257 5675 : poKML->nDepth_++;
258 : }
259 5 : else if (poKML->poCurrent_ != nullptr)
260 : {
261 10 : std::string sNewContent = "<";
262 5 : sNewContent += pszName;
263 6 : for (int i = 0; ppszAttr[i]; i += 2)
264 : {
265 1 : sNewContent += " ";
266 1 : sNewContent += ppszAttr[i];
267 1 : sNewContent += "=\"";
268 1 : sNewContent += ppszAttr[i + 1];
269 1 : sNewContent += "\"";
270 : }
271 5 : sNewContent += ">";
272 5 : if (poKML->poCurrent_->numContent() == 0)
273 0 : poKML->poCurrent_->addContent(sNewContent);
274 : else
275 5 : poKML->poCurrent_->appendContent(sNewContent);
276 : }
277 : }
278 0 : catch (const std::exception &ex)
279 : {
280 0 : CPLError(CE_Failure, CPLE_AppDefined, "KML: libstdc++ exception : %s",
281 0 : ex.what());
282 0 : XML_StopParser(poKML->oCurrentParser, XML_FALSE);
283 : }
284 : }
285 :
286 2069 : void XMLCALL KML::startElementValidate(void *pUserData, const char *pszName,
287 : const char **ppszAttr)
288 : {
289 2069 : KML *poKML = static_cast<KML *>(pUserData);
290 :
291 2069 : if (poKML->validity != KML_VALIDITY_UNKNOWN)
292 2043 : return;
293 :
294 26 : poKML->validity = KML_VALIDITY_INVALID;
295 :
296 26 : const char *pszColumn = strchr(pszName, ':');
297 26 : if (pszColumn)
298 1 : pszName = pszColumn + 1;
299 :
300 26 : if (strcmp(pszName, "kml") == 0 || strcmp(pszName, "Document") == 0)
301 : {
302 : // Check all Attributes
303 55 : for (int i = 0; ppszAttr[i]; i += 2)
304 : {
305 : // Find the namespace and determine the KML version
306 29 : if (strcmp(ppszAttr[i], "xmlns") == 0)
307 : {
308 : // Is it KML 2.2?
309 24 : if ((strcmp(ppszAttr[i + 1],
310 24 : "http://earth.google.com/kml/2.2") == 0) ||
311 24 : (strcmp(ppszAttr[i + 1],
312 : "http://www.opengis.net/kml/2.2") == 0))
313 : {
314 10 : poKML->validity = KML_VALIDITY_VALID;
315 10 : poKML->sVersion_ = "2.2";
316 : }
317 14 : else if (strcmp(ppszAttr[i + 1],
318 : "http://earth.google.com/kml/2.1") == 0)
319 : {
320 14 : poKML->validity = KML_VALIDITY_VALID;
321 14 : poKML->sVersion_ = "2.1";
322 : }
323 0 : else if (strcmp(ppszAttr[i + 1],
324 : "http://earth.google.com/kml/2.0") == 0)
325 : {
326 0 : poKML->validity = KML_VALIDITY_VALID;
327 0 : poKML->sVersion_ = "2.0";
328 : }
329 : else
330 : {
331 0 : CPLDebug("KML",
332 : "Unhandled xmlns value : %s. Going on though...",
333 0 : ppszAttr[i]);
334 0 : poKML->validity = KML_VALIDITY_VALID;
335 0 : poKML->sVersion_ = "?";
336 : }
337 : }
338 : }
339 :
340 26 : if (poKML->validity == KML_VALIDITY_INVALID)
341 : {
342 2 : CPLDebug("KML", "Did not find xmlns attribute in <kml> element. "
343 : "Going on though...");
344 2 : poKML->validity = KML_VALIDITY_VALID;
345 2 : poKML->sVersion_ = "?";
346 : }
347 : }
348 : }
349 :
350 7667 : void XMLCALL KML::dataHandlerValidate(void *pUserData,
351 : const char * /* pszData */,
352 : int /* nLen */)
353 : {
354 7667 : KML *poKML = static_cast<KML *>(pUserData);
355 :
356 7667 : poKML->nDataHandlerCounter++;
357 7667 : if (poKML->nDataHandlerCounter >= PARSER_BUF_SIZE)
358 : {
359 0 : CPLError(CE_Failure, CPLE_AppDefined,
360 : "File probably corrupted (million laugh pattern)");
361 0 : XML_StopParser(poKML->oCurrentParser, XML_FALSE);
362 : }
363 7667 : }
364 :
365 5680 : void XMLCALL KML::endElement(void *pUserData, const char *pszName)
366 : {
367 5680 : KML *poKML = static_cast<KML *>(pUserData);
368 :
369 : try
370 : {
371 5680 : poKML->nWithoutEventCounter = 0;
372 :
373 5680 : const char *pszColumn = strchr(pszName, ':');
374 5680 : if (pszColumn)
375 5 : pszName = pszColumn + 1;
376 :
377 11360 : if (poKML->poCurrent_ != nullptr &&
378 5680 : poKML->poCurrent_->getName().compare(pszName) == 0)
379 : {
380 5675 : poKML->nDepth_--;
381 5675 : KMLNode *poTmp = poKML->poCurrent_;
382 : // Split the coordinates
383 5943 : if (poKML->poCurrent_->getName().compare("coordinates") == 0 &&
384 268 : poKML->poCurrent_->numContent() == 1)
385 : {
386 526 : const std::string sData = poKML->poCurrent_->getContent(0);
387 263 : std::size_t nPos = 0;
388 263 : const std::size_t nLength = sData.length();
389 263 : const char *pszData = sData.c_str();
390 : while (true)
391 : {
392 : // Cut off whitespaces
393 33868 : while (nPos < nLength &&
394 33605 : (pszData[nPos] == ' ' || pszData[nPos] == '\n' ||
395 2106 : pszData[nPos] == '\r' || pszData[nPos] == '\t'))
396 31499 : nPos++;
397 :
398 2369 : if (nPos == nLength)
399 263 : break;
400 :
401 2106 : const std::size_t nPosBegin = nPos;
402 2106 : size_t nContentSize = 0;
403 :
404 : // Get content
405 81109 : while (nPos < nLength && pszData[nPos] != ' ' &&
406 162068 : pszData[nPos] != '\n' && pszData[nPos] != '\r' &&
407 79090 : pszData[nPos] != '\t')
408 : {
409 79090 : nContentSize++;
410 79090 : nPos++;
411 : }
412 :
413 2106 : if (nContentSize > 0)
414 : {
415 4212 : std::string sTmp(pszData + nPosBegin, nContentSize);
416 2106 : poKML->poCurrent_->addContent(sTmp);
417 : }
418 2106 : }
419 263 : if (poKML->poCurrent_->numContent() > 1)
420 263 : poKML->poCurrent_->deleteContent(0);
421 : }
422 5412 : else if (poKML->poCurrent_->numContent() == 1)
423 : {
424 9960 : const std::string sData = poKML->poCurrent_->getContent(0);
425 9960 : std::string sDataWithoutNL;
426 4980 : std::size_t nPos = 0;
427 4980 : const std::size_t nLength = sData.length();
428 4980 : const char *pszData = sData.c_str();
429 4980 : std::size_t nLineStartPos = 0;
430 4980 : bool bLineStart = true;
431 :
432 : // Re-assemble multi-line content by removing leading spaces for
433 : // each line. I am not sure why we do that. Should we preserve
434 : // content as such?
435 173887 : while (nPos < nLength)
436 : {
437 168907 : const char ch = pszData[nPos];
438 168907 : if (bLineStart &&
439 11829 : (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'))
440 80494 : nLineStartPos++;
441 88413 : else if (ch == '\n' || ch == '\r')
442 : {
443 1244 : if (!bLineStart)
444 : {
445 : std::string sTmp(pszData + nLineStartPos,
446 1244 : nPos - nLineStartPos);
447 1244 : if (!sDataWithoutNL.empty())
448 1111 : sDataWithoutNL += '\n';
449 1244 : sDataWithoutNL += sTmp;
450 1244 : bLineStart = true;
451 : }
452 1244 : nLineStartPos = nPos + 1;
453 : }
454 : else
455 : {
456 87169 : bLineStart = false;
457 : }
458 168907 : nPos++;
459 : }
460 :
461 4980 : if (nLineStartPos > 0)
462 : {
463 1861 : if (nLineStartPos < nPos)
464 : {
465 : std::string sTmp(pszData + nLineStartPos,
466 242 : nPos - nLineStartPos);
467 121 : if (!sDataWithoutNL.empty())
468 121 : sDataWithoutNL += '\n';
469 121 : sDataWithoutNL += sTmp;
470 : }
471 :
472 1861 : poKML->poCurrent_->deleteContent(0);
473 1861 : poKML->poCurrent_->addContent(sDataWithoutNL);
474 : }
475 : }
476 :
477 5675 : if (poKML->poCurrent_->getParent() != nullptr)
478 5650 : poKML->poCurrent_ = poKML->poCurrent_->getParent();
479 : else
480 25 : poKML->poCurrent_ = nullptr;
481 :
482 5675 : if (!poKML->isHandled(pszName))
483 : {
484 3553 : CPLDebug("KML", "Not handled: %s", pszName);
485 3553 : delete poTmp;
486 3553 : if (poKML->poCurrent_ == poTmp)
487 0 : poKML->poCurrent_ = nullptr;
488 3553 : if (poKML->poTrunk_ == poTmp)
489 0 : poKML->poTrunk_ = nullptr;
490 : }
491 : else
492 : {
493 2122 : if (poKML->poCurrent_ != nullptr)
494 2097 : poKML->poCurrent_->addChildren(poTmp);
495 : }
496 : }
497 5 : else if (poKML->poCurrent_ != nullptr)
498 : {
499 10 : std::string sNewContent = "</";
500 5 : sNewContent += pszName;
501 5 : sNewContent += ">";
502 5 : if (poKML->poCurrent_->numContent() == 0)
503 0 : poKML->poCurrent_->addContent(sNewContent);
504 : else
505 5 : poKML->poCurrent_->appendContent(sNewContent);
506 : }
507 : }
508 0 : catch (const std::exception &ex)
509 : {
510 0 : CPLError(CE_Failure, CPLE_AppDefined, "KML: libstdc++ exception : %s",
511 0 : ex.what());
512 0 : XML_StopParser(poKML->oCurrentParser, XML_FALSE);
513 : }
514 5680 : }
515 :
516 24279 : void XMLCALL KML::dataHandler(void *pUserData, const char *pszData, int nLen)
517 : {
518 24279 : KML *poKML = static_cast<KML *>(pUserData);
519 :
520 24279 : poKML->nWithoutEventCounter = 0;
521 :
522 24279 : if (nLen < 1 || poKML->poCurrent_ == nullptr)
523 0 : return;
524 :
525 24279 : poKML->nDataHandlerCounter++;
526 24279 : if (poKML->nDataHandlerCounter >= PARSER_BUF_SIZE)
527 : {
528 0 : CPLError(CE_Failure, CPLE_AppDefined,
529 : "File probably corrupted (million laugh pattern)");
530 0 : XML_StopParser(poKML->oCurrentParser, XML_FALSE);
531 : }
532 :
533 : try
534 : {
535 48558 : std::string sData(pszData, nLen);
536 :
537 24279 : if (poKML->poCurrent_->numContent() == 0)
538 5243 : poKML->poCurrent_->addContent(sData);
539 : else
540 19036 : poKML->poCurrent_->appendContent(sData);
541 : }
542 0 : catch (const std::exception &ex)
543 : {
544 0 : CPLError(CE_Failure, CPLE_AppDefined, "KML: libstdc++ exception : %s",
545 0 : ex.what());
546 0 : XML_StopParser(poKML->oCurrentParser, XML_FALSE);
547 : }
548 : }
549 :
550 26 : bool KML::isValid()
551 : {
552 26 : checkValidity();
553 :
554 26 : if (validity == KML_VALIDITY_VALID)
555 25 : CPLDebug("KML", "Valid: 1 Version: %s", sVersion_.c_str());
556 :
557 26 : return validity == KML_VALIDITY_VALID;
558 : }
559 :
560 0 : std::string KML::getError() const
561 : {
562 0 : return sError_;
563 : }
564 :
565 24 : int KML::classifyNodes()
566 : {
567 24 : if (poTrunk_ == nullptr)
568 0 : return false;
569 24 : return poTrunk_->classify(this);
570 : }
571 :
572 20 : void KML::eliminateEmpty()
573 : {
574 20 : if (poTrunk_ != nullptr)
575 20 : poTrunk_->eliminateEmpty(this);
576 20 : }
577 :
578 0 : void KML::print(unsigned short nNum)
579 : {
580 0 : if (poTrunk_ != nullptr)
581 0 : poTrunk_->print(nNum);
582 0 : }
583 :
584 5675 : bool KML::isHandled(std::string const &elem) const
585 : {
586 10206 : return isLeaf(elem) || isFeature(elem) || isFeatureContainer(elem) ||
587 7554 : isContainer(elem) || isRest(elem) || elem == "Schema" ||
588 10676 : elem == "SimpleField" || elem == "SchemaData" ||
589 13763 : elem == "SimpleData" || elem == "ExtendedData";
590 : }
591 :
592 24 : bool KML::hasOnlyEmpty() const
593 : {
594 24 : return poTrunk_->hasOnlyEmpty();
595 : }
596 :
597 24 : int KML::getNumLayers() const
598 : {
599 24 : return nNumLayers_;
600 : }
601 :
602 920 : bool KML::selectLayer(int nNum)
603 : {
604 920 : if (nNumLayers_ < 1 || nNum >= nNumLayers_)
605 0 : return false;
606 920 : poCurrent_ = papoLayers_[nNum];
607 920 : return true;
608 : }
609 :
610 83 : std::string KML::getCurrentName() const
611 : {
612 83 : std::string tmp;
613 83 : if (poCurrent_ != nullptr)
614 : {
615 83 : tmp = poCurrent_->getNameElement();
616 : }
617 83 : return tmp;
618 : }
619 :
620 203 : Nodetype KML::getCurrentType() const
621 : {
622 203 : if (poCurrent_ != nullptr)
623 203 : return poCurrent_->getType();
624 :
625 0 : return Unknown;
626 : }
627 :
628 75 : int KML::is25D() const
629 : {
630 75 : if (poCurrent_ != nullptr)
631 75 : return poCurrent_->is25D();
632 :
633 0 : return Unknown;
634 : }
635 :
636 61 : int KML::getNumFeatures()
637 : {
638 61 : if (poCurrent_ == nullptr)
639 0 : return -1;
640 :
641 61 : return static_cast<int>(poCurrent_->getNumFeatures());
642 : }
643 :
644 960 : Feature *KML::getFeature(std::size_t nNum, int &nLastAsked, int &nLastCount)
645 : {
646 960 : if (poCurrent_ == nullptr)
647 0 : return nullptr;
648 :
649 960 : return poCurrent_->getFeature(nNum, nLastAsked, nLastCount);
650 : }
651 :
652 116 : void KML::unregisterLayerIfMatchingThisNode(KMLNode *poNode)
653 : {
654 116 : for (int i = 0; i < nNumLayers_;)
655 : {
656 2 : if (papoLayers_[i] == poNode)
657 : {
658 2 : if (i < nNumLayers_ - 1)
659 : {
660 0 : memmove(papoLayers_ + i, papoLayers_ + i + 1,
661 0 : (nNumLayers_ - 1 - i) * sizeof(KMLNode *));
662 : }
663 2 : nNumLayers_--;
664 2 : break;
665 : }
666 0 : i++;
667 : }
668 116 : }
|