File indexing completed on 2024-05-19 03:51:49

0001 /*
0002     SPDX-FileCopyrightText: 2008 Nikolas Zimmermann <zimmermann@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 
0008 // Own
0009 #include "GeoParser.h"
0010 
0011 // Marble
0012 #include "MarbleDebug.h"
0013 
0014 // Geodata
0015 #include "GeoDocument.h"
0016 #include "GeoTagHandler.h"
0017 
0018 namespace Marble
0019 {
0020 
0021 // Set to a value greater than 0, to dump parent node chain while parsing
0022 #define DUMP_PARENT_STACK 0
0023 
0024 GeoParser::GeoParser( GeoDataGenericSourceType source )
0025     : QXmlStreamReader(),
0026       m_document( nullptr ),
0027       m_source( source )
0028 {
0029 }
0030 
0031 GeoParser::~GeoParser()
0032 {
0033     delete m_document;
0034 }
0035 
0036 #if DUMP_PARENT_STACK > 0
0037 static void dumpParentStack( const QString& name, int size, bool close )
0038 {
0039     static int depth = 0;
0040 
0041     if ( !close )
0042         depth++;
0043 
0044     QString result;
0045     for ( int i = 0; i < depth; ++i )
0046         result += QLatin1Char(' ');
0047 
0048     if ( close ) {
0049         depth--;
0050         result += QLatin1String("</");
0051     } else
0052         result += QLatin1Char('<');
0053 
0054     result += name + QLatin1String("> stack size ") + QString::number(size);
0055     fprintf( stderr, "%s\n", qPrintable( result ));
0056 }
0057 #endif
0058 
0059 bool GeoParser::read( QIODevice* device )
0060 {
0061     // Assert previous document got released.
0062     Q_ASSERT( !m_document );
0063     m_document = createDocument();
0064     Q_ASSERT( m_document );
0065 
0066     // Set data source
0067     setDevice( device );
0068 
0069     // Start parsing
0070     while ( !atEnd() ) {
0071         readNext();
0072 
0073         if ( isStartElement() ) {
0074             if ( isValidRootElement() ) {
0075 #if DUMP_PARENT_STACK > 0
0076                 dumpParentStack( name().toString(), m_nodeStack.size(), false );
0077 #endif
0078  
0079                 parseDocument();
0080 
0081                 if ( !m_nodeStack.isEmpty() )
0082                     raiseError(
0083                         // Keep trailing space in both strings, to match translated string
0084                         // TODO: check if that space is kept through the tool pipeline
0085                         //~ singular Parsing failed line %1. Still %n unclosed tag after document end. 
0086                         //~ plural Parsing failed line %1. Still %n unclosed tags after document end. 
0087                         QObject::tr("Parsing failed line %1. Still %n unclosed tag(s) after document end. ", "",
0088                                      m_nodeStack.size() ).arg( lineNumber() ) + errorString());
0089             } else
0090                 return false;
0091         }
0092     }
0093 
0094     if ( error() ) {
0095         if ( lineNumber() == 1) {
0096             raiseError(QString());
0097         }
0098         // Defer the deletion to the dtor
0099         // This allows the BookmarkManager to recover the broken .kml files it produced in Marble 1.0 and 1.1
0100         /** @todo: Remove this workaround around Marble 1.4 */
0101         // delete releaseDocument();
0102     }
0103     return !error();
0104 }
0105 
0106 bool GeoParser::isValidElement( const QString& tagName ) const
0107 {
0108     return name() == tagName;
0109 }
0110 
0111 GeoStackItem GeoParser::parentElement( unsigned int depth ) const
0112 {
0113     QStack<GeoStackItem>::const_iterator it = m_nodeStack.constEnd() - 1;
0114 
0115     if ( it - depth < m_nodeStack.constBegin() )
0116         return GeoStackItem();
0117 
0118     return *(it - depth);
0119 }
0120 
0121 void GeoParser::parseDocument()
0122 {
0123     if( !isStartElement() ) {
0124         raiseError( QObject::tr("Error parsing file at line: %1 and column %2 . ")
0125                     .arg( lineNumber() ).arg( columnNumber() )
0126                     +  QObject::tr("This is an Invalid File") );
0127         return;
0128     }
0129 
0130     bool processChildren = true;
0131     QualifiedName qName( name().toString(), namespaceUri().toString() );
0132 
0133     if( tokenType() == QXmlStreamReader::Invalid )
0134         raiseWarning( QString( "%1: %2" ).arg( error() ).arg( errorString() ) );
0135 
0136     GeoStackItem stackItem( qName, nullptr );
0137 
0138     if ( const GeoTagHandler* handler = GeoTagHandler::recognizes( qName )) {
0139         stackItem.assignNode( handler->parse( *this ));
0140         processChildren = !isEndElement();
0141     }
0142     // Only add GeoStackItem to the parent chain, if the tag handler
0143     // for the current element possibly contains non-textual children.
0144     // Consider following DGML snippet "<name>Test</name>" - the
0145     // DGMLNameTagHandler assumes that <name> only contains textual
0146     // children, and reads the joined value of all children using
0147     // readElementText(). This implicates that tags like <name>
0148     // don't contain any children that would need to be processed using
0149     // this parseDocument() function.
0150     if ( processChildren ) {
0151         m_nodeStack.push( stackItem );
0152 #if DUMP_PARENT_STACK > 0
0153         dumpParentStack( name().toString(), m_nodeStack.size(), false );
0154 #endif
0155         while ( !atEnd() ) {
0156             readNext();
0157             if ( isEndElement() ) {
0158                 m_nodeStack.pop();
0159 #if DUMP_PARENT_STACK > 0
0160                 dumpParentStack( name().toString(), m_nodeStack.size(), true );
0161 #endif
0162                 break;
0163             }
0164 
0165             if ( isStartElement() ) {
0166                 parseDocument();
0167             }
0168         }
0169     }
0170 #if DUMP_PARENT_STACK > 0
0171     else {
0172         // This is only used for debugging purposes.
0173         m_nodeStack.push( stackItem );
0174         dumpParentStack(name().toString() + QLatin1String("-discarded"), m_nodeStack.size(), false);
0175 
0176         m_nodeStack.pop();
0177         dumpParentStack(name().toString() + QLatin1String("-discarded"), m_nodeStack.size(), true);
0178     }
0179 #endif
0180 }
0181 
0182 void GeoParser::raiseWarning( const QString& warning )
0183 {
0184     // TODO: Maybe introduce a strict parsing mode where we feed the warning to
0185     // raiseError() (which stops parsing).
0186     mDebug() << "[GeoParser::raiseWarning] -> " << warning;
0187 }
0188 
0189 QString GeoParser::attribute( const char* attributeName ) const
0190 {
0191     return attributes().value(QLatin1String(attributeName)).toString();
0192 }
0193 
0194 GeoDocument* GeoParser::releaseDocument()
0195 {
0196     GeoDocument* document = m_document;
0197     m_document = nullptr;
0198     return document;
0199 }
0200 
0201 }