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 2105 : size_t nContentSize = 0;
403 :
404 : // Get content
405 81106 : while (nPos < nLength && pszData[nPos] != ' ' &&
406 162061 : pszData[nPos] != '\n' && pszData[nPos] != '\r' &&
407 79087 : pszData[nPos] != '\t')
408 : {
409 79087 : nContentSize++;
410 79087 : nPos++;
411 : }
412 :
413 2105 : if (nContentSize > 0)
414 : {
415 4210 : std::string sTmp(pszData + nPosBegin, nContentSize);
416 2105 : poKML->poCurrent_->addContent(sTmp);
417 : }
418 2105 : }
419 262 : if (poKML->poCurrent_->numContent() > 1)
420 262 : poKML->poCurrent_->deleteContent(0);
421 : }
422 5397 : else if (poKML->poCurrent_->numContent() == 1)
423 : {
424 9940 : const std::string sData = poKML->poCurrent_->getContent(0);
425 9940 : std::string sDataWithoutNL;
426 4970 : std::size_t nPos = 0;
427 4970 : const std::size_t nLength = sData.length();
428 4970 : const char *pszData = sData.c_str();
429 4970 : std::size_t nLineStartPos = 0;
430 4970 : 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 173819 : while (nPos < nLength)
436 : {
437 168849 : const char ch = pszData[nPos];
438 168849 : if (bLineStart &&
439 11797 : (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'))
440 80456 : nLineStartPos++;
441 88393 : 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 87149 : bLineStart = false;
457 : }
458 168849 : nPos++;
459 : }
460 :
461 4970 : if (nLineStartPos > 0)
462 : {
463 1855 : 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 1855 : poKML->poCurrent_->deleteContent(0);
473 1855 : poKML->poCurrent_->addContent(sDataWithoutNL);
474 : }
475 : }
476 :
477 5659 : if (poKML->poCurrent_->getParent() != nullptr)
478 5635 : poKML->poCurrent_ = poKML->poCurrent_->getParent();
479 : else
480 24 : poKML->poCurrent_ = nullptr;
481 :
482 5659 : 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 2106 : if (poKML->poCurrent_ != nullptr)
494 2082 : 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 5664 : }
515 :
516 24246 : void XMLCALL KML::dataHandler(void *pUserData, const char *pszData, int nLen)
517 : {
518 24246 : KML *poKML = static_cast<KML *>(pUserData);
519 :
520 24246 : poKML->nWithoutEventCounter = 0;
521 :
522 24246 : if (nLen < 1 || poKML->poCurrent_ == nullptr)
523 0 : return;
524 :
525 24246 : poKML->nDataHandlerCounter++;
526 24246 : 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 48492 : std::string sData(pszData, nLen);
536 :
537 24246 : if (poKML->poCurrent_->numContent() == 0)
538 5232 : poKML->poCurrent_->addContent(sData);
539 : else
540 19014 : 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 25 : bool KML::isValid()
551 : {
552 25 : checkValidity();
553 :
554 25 : if (validity == KML_VALIDITY_VALID)
555 24 : CPLDebug("KML", "Valid: 1 Version: %s", sVersion_.c_str());
556 :
557 25 : return validity == KML_VALIDITY_VALID;
558 : }
559 :
560 0 : std::string KML::getError() const
561 : {
562 0 : return sError_;
563 : }
564 :
565 23 : int KML::classifyNodes()
566 : {
567 23 : if (poTrunk_ == nullptr)
568 0 : return false;
569 23 : return poTrunk_->classify(this);
570 : }
571 :
572 19 : void KML::eliminateEmpty()
573 : {
574 19 : if (poTrunk_ != nullptr)
575 19 : poTrunk_->eliminateEmpty(this);
576 19 : }
577 :
578 0 : void KML::print(unsigned short nNum)
579 : {
580 0 : if (poTrunk_ != nullptr)
581 0 : poTrunk_->print(nNum);
582 0 : }
583 :
584 5659 : bool KML::isHandled(std::string const &elem) const
585 : {
586 10176 : return isLeaf(elem) || isFeature(elem) || isFeatureContainer(elem) ||
587 10176 : isContainer(elem) || isRest(elem);
588 : }
589 :
590 0 : bool KML::isLeaf(std::string const & /* elem */) const
591 : {
592 0 : return false;
593 : }
594 :
595 0 : bool KML::isFeature(std::string const & /* elem */) const
596 : {
597 0 : return false;
598 : }
599 :
600 0 : bool KML::isFeatureContainer(std::string const & /* elem */) const
601 : {
602 0 : return false;
603 : }
604 :
605 0 : bool KML::isContainer(std::string const & /* elem */) const
606 : {
607 0 : return false;
608 : }
609 :
610 0 : bool KML::isRest(std::string const & /* elem */) const
611 : {
612 0 : return false;
613 : }
614 :
615 0 : void KML::findLayers(KMLNode * /* poNode */, int /* bKeepEmptyContainers */)
616 : {
617 : // idle
618 0 : }
619 :
620 23 : bool KML::hasOnlyEmpty() const
621 : {
622 23 : return poTrunk_->hasOnlyEmpty();
623 : }
624 :
625 23 : int KML::getNumLayers() const
626 : {
627 23 : return nNumLayers_;
628 : }
629 :
630 875 : bool KML::selectLayer(int nNum)
631 : {
632 875 : if (nNumLayers_ < 1 || nNum >= nNumLayers_)
633 0 : return false;
634 875 : poCurrent_ = papoLayers_[nNum];
635 875 : return true;
636 : }
637 :
638 82 : std::string KML::getCurrentName() const
639 : {
640 82 : std::string tmp;
641 82 : if (poCurrent_ != nullptr)
642 : {
643 82 : tmp = poCurrent_->getNameElement();
644 : }
645 82 : return tmp;
646 : }
647 :
648 202 : Nodetype KML::getCurrentType() const
649 : {
650 202 : if (poCurrent_ != nullptr)
651 202 : return poCurrent_->getType();
652 :
653 0 : return Unknown;
654 : }
655 :
656 74 : int KML::is25D() const
657 : {
658 74 : if (poCurrent_ != nullptr)
659 74 : return poCurrent_->is25D();
660 :
661 0 : return Unknown;
662 : }
663 :
664 61 : int KML::getNumFeatures()
665 : {
666 61 : if (poCurrent_ == nullptr)
667 0 : return -1;
668 :
669 61 : return static_cast<int>(poCurrent_->getNumFeatures());
670 : }
671 :
672 916 : Feature *KML::getFeature(std::size_t nNum, int &nLastAsked, int &nLastCount)
673 : {
674 916 : if (poCurrent_ == nullptr)
675 0 : return nullptr;
676 :
677 916 : return poCurrent_->getFeature(nNum, nLastAsked, nLastCount);
678 : }
679 :
680 116 : void KML::unregisterLayerIfMatchingThisNode(KMLNode *poNode)
681 : {
682 116 : for (int i = 0; i < nNumLayers_;)
683 : {
684 2 : if (papoLayers_[i] == poNode)
685 : {
686 2 : if (i < nNumLayers_ - 1)
687 : {
688 0 : memmove(papoLayers_ + i, papoLayers_ + i + 1,
689 0 : (nNumLayers_ - 1 - i) * sizeof(KMLNode *));
690 : }
691 2 : nNumLayers_--;
692 2 : break;
693 : }
694 0 : i++;
695 : }
696 116 : }
|