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 25 : KML::~KML()
33 : {
34 25 : if (nullptr != pKMLFile_)
35 25 : VSIFCloseL(pKMLFile_);
36 25 : CPLFree(papoLayers_);
37 :
38 25 : delete poTrunk_;
39 25 : }
40 :
41 25 : bool KML::open(const char *pszFilename)
42 : {
43 25 : if (nullptr != pKMLFile_)
44 0 : VSIFCloseL(pKMLFile_);
45 :
46 25 : pKMLFile_ = VSIFOpenL(pszFilename, "r");
47 25 : return pKMLFile_ != nullptr;
48 : }
49 :
50 24 : bool KML::parse()
51 : {
52 24 : if (nullptr == pKMLFile_)
53 : {
54 0 : sError_ = "No file given";
55 0 : return false;
56 : }
57 :
58 24 : if (poTrunk_ != nullptr)
59 : {
60 0 : delete poTrunk_;
61 0 : poTrunk_ = nullptr;
62 : }
63 :
64 24 : if (poCurrent_ != nullptr)
65 : {
66 0 : delete poCurrent_;
67 0 : poCurrent_ = nullptr;
68 : }
69 :
70 24 : XML_Parser oParser = OGRCreateExpatXMLParser();
71 24 : XML_SetUserData(oParser, this);
72 24 : XML_SetElementHandler(oParser, startElement, endElement);
73 24 : XML_SetCharacterDataHandler(oParser, dataHandler);
74 24 : oCurrentParser = oParser;
75 24 : nWithoutEventCounter = 0;
76 :
77 24 : int nDone = 0;
78 24 : unsigned nLen = 0;
79 48 : std::vector<char> aBuf(PARSER_BUF_SIZE);
80 24 : bool bError = false;
81 :
82 45 : do
83 : {
84 69 : nDataHandlerCounter = 0;
85 69 : nLen = static_cast<unsigned>(
86 69 : VSIFReadL(aBuf.data(), 1, aBuf.size(), pKMLFile_));
87 69 : nDone = nLen < aBuf.size();
88 69 : 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 68 : nWithoutEventCounter++;
100 68 : } while (!nDone && nLen > 0 && nWithoutEventCounter < 10);
101 :
102 24 : XML_ParserFree(oParser);
103 24 : VSIRewindL(pKMLFile_);
104 :
105 24 : 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 24 : 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 23 : poCurrent_ = nullptr;
136 23 : return true;
137 : }
138 :
139 25 : void KML::checkValidity()
140 : {
141 25 : if (poTrunk_ != nullptr)
142 : {
143 0 : delete poTrunk_;
144 0 : poTrunk_ = nullptr;
145 : }
146 :
147 25 : if (poCurrent_ != nullptr)
148 : {
149 0 : delete poCurrent_;
150 0 : poCurrent_ = nullptr;
151 : }
152 :
153 25 : if (pKMLFile_ == nullptr)
154 : {
155 0 : sError_ = "No file given";
156 1 : return;
157 : }
158 :
159 25 : XML_Parser oParser = OGRCreateExpatXMLParser();
160 25 : XML_SetUserData(oParser, this);
161 25 : XML_SetElementHandler(oParser, startElementValidate, nullptr);
162 25 : XML_SetCharacterDataHandler(oParser, dataHandlerValidate);
163 25 : int nCount = 0;
164 :
165 25 : oCurrentParser = oParser;
166 :
167 25 : int nDone = 0;
168 25 : unsigned nLen = 0;
169 50 : std::vector<char> aBuf(PARSER_BUF_SIZE);
170 :
171 : // Parses the file until we find the first element.
172 0 : do
173 : {
174 25 : nDataHandlerCounter = 0;
175 25 : nLen = static_cast<unsigned>(
176 25 : VSIFReadL(aBuf.data(), 1, aBuf.size(), pKMLFile_));
177 25 : nDone = nLen < aBuf.size();
178 25 : 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 24 : 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 24 : } while (!nDone && nLen > 0 && validity == KML_VALIDITY_UNKNOWN &&
207 : nCount < 50);
208 :
209 24 : XML_ParserFree(oParser);
210 24 : VSIRewindL(pKMLFile_);
211 24 : poCurrent_ = nullptr;
212 : }
213 :
214 5664 : void XMLCALL KML::startElement(void *pUserData, const char *pszName,
215 : const char **ppszAttr)
216 : {
217 5664 : KML *poKML = static_cast<KML *>(pUserData);
218 : try
219 : {
220 5664 : poKML->nWithoutEventCounter = 0;
221 :
222 5664 : const char *pszColumn = strchr(pszName, ':');
223 5664 : if (pszColumn)
224 5 : pszName = pszColumn + 1;
225 :
226 11304 : if (poKML->poTrunk_ == nullptr ||
227 11280 : (poKML->poCurrent_ != nullptr &&
228 5640 : poKML->poCurrent_->getName().compare("description") != 0))
229 : {
230 5659 : 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 5659 : KMLNode *poMynew = new KMLNode();
240 5659 : poMynew->setName(pszName);
241 5659 : poMynew->setLevel(poKML->nDepth_);
242 :
243 7095 : for (int i = 0; ppszAttr[i]; i += 2)
244 : {
245 1436 : Attribute *poAtt = new Attribute();
246 1436 : poAtt->sName = ppszAttr[i];
247 1436 : poAtt->sValue = ppszAttr[i + 1];
248 1436 : poMynew->addAttribute(poAtt);
249 : }
250 :
251 5659 : if (poKML->poTrunk_ == nullptr)
252 24 : poKML->poTrunk_ = poMynew;
253 5659 : if (poKML->poCurrent_ != nullptr)
254 5635 : poMynew->setParent(poKML->poCurrent_);
255 5659 : poKML->poCurrent_ = poMynew;
256 :
257 5659 : 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 2053 : void XMLCALL KML::startElementValidate(void *pUserData, const char *pszName,
287 : const char **ppszAttr)
288 : {
289 2053 : KML *poKML = static_cast<KML *>(pUserData);
290 :
291 2053 : if (poKML->validity != KML_VALIDITY_UNKNOWN)
292 2028 : return;
293 :
294 25 : poKML->validity = KML_VALIDITY_INVALID;
295 :
296 25 : const char *pszColumn = strchr(pszName, ':');
297 25 : if (pszColumn)
298 1 : pszName = pszColumn + 1;
299 :
300 25 : if (strcmp(pszName, "kml") == 0 || strcmp(pszName, "Document") == 0)
301 : {
302 : // Check all Attributes
303 53 : for (int i = 0; ppszAttr[i]; i += 2)
304 : {
305 : // Find the namespace and determine the KML version
306 28 : if (strcmp(ppszAttr[i], "xmlns") == 0)
307 : {
308 : // Is it KML 2.2?
309 23 : if ((strcmp(ppszAttr[i + 1],
310 23 : "http://earth.google.com/kml/2.2") == 0) ||
311 23 : (strcmp(ppszAttr[i + 1],
312 : "http://www.opengis.net/kml/2.2") == 0))
313 : {
314 9 : poKML->validity = KML_VALIDITY_VALID;
315 9 : 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 25 : 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 7634 : void XMLCALL KML::dataHandlerValidate(void *pUserData,
351 : const char * /* pszData */,
352 : int /* nLen */)
353 : {
354 7634 : KML *poKML = static_cast<KML *>(pUserData);
355 :
356 7634 : poKML->nDataHandlerCounter++;
357 7634 : 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 7634 : }
364 :
365 5664 : void XMLCALL KML::endElement(void *pUserData, const char *pszName)
366 : {
367 5664 : KML *poKML = static_cast<KML *>(pUserData);
368 :
369 : try
370 : {
371 5664 : poKML->nWithoutEventCounter = 0;
372 :
373 5664 : const char *pszColumn = strchr(pszName, ':');
374 5664 : if (pszColumn)
375 5 : pszName = pszColumn + 1;
376 :
377 11328 : if (poKML->poCurrent_ != nullptr &&
378 5664 : poKML->poCurrent_->getName().compare(pszName) == 0)
379 : {
380 5659 : poKML->nDepth_--;
381 5659 : KMLNode *poTmp = poKML->poCurrent_;
382 : // Split the coordinates
383 5926 : if (poKML->poCurrent_->getName().compare("coordinates") == 0 &&
384 267 : poKML->poCurrent_->numContent() == 1)
385 : {
386 524 : const std::string sData = poKML->poCurrent_->getContent(0);
387 262 : std::size_t nPos = 0;
388 262 : const std::size_t nLength = sData.length();
389 262 : const char *pszData = sData.c_str();
390 : while (true)
391 : {
392 : // Cut off whitespaces
393 33866 : while (nPos < nLength &&
394 33604 : (pszData[nPos] == ' ' || pszData[nPos] == '\n' ||
395 2105 : pszData[nPos] == '\r' || pszData[nPos] == '\t'))
396 31499 : nPos++;
397 :
398 2367 : if (nPos == nLength)
399 262 : break;
400 :
401 2105 : const std::size_t nPosBegin = nPos;
402 :
403 : // Get content
404 81106 : while (nPos < nLength && pszData[nPos] != ' ' &&
405 162061 : pszData[nPos] != '\n' && pszData[nPos] != '\r' &&
406 79087 : pszData[nPos] != '\t')
407 79087 : nPos++;
408 :
409 2105 : if (nPos - nPosBegin > 0)
410 : {
411 4210 : std::string sTmp(pszData + nPosBegin, nPos - nPosBegin);
412 2105 : poKML->poCurrent_->addContent(sTmp);
413 : }
414 2105 : }
415 262 : if (poKML->poCurrent_->numContent() > 1)
416 262 : poKML->poCurrent_->deleteContent(0);
417 : }
418 5397 : else if (poKML->poCurrent_->numContent() == 1)
419 : {
420 9940 : const std::string sData = poKML->poCurrent_->getContent(0);
421 9940 : std::string sDataWithoutNL;
422 4970 : std::size_t nPos = 0;
423 4970 : const std::size_t nLength = sData.length();
424 4970 : const char *pszData = sData.c_str();
425 4970 : std::size_t nLineStartPos = 0;
426 4970 : bool bLineStart = true;
427 :
428 : // Re-assemble multi-line content by removing leading spaces for
429 : // each line. I am not sure why we do that. Should we preserve
430 : // content as such?
431 173819 : while (nPos < nLength)
432 : {
433 168849 : const char ch = pszData[nPos];
434 168849 : if (bLineStart &&
435 11797 : (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'))
436 80456 : nLineStartPos++;
437 88393 : else if (ch == '\n' || ch == '\r')
438 : {
439 1244 : if (!bLineStart)
440 : {
441 : std::string sTmp(pszData + nLineStartPos,
442 1244 : nPos - nLineStartPos);
443 1244 : if (!sDataWithoutNL.empty())
444 1111 : sDataWithoutNL += '\n';
445 1244 : sDataWithoutNL += sTmp;
446 1244 : bLineStart = true;
447 : }
448 1244 : nLineStartPos = nPos + 1;
449 : }
450 : else
451 : {
452 87149 : bLineStart = false;
453 : }
454 168849 : nPos++;
455 : }
456 :
457 4970 : if (nLineStartPos > 0)
458 : {
459 1855 : if (nLineStartPos < nPos)
460 : {
461 : std::string sTmp(pszData + nLineStartPos,
462 242 : nPos - nLineStartPos);
463 121 : if (!sDataWithoutNL.empty())
464 121 : sDataWithoutNL += '\n';
465 121 : sDataWithoutNL += sTmp;
466 : }
467 :
468 1855 : poKML->poCurrent_->deleteContent(0);
469 1855 : poKML->poCurrent_->addContent(sDataWithoutNL);
470 : }
471 : }
472 :
473 5659 : if (poKML->poCurrent_->getParent() != nullptr)
474 5635 : poKML->poCurrent_ = poKML->poCurrent_->getParent();
475 : else
476 24 : poKML->poCurrent_ = nullptr;
477 :
478 5659 : if (!poKML->isHandled(pszName))
479 : {
480 3553 : CPLDebug("KML", "Not handled: %s", pszName);
481 3553 : delete poTmp;
482 3553 : if (poKML->poCurrent_ == poTmp)
483 0 : poKML->poCurrent_ = nullptr;
484 3553 : if (poKML->poTrunk_ == poTmp)
485 0 : poKML->poTrunk_ = nullptr;
486 : }
487 : else
488 : {
489 2106 : if (poKML->poCurrent_ != nullptr)
490 2082 : poKML->poCurrent_->addChildren(poTmp);
491 : }
492 : }
493 5 : else if (poKML->poCurrent_ != nullptr)
494 : {
495 10 : std::string sNewContent = "</";
496 5 : sNewContent += pszName;
497 5 : sNewContent += ">";
498 5 : if (poKML->poCurrent_->numContent() == 0)
499 0 : poKML->poCurrent_->addContent(sNewContent);
500 : else
501 5 : poKML->poCurrent_->appendContent(sNewContent);
502 : }
503 : }
504 0 : catch (const std::exception &ex)
505 : {
506 0 : CPLError(CE_Failure, CPLE_AppDefined, "KML: libstdc++ exception : %s",
507 0 : ex.what());
508 0 : XML_StopParser(poKML->oCurrentParser, XML_FALSE);
509 : }
510 5664 : }
511 :
512 24246 : void XMLCALL KML::dataHandler(void *pUserData, const char *pszData, int nLen)
513 : {
514 24246 : KML *poKML = static_cast<KML *>(pUserData);
515 :
516 24246 : poKML->nWithoutEventCounter = 0;
517 :
518 24246 : if (nLen < 1 || poKML->poCurrent_ == nullptr)
519 0 : return;
520 :
521 24246 : poKML->nDataHandlerCounter++;
522 24246 : if (poKML->nDataHandlerCounter >= PARSER_BUF_SIZE)
523 : {
524 0 : CPLError(CE_Failure, CPLE_AppDefined,
525 : "File probably corrupted (million laugh pattern)");
526 0 : XML_StopParser(poKML->oCurrentParser, XML_FALSE);
527 : }
528 :
529 : try
530 : {
531 48492 : std::string sData(pszData, nLen);
532 :
533 24246 : if (poKML->poCurrent_->numContent() == 0)
534 5232 : poKML->poCurrent_->addContent(sData);
535 : else
536 19014 : poKML->poCurrent_->appendContent(sData);
537 : }
538 0 : catch (const std::exception &ex)
539 : {
540 0 : CPLError(CE_Failure, CPLE_AppDefined, "KML: libstdc++ exception : %s",
541 0 : ex.what());
542 0 : XML_StopParser(poKML->oCurrentParser, XML_FALSE);
543 : }
544 : }
545 :
546 25 : bool KML::isValid()
547 : {
548 25 : checkValidity();
549 :
550 25 : if (validity == KML_VALIDITY_VALID)
551 24 : CPLDebug("KML", "Valid: 1 Version: %s", sVersion_.c_str());
552 :
553 25 : return validity == KML_VALIDITY_VALID;
554 : }
555 :
556 0 : std::string KML::getError() const
557 : {
558 0 : return sError_;
559 : }
560 :
561 23 : int KML::classifyNodes()
562 : {
563 23 : if (poTrunk_ == nullptr)
564 0 : return false;
565 23 : return poTrunk_->classify(this);
566 : }
567 :
568 19 : void KML::eliminateEmpty()
569 : {
570 19 : if (poTrunk_ != nullptr)
571 19 : poTrunk_->eliminateEmpty(this);
572 19 : }
573 :
574 0 : void KML::print(unsigned short nNum)
575 : {
576 0 : if (poTrunk_ != nullptr)
577 0 : poTrunk_->print(nNum);
578 0 : }
579 :
580 5659 : bool KML::isHandled(std::string const &elem) const
581 : {
582 10176 : return isLeaf(elem) || isFeature(elem) || isFeatureContainer(elem) ||
583 10176 : isContainer(elem) || isRest(elem);
584 : }
585 :
586 0 : bool KML::isLeaf(std::string const & /* elem */) const
587 : {
588 0 : return false;
589 : }
590 :
591 0 : bool KML::isFeature(std::string const & /* elem */) const
592 : {
593 0 : return false;
594 : }
595 :
596 0 : bool KML::isFeatureContainer(std::string const & /* elem */) const
597 : {
598 0 : return false;
599 : }
600 :
601 0 : bool KML::isContainer(std::string const & /* elem */) const
602 : {
603 0 : return false;
604 : }
605 :
606 0 : bool KML::isRest(std::string const & /* elem */) const
607 : {
608 0 : return false;
609 : }
610 :
611 0 : void KML::findLayers(KMLNode * /* poNode */, int /* bKeepEmptyContainers */)
612 : {
613 : // idle
614 0 : }
615 :
616 23 : bool KML::hasOnlyEmpty() const
617 : {
618 23 : return poTrunk_->hasOnlyEmpty();
619 : }
620 :
621 23 : int KML::getNumLayers() const
622 : {
623 23 : return nNumLayers_;
624 : }
625 :
626 875 : bool KML::selectLayer(int nNum)
627 : {
628 875 : if (nNumLayers_ < 1 || nNum >= nNumLayers_)
629 0 : return false;
630 875 : poCurrent_ = papoLayers_[nNum];
631 875 : return true;
632 : }
633 :
634 82 : std::string KML::getCurrentName() const
635 : {
636 82 : std::string tmp;
637 82 : if (poCurrent_ != nullptr)
638 : {
639 82 : tmp = poCurrent_->getNameElement();
640 : }
641 82 : return tmp;
642 : }
643 :
644 202 : Nodetype KML::getCurrentType() const
645 : {
646 202 : if (poCurrent_ != nullptr)
647 202 : return poCurrent_->getType();
648 :
649 0 : return Unknown;
650 : }
651 :
652 74 : int KML::is25D() const
653 : {
654 74 : if (poCurrent_ != nullptr)
655 74 : return poCurrent_->is25D();
656 :
657 0 : return Unknown;
658 : }
659 :
660 61 : int KML::getNumFeatures()
661 : {
662 61 : if (poCurrent_ == nullptr)
663 0 : return -1;
664 :
665 61 : return static_cast<int>(poCurrent_->getNumFeatures());
666 : }
667 :
668 916 : Feature *KML::getFeature(std::size_t nNum, int &nLastAsked, int &nLastCount)
669 : {
670 916 : if (poCurrent_ == nullptr)
671 0 : return nullptr;
672 :
673 916 : return poCurrent_->getFeature(nNum, nLastAsked, nLastCount);
674 : }
675 :
676 116 : void KML::unregisterLayerIfMatchingThisNode(KMLNode *poNode)
677 : {
678 116 : for (int i = 0; i < nNumLayers_;)
679 : {
680 2 : if (papoLayers_[i] == poNode)
681 : {
682 2 : if (i < nNumLayers_ - 1)
683 : {
684 0 : memmove(papoLayers_ + i, papoLayers_ + i + 1,
685 0 : (nNumLayers_ - 1 - i) * sizeof(KMLNode *));
686 : }
687 2 : nNumLayers_--;
688 2 : break;
689 : }
690 0 : i++;
691 : }
692 116 : }
|