File indexing completed on 2025-01-05 03:59:10

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