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 }