File indexing completed on 2025-01-26 03:34:15
0001 /* 0002 File : XYAnalysisCurve.h 0003 Project : LabPlot 0004 Description : Base class for all analysis curves 0005 -------------------------------------------------------------------- 0006 SPDX-FileCopyrightText: 2017-2022 Alexander Semke <alexander.semke@web.de> 0007 SPDX-FileCopyrightText: 2018-2022 Stefan Gerlach <stefan.gerlach@uni.kn> 0008 SPDX-License-Identifier: GPL-2.0-or-later 0009 */ 0010 0011 /*! 0012 \class XYAnalysisCurve 0013 \brief Base class for all analysis curves 0014 0015 \ingroup worksheet 0016 */ 0017 0018 #include "XYAnalysisCurve.h" 0019 #include "XYAnalysisCurvePrivate.h" 0020 #include "backend/core/Project.h" 0021 #include "backend/core/column/Column.h" 0022 #include "backend/lib/XmlStreamReader.h" 0023 #include "backend/lib/commandtemplates.h" 0024 #include "backend/lib/macros.h" 0025 #include "backend/spreadsheet/Spreadsheet.h" 0026 #include "backend/worksheet/plots/cartesian/Symbol.h" 0027 #include "backend/worksheet/plots/cartesian/XYFitCurve.h" 0028 #include "backend/worksheet/plots/cartesian/XYSmoothCurve.h" 0029 0030 #include <KLocalizedString> 0031 #include <QDateTime> 0032 0033 XYAnalysisCurve::XYAnalysisCurve(const QString& name, XYAnalysisCurvePrivate* dd, AspectType type) 0034 : XYCurve(name, dd, type) { 0035 init(); 0036 } 0037 0038 // no need to delete the d-pointer here - it inherits from QGraphicsItem 0039 // and is deleted during the cleanup in QGraphicsScene 0040 XYAnalysisCurve::~XYAnalysisCurve() = default; 0041 0042 void XYAnalysisCurve::init() { 0043 Q_D(XYAnalysisCurve); 0044 d->lineType = XYCurve::LineType::Line; 0045 d->symbol->setStyle(Symbol::Style::NoSymbols); 0046 } 0047 0048 bool XYAnalysisCurve::resultAvailable() const { 0049 return result().available; 0050 } 0051 0052 bool XYAnalysisCurve::usingColumn(const Column* column) const { 0053 Q_D(const XYAnalysisCurve); 0054 0055 if (d->dataSourceType == DataSourceType::Spreadsheet) 0056 return (d->xDataColumn == column || d->yDataColumn == column || d->y2DataColumn == column); 0057 else 0058 return (d->dataSourceCurve->xColumn() == column || d->dataSourceCurve->yColumn() == column); 0059 } 0060 0061 void XYAnalysisCurve::updateColumnDependencies(const AbstractColumn* column) { 0062 D(const XYAnalysisCurve); 0063 const QString& columnPath = column->path(); 0064 setUndoAware(false); 0065 0066 if (d->xDataColumnPath == columnPath) 0067 setXDataColumn(column); 0068 if (d->yDataColumnPath == columnPath) 0069 setYDataColumn(column); 0070 if (d->y2DataColumnPath == columnPath) 0071 setY2DataColumn(column); 0072 0073 auto* fitCurve = dynamic_cast<XYFitCurve*>(this); 0074 if (fitCurve) { 0075 if (fitCurve->xErrorColumnPath() == columnPath) 0076 fitCurve->setXErrorColumn(column); 0077 if (fitCurve->yErrorColumnPath() == columnPath) 0078 fitCurve->setYErrorColumn(column); 0079 } 0080 0081 if (d->valuesColumnPath == columnPath) 0082 setValuesColumn(column); 0083 0084 setUndoAware(true); 0085 } 0086 0087 // copy valid data from x/y data columns to x/y data vectors 0088 // for analysis functions 0089 // avgUniqueX: average y values for duplicate x values 0090 void XYAnalysisCurve::copyData(QVector<double>& xData, 0091 QVector<double>& yData, 0092 const AbstractColumn* xDataColumn, 0093 const AbstractColumn* yDataColumn, 0094 double xMin, 0095 double xMax, 0096 bool avgUniqueX) { 0097 const int rowCount = std::min(xDataColumn->rowCount(), yDataColumn->rowCount()); 0098 bool uniqueX = true; 0099 for (int row = 0; row < rowCount; ++row) { 0100 if (!xDataColumn->isValid(row) || xDataColumn->isMasked(row) || !yDataColumn->isValid(row) || yDataColumn->isMasked(row)) 0101 continue; 0102 0103 double x = NAN; 0104 switch (xDataColumn->columnMode()) { 0105 case AbstractColumn::ColumnMode::Double: 0106 x = xDataColumn->valueAt(row); 0107 break; 0108 case AbstractColumn::ColumnMode::Integer: 0109 x = xDataColumn->integerAt(row); 0110 break; 0111 case AbstractColumn::ColumnMode::BigInt: 0112 x = xDataColumn->bigIntAt(row); 0113 break; 0114 case AbstractColumn::ColumnMode::Text: // invalid 0115 break; 0116 case AbstractColumn::ColumnMode::DateTime: 0117 case AbstractColumn::ColumnMode::Day: 0118 case AbstractColumn::ColumnMode::Month: 0119 x = xDataColumn->dateTimeAt(row).toMSecsSinceEpoch(); 0120 } 0121 0122 double y = NAN; 0123 switch (yDataColumn->columnMode()) { 0124 case AbstractColumn::ColumnMode::Double: 0125 y = yDataColumn->valueAt(row); 0126 break; 0127 case AbstractColumn::ColumnMode::Integer: 0128 y = yDataColumn->integerAt(row); 0129 break; 0130 case AbstractColumn::ColumnMode::BigInt: 0131 y = yDataColumn->bigIntAt(row); 0132 break; 0133 case AbstractColumn::ColumnMode::Text: // invalid 0134 break; 0135 case AbstractColumn::ColumnMode::DateTime: 0136 case AbstractColumn::ColumnMode::Day: 0137 case AbstractColumn::ColumnMode::Month: 0138 y = yDataColumn->dateTimeAt(row).toMSecsSinceEpoch(); 0139 } 0140 0141 // only when inside given range 0142 if (x >= xMin && x <= xMax) { 0143 if (xData.contains(x)) 0144 uniqueX = false; 0145 xData.append(x); 0146 yData.append(y); 0147 } 0148 } 0149 0150 if (uniqueX || !avgUniqueX) 0151 return; 0152 0153 // for (int i = 0; i < xData.size(); i++) 0154 // WARN( xData.at(i) << " " << yData.at(i)) 0155 0156 // average values for consecutive same x value 0157 double oldX = NAN, sum = 0.; 0158 int count = 1; 0159 for (int i = 0; i < xData.size(); i++) { 0160 // WARN(" i = " << i) 0161 const double x = xData.at(i); 0162 const double y = yData.at(i); 0163 // WARN(x << " / " << y << ": " << sum << " " << oldX << " " << count) 0164 if (x == oldX) { // same x, but not last 0165 // DEBUG(" same x") 0166 sum += y; 0167 count++; 0168 if (i < xData.size() - 1) { 0169 continue; 0170 } 0171 } 0172 0173 // WARN(" next/last x") 0174 if (count > 1) { // average and remove duplicate 0175 // WARN("average: " << sum/count) 0176 const int index = i - count + 1; 0177 yData[index - 1] = sum / count; 0178 // WARN("remove at " << index << ", count = " << count-1) 0179 xData.remove(index, count - 1); 0180 yData.remove(index, count - 1); 0181 0182 i -= count - 1; 0183 count = 1; 0184 } 0185 sum = y; 0186 oldX = x; 0187 } 0188 } 0189 0190 // ############################################################################## 0191 // ########################## getter methods ################################## 0192 // ############################################################################## 0193 BASIC_SHARED_D_READER_IMPL(XYAnalysisCurve, XYAnalysisCurve::DataSourceType, dataSourceType, dataSourceType) 0194 BASIC_SHARED_D_READER_IMPL(XYAnalysisCurve, const XYCurve*, dataSourceCurve, dataSourceCurve) 0195 const QString& XYAnalysisCurve::dataSourceCurvePath() const { 0196 D(XYAnalysisCurve); 0197 return d->dataSourceCurvePath; 0198 } 0199 0200 BASIC_SHARED_D_READER_IMPL(XYAnalysisCurve, const AbstractColumn*, xDataColumn, xDataColumn) 0201 BASIC_SHARED_D_READER_IMPL(XYAnalysisCurve, const AbstractColumn*, yDataColumn, yDataColumn) 0202 BASIC_SHARED_D_READER_IMPL(XYAnalysisCurve, const AbstractColumn*, y2DataColumn, y2DataColumn) 0203 BASIC_SHARED_D_READER_IMPL(XYAnalysisCurve, QString, xDataColumnPath, xDataColumnPath) 0204 BASIC_SHARED_D_READER_IMPL(XYAnalysisCurve, QString, yDataColumnPath, yDataColumnPath) 0205 BASIC_SHARED_D_READER_IMPL(XYAnalysisCurve, QString, y2DataColumnPath, y2DataColumnPath) 0206 0207 bool XYAnalysisCurve::saveCalculations() const { 0208 return const_cast<XYAnalysisCurve*>(this)->project()->saveCalculations(); 0209 } 0210 0211 // ############################################################################## 0212 // ################# setter methods and undo commands ########################## 0213 // ############################################################################## 0214 STD_SETTER_CMD_IMPL_S(XYAnalysisCurve, SetDataSourceType, XYAnalysisCurve::DataSourceType, dataSourceType) 0215 void XYAnalysisCurve::setDataSourceType(DataSourceType type) { 0216 Q_D(XYAnalysisCurve); 0217 if (type != d->dataSourceType) 0218 exec(new XYAnalysisCurveSetDataSourceTypeCmd(d, type, ki18n("%1: data source type changed"))); 0219 } 0220 0221 STD_SETTER_CMD_IMPL_F_S(XYAnalysisCurve, SetDataSourceCurve, const XYCurve*, dataSourceCurve, retransform) 0222 void XYAnalysisCurve::setDataSourceCurve(const XYCurve* curve) { 0223 Q_D(XYAnalysisCurve); 0224 if (curve != d->dataSourceCurve) { 0225 exec(new XYAnalysisCurveSetDataSourceCurveCmd(d, curve, ki18n("%1: data source curve changed"))); 0226 handleSourceDataChanged(); 0227 0228 // handle the changes when different columns were provided for the source curve 0229 connect(curve, SIGNAL(xColumnChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); 0230 connect(curve, SIGNAL(yColumnChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); 0231 // TODO? connect(curve, SIGNAL(y2ColumnChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); 0232 0233 // handle the changes when the data inside of the source curve columns 0234 connect(curve, &XYCurve::xDataChanged, this, &XYAnalysisCurve::handleSourceDataChanged); 0235 connect(curve, &XYCurve::yDataChanged, this, &XYAnalysisCurve::handleSourceDataChanged); 0236 0237 // TODO: add disconnect in the undo-function 0238 } 0239 } 0240 0241 STD_SETTER_CMD_IMPL_S(XYAnalysisCurve, SetXDataColumn, const AbstractColumn*, xDataColumn) 0242 void XYAnalysisCurve::setXDataColumn(const AbstractColumn* column) { 0243 DEBUG(Q_FUNC_INFO); 0244 Q_D(XYAnalysisCurve); 0245 if (column != d->xDataColumn) { 0246 exec(new XYAnalysisCurveSetXDataColumnCmd(d, column, ki18n("%1: assign x-data"))); 0247 handleSourceDataChanged(); 0248 if (column) { 0249 setXDataColumnPath(column->path()); 0250 connect(column->parentAspect(), &AbstractAspect::childAspectAboutToBeRemoved, this, &XYAnalysisCurve::xDataColumnAboutToBeRemoved); 0251 connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); 0252 connect(column, &AbstractAspect::aspectDescriptionChanged, this, &XYAnalysisCurve::xDataColumnNameChanged); 0253 // TODO disconnect on undo 0254 } else 0255 setXDataColumnPath(QString()); 0256 } 0257 } 0258 0259 STD_SETTER_CMD_IMPL_S(XYAnalysisCurve, SetYDataColumn, const AbstractColumn*, yDataColumn) 0260 void XYAnalysisCurve::setYDataColumn(const AbstractColumn* column) { 0261 DEBUG(Q_FUNC_INFO); 0262 Q_D(XYAnalysisCurve); 0263 if (column != d->yDataColumn) { 0264 exec(new XYAnalysisCurveSetYDataColumnCmd(d, column, ki18n("%1: assign y-data"))); 0265 handleSourceDataChanged(); 0266 if (column) { 0267 setYDataColumnPath(column->path()); 0268 connect(column->parentAspect(), &AbstractAspect::childAspectAboutToBeRemoved, this, &XYAnalysisCurve::yDataColumnAboutToBeRemoved); 0269 connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); 0270 connect(column, &AbstractAspect::aspectDescriptionChanged, this, &XYAnalysisCurve::yDataColumnNameChanged); 0271 // TODO disconnect on undo 0272 } else 0273 setYDataColumnPath(QString()); 0274 } 0275 } 0276 0277 STD_SETTER_CMD_IMPL_S(XYAnalysisCurve, SetY2DataColumn, const AbstractColumn*, y2DataColumn) 0278 void XYAnalysisCurve::setY2DataColumn(const AbstractColumn* column) { 0279 DEBUG(Q_FUNC_INFO); 0280 Q_D(XYAnalysisCurve); 0281 if (column != d->y2DataColumn) { 0282 exec(new XYAnalysisCurveSetY2DataColumnCmd(d, column, ki18n("%1: assign second y-data"))); 0283 handleSourceDataChanged(); 0284 if (column) { 0285 setY2DataColumnPath(column->path()); 0286 connect(column->parentAspect(), &AbstractAspect::childAspectAboutToBeRemoved, this, &XYAnalysisCurve::y2DataColumnAboutToBeRemoved); 0287 connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); 0288 connect(column, &AbstractAspect::aspectDescriptionChanged, this, &XYAnalysisCurve::y2DataColumnNameChanged); 0289 // TODO disconnect on undo 0290 } else 0291 setY2DataColumnPath(QString()); 0292 } 0293 } 0294 0295 void XYAnalysisCurve::setXDataColumnPath(const QString& path) { 0296 Q_D(XYAnalysisCurve); 0297 d->xDataColumnPath = path; 0298 } 0299 0300 void XYAnalysisCurve::setYDataColumnPath(const QString& path) { 0301 Q_D(XYAnalysisCurve); 0302 d->yDataColumnPath = path; 0303 } 0304 0305 void XYAnalysisCurve::setY2DataColumnPath(const QString& path) { 0306 Q_D(XYAnalysisCurve); 0307 d->y2DataColumnPath = path; 0308 } 0309 0310 // ############################################################################## 0311 // ################################# SLOTS #################################### 0312 // ############################################################################## 0313 void XYAnalysisCurve::handleSourceDataChanged() { 0314 Q_D(XYAnalysisCurve); 0315 d->sourceDataChangedSinceLastRecalc = true; 0316 Q_EMIT sourceDataChanged(); 0317 } 0318 0319 void XYAnalysisCurve::xDataColumnAboutToBeRemoved(const AbstractAspect* aspect) { 0320 Q_D(XYAnalysisCurve); 0321 if (aspect == d->xDataColumn) { 0322 d->xDataColumn = nullptr; 0323 d->retransform(); 0324 } 0325 } 0326 0327 void XYAnalysisCurve::yDataColumnAboutToBeRemoved(const AbstractAspect* aspect) { 0328 Q_D(XYAnalysisCurve); 0329 if (aspect == d->yDataColumn) { 0330 d->yDataColumn = nullptr; 0331 d->retransform(); 0332 } 0333 } 0334 0335 void XYAnalysisCurve::y2DataColumnAboutToBeRemoved(const AbstractAspect* aspect) { 0336 Q_D(XYAnalysisCurve); 0337 if (aspect == d->y2DataColumn) { 0338 d->y2DataColumn = nullptr; 0339 d->retransform(); 0340 } 0341 } 0342 0343 void XYAnalysisCurve::xDataColumnNameChanged() { 0344 Q_D(XYAnalysisCurve); 0345 setXDataColumnPath(d->xDataColumn->path()); 0346 } 0347 0348 void XYAnalysisCurve::yDataColumnNameChanged() { 0349 Q_D(XYAnalysisCurve); 0350 setYDataColumnPath(d->yDataColumn->path()); 0351 } 0352 0353 void XYAnalysisCurve::y2DataColumnNameChanged() { 0354 Q_D(XYAnalysisCurve); 0355 setYDataColumnPath(d->y2DataColumn->path()); 0356 } 0357 0358 /*! 0359 * creates a new spreadsheet having the data with the results of the calculation. 0360 * the new spreadsheet is added to the current folder. 0361 */ 0362 void XYAnalysisCurve::createDataSpreadsheet() { 0363 if (!xColumn() || !yColumn()) 0364 return; 0365 0366 auto* spreadsheet = new Spreadsheet(i18n("%1 - Data", name())); 0367 spreadsheet->removeColumns(0, spreadsheet->columnCount()); // remove default columns 0368 spreadsheet->setRowCount(xColumn()->rowCount()); 0369 0370 // x values 0371 auto* data = static_cast<const Column*>(xColumn())->data(); 0372 auto* xColumn = new Column(QLatin1String("x"), *static_cast<QVector<double>*>(data)); 0373 xColumn->setPlotDesignation(AbstractColumn::PlotDesignation::X); 0374 spreadsheet->addChild(xColumn); 0375 0376 // y values 0377 data = static_cast<const Column*>(yColumn())->data(); 0378 auto* yColumn = new Column(QLatin1String("y"), *static_cast<QVector<double>*>(data)); 0379 yColumn->setPlotDesignation(AbstractColumn::PlotDesignation::Y); 0380 spreadsheet->addChild(yColumn); 0381 0382 // residual values for fit curves 0383 if (type() == AspectType::XYFitCurve) { 0384 data = static_cast<const Column*>(static_cast<XYFitCurve*>(this)->residualsColumn())->data(); 0385 auto* residualsColumn = new Column(QLatin1String("residuals"), *static_cast<QVector<double>*>(data)); 0386 residualsColumn->setPlotDesignation(AbstractColumn::PlotDesignation::Y); 0387 spreadsheet->addChild(residualsColumn); 0388 } else if (type() == AspectType::XYSmoothCurve) { 0389 // rough values for smooth curves 0390 data = static_cast<const Column*>(static_cast<XYSmoothCurve*>(this)->roughsColumn())->data(); 0391 auto* roughsColumn = new Column(QLatin1String("rough values"), *static_cast<QVector<double>*>(data)); 0392 roughsColumn->setPlotDesignation(AbstractColumn::PlotDesignation::Y); 0393 spreadsheet->addChild(roughsColumn); 0394 } 0395 0396 // add the new spreadsheet to the current folder 0397 folder()->addChild(spreadsheet); 0398 } 0399 0400 // ############################################################################## 0401 // ######################### Private implementation ############################# 0402 // ############################################################################## 0403 XYAnalysisCurvePrivate::XYAnalysisCurvePrivate(XYAnalysisCurve* owner) 0404 : XYCurvePrivate(owner) 0405 , q(owner) { 0406 } 0407 0408 // no need to delete xColumn and yColumn, they are deleted 0409 // when the parent aspect is removed 0410 XYAnalysisCurvePrivate::~XYAnalysisCurvePrivate() = default; 0411 0412 void XYAnalysisCurvePrivate::prepareTmpDataColumn(const AbstractColumn** tmpXDataColumn, const AbstractColumn** tmpYDataColumn) { 0413 if (dataSourceType == XYAnalysisCurve::DataSourceType::Spreadsheet) { 0414 // spreadsheet columns as data source 0415 *tmpXDataColumn = xDataColumn; 0416 *tmpYDataColumn = yDataColumn; 0417 } else { 0418 // curve columns as data source 0419 *tmpXDataColumn = dataSourceCurve->xColumn(); 0420 *tmpYDataColumn = dataSourceCurve->yColumn(); 0421 } 0422 } 0423 0424 void XYAnalysisCurvePrivate::recalculate() { 0425 // create filter result columns if not available yet, clear them otherwise 0426 if (!xColumn) { 0427 xColumn = new Column(QStringLiteral("x"), AbstractColumn::ColumnMode::Double); 0428 yColumn = new Column(QStringLiteral("y"), AbstractColumn::ColumnMode::Double); 0429 xVector = static_cast<QVector<double>*>(xColumn->data()); 0430 yVector = static_cast<QVector<double>*>(yColumn->data()); 0431 0432 xColumn->setHidden(true); 0433 q->addChild(xColumn); 0434 yColumn->setHidden(true); 0435 q->addChild(yColumn); 0436 0437 q->setUndoAware(false); 0438 q->setXColumn(xColumn); 0439 q->setYColumn(yColumn); 0440 q->setUndoAware(true); 0441 } else { 0442 xColumn->invalidateProperties(); 0443 yColumn->invalidateProperties(); 0444 xVector->clear(); 0445 yVector->clear(); 0446 } 0447 0448 resetResults(); 0449 0450 const AbstractColumn* tmpXDataColumn = nullptr; 0451 const AbstractColumn* tmpYDataColumn = nullptr; 0452 prepareTmpDataColumn(&tmpXDataColumn, &tmpYDataColumn); 0453 0454 if (!preparationValid(tmpXDataColumn, tmpYDataColumn)) { 0455 sourceDataChangedSinceLastRecalc = false; 0456 // recalcLogicalPoints(); TODO: needed? 0457 } else { 0458 bool result = recalculateSpecific(tmpXDataColumn, tmpYDataColumn); 0459 sourceDataChangedSinceLastRecalc = false; 0460 0461 if (result) { 0462 // redraw the curve 0463 recalcLogicalPoints(); 0464 } 0465 } 0466 Q_EMIT q->dataChanged(); 0467 } 0468 0469 bool XYAnalysisCurvePrivate::preparationValid(const AbstractColumn* tmpXDataColumn, const AbstractColumn* tmpYDataColumn) { 0470 return tmpXDataColumn && tmpYDataColumn; 0471 } 0472 0473 // ############################################################################## 0474 // ################## Serialization/Deserialization ########################### 0475 // ############################################################################## 0476 //! Save as XML 0477 void XYAnalysisCurve::save(QXmlStreamWriter* writer) const { 0478 Q_D(const XYAnalysisCurve); 0479 0480 writer->writeStartElement(QStringLiteral("xyAnalysisCurve")); 0481 0482 // write xy-curve information 0483 XYCurve::save(writer); 0484 0485 // write data source specific information 0486 writer->writeStartElement(QStringLiteral("dataSource")); 0487 writer->writeAttribute(QStringLiteral("type"), QString::number(static_cast<int>(d->dataSourceType))); 0488 WRITE_PATH(d->dataSourceCurve, dataSourceCurve); 0489 WRITE_COLUMN(d->xDataColumn, xDataColumn); 0490 WRITE_COLUMN(d->yDataColumn, yDataColumn); 0491 WRITE_COLUMN(d->y2DataColumn, y2DataColumn); 0492 writer->writeEndElement(); 0493 0494 writer->writeEndElement(); //"xyAnalysisCurve" 0495 } 0496 0497 //! Load from XML 0498 bool XYAnalysisCurve::load(XmlStreamReader* reader, bool preview) { 0499 Q_D(XYAnalysisCurve); 0500 0501 QXmlStreamAttributes attribs; 0502 QString str; 0503 0504 while (!reader->atEnd()) { 0505 reader->readNext(); 0506 if (reader->isEndElement() && reader->name() == QLatin1String("xyAnalysisCurve")) 0507 break; 0508 0509 if (!reader->isStartElement()) 0510 continue; 0511 0512 if (reader->name() == QLatin1String("xyCurve")) { 0513 if (!XYCurve::load(reader, preview)) 0514 return false; 0515 } else if (reader->name() == QLatin1String("dataSource")) { 0516 attribs = reader->attributes(); 0517 READ_INT_VALUE("type", dataSourceType, XYAnalysisCurve::DataSourceType); 0518 READ_PATH(dataSourceCurve); 0519 READ_COLUMN(xDataColumn); 0520 READ_COLUMN(yDataColumn); 0521 READ_COLUMN(y2DataColumn); 0522 } else { // unknown element 0523 reader->raiseUnknownElementWarning(); 0524 if (!reader->skipToEndElement()) 0525 return false; 0526 } 0527 } 0528 0529 return true; 0530 }