File indexing completed on 2024-11-24 04:15:40

0001 /*
0002     SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "mapcsscondition_p.h"
0008 #include "mapcssstate_p.h"
0009 
0010 #include <QDebug>
0011 #include <QIODevice>
0012 
0013 using namespace KOSMIndoorMap;
0014 
0015 MapCSSCondition::MapCSSCondition() = default;
0016 MapCSSCondition::MapCSSCondition(MapCSSCondition &&) = default;
0017 MapCSSCondition::~MapCSSCondition() = default;
0018 
0019 static double toNumber(const QByteArray &val)
0020 {
0021     bool res = false;
0022     const auto n = val.toDouble(&res);
0023     return res ? n : NAN;
0024 }
0025 
0026 void MapCSSCondition::compile(const OSM::DataSet &dataSet)
0027 {
0028     if (m_key == "mx:closed") {
0029         m_tagKey = dataSet.tagKey("opening_hours");
0030         m_op = (m_op == KeyNotSet ? IsNotClosed : IsClosed);
0031     } else {
0032         m_tagKey = dataSet.tagKey(m_key.constData());
0033     }
0034 
0035     switch(m_op) {
0036         case KeySet:
0037         case KeyNotSet:
0038             break;
0039         case Equal:
0040         case NotEqual:
0041             if (m_value.isEmpty() && std::isnan(m_numericValue)) {
0042                 qWarning() << "Empty comparison, use key (not) set operation instead!";
0043             }
0044             break;
0045         case LessThan:
0046         case GreaterThan:
0047         case LessOrEqual:
0048         case GreaterOrEqual:
0049             if (std::isnan(m_numericValue)) {
0050                 qWarning() << "Numeric comparison without numeric value set!";
0051             }
0052             break;
0053         case IsClosed:
0054         case IsNotClosed:
0055             break;
0056     }
0057 }
0058 
0059 bool MapCSSCondition::matches(const MapCSSState &state) const
0060 {
0061     if (m_tagKey.isNull()) {
0062         // if we couldn't compile the tag, it doesn't exist and thus can never be set
0063         return m_op == KeyNotSet || m_op == NotEqual;
0064     }
0065 
0066     // this method is such a hot path that even the ref/deref in QByteArray for OSM::Element::tagValue matters
0067     // so we do tag lookup manually here
0068     const auto tagEnd = state.element.tagsEnd();
0069     const auto tagIt = std::lower_bound(state.element.tagsBegin(), tagEnd, m_tagKey);
0070     const auto tagIsSet = (tagIt != tagEnd && (*tagIt).key == m_tagKey);
0071     switch (m_op) {
0072         case KeySet:
0073             return tagIsSet;
0074         case KeyNotSet:
0075             return !tagIsSet;
0076         case Equal:
0077             if (std::isnan(m_numericValue)) {
0078                 return !tagIsSet ? false : (*tagIt).value == m_value;
0079             }
0080             return !tagIsSet ? false : toNumber((*tagIt).value) == m_numericValue;
0081         case NotEqual:
0082             if (std::isnan(m_numericValue)) {
0083                 return !tagIsSet ? true : (*tagIt).value != m_value;
0084             }
0085             return !tagIsSet ? true : toNumber((*tagIt).value) != m_numericValue;
0086         case LessThan: return !tagIsSet ? false : toNumber((*tagIt).value) < m_numericValue;
0087         case GreaterThan: return !tagIsSet ? false : toNumber((*tagIt).value) > m_numericValue;
0088         case LessOrEqual: return !tagIsSet ? false : toNumber((*tagIt).value) <= m_numericValue;
0089         case GreaterOrEqual: return !tagIsSet ? false : toNumber((*tagIt).value) >= m_numericValue;
0090         case IsClosed:
0091         case IsNotClosed:
0092         {
0093             if (!tagIsSet || (*tagIt).value.isEmpty() || !state.openingHours) {
0094                 return m_op == IsNotClosed;
0095             }
0096             const auto closed = state.openingHours->isClosed(state.element, (*tagIt).value);
0097             return m_op == IsClosed ? closed : !closed;
0098         }
0099     }
0100     return false;
0101 }
0102 
0103 bool MapCSSCondition::matchesCanvas(const MapCSSState &state) const
0104 {
0105     if (m_key != "level") {
0106         return false;
0107     }
0108 
0109     switch (m_op) {
0110         case KeySet:
0111         case KeyNotSet:
0112         case IsClosed:
0113         case IsNotClosed:
0114             return false;
0115         case Equal: return (state.floorLevel/10) == m_numericValue;
0116         case NotEqual: return (state.floorLevel/10) != m_numericValue;
0117         case LessThan: return (state.floorLevel/10) < m_numericValue;
0118         case GreaterThan: return (state.floorLevel/10) > m_numericValue;
0119         case LessOrEqual: return (state.floorLevel/10) <= m_numericValue;
0120         case GreaterOrEqual: return (state.floorLevel/10) >= m_numericValue;
0121     }
0122 
0123     return false;
0124 }
0125 
0126 void MapCSSCondition::setKey(const char *key, int len)
0127 {
0128     m_key = QByteArray(key, len);
0129 }
0130 
0131 void MapCSSCondition::setOperation(MapCSSCondition::Operator op)
0132 {
0133     m_op = op;
0134 }
0135 
0136 void MapCSSCondition::setValue(const char *value, int len)
0137 {
0138     m_value = QByteArray(value, len);
0139 }
0140 
0141 void MapCSSCondition::setValue(double val)
0142 {
0143     m_numericValue = val;
0144 }
0145 
0146 void MapCSSCondition::write(QIODevice *out) const
0147 {
0148     out->write("[");
0149     if (m_op == KeyNotSet) { out->write("!"); }
0150     out->write(m_key);
0151 
0152     switch (m_op) {
0153         case KeySet:
0154         case KeyNotSet:
0155         case IsClosed:
0156         case IsNotClosed:
0157             out->write("]"); return;
0158         case Equal: out->write("="); break;
0159         case NotEqual: out->write("!="); break;
0160         case LessThan: out->write("<"); break;
0161         case GreaterThan: out->write(">"); break;
0162         case LessOrEqual: out->write("<="); break;
0163         case GreaterOrEqual: out->write(">="); break;
0164     }
0165 
0166     if (m_numericValue != NAN && m_value.isEmpty()) {
0167         out->write(QByteArray::number(m_numericValue));
0168     } else {
0169         // TODO quote if m_value contains non-identifier chars
0170         out->write(m_value);
0171     }
0172 
0173     out->write("]");
0174 }
0175 
0176 
0177 void MapCSSConditionHolder::addCondition(MapCSSCondition *condition)
0178 {
0179     conditions.push_back(std::unique_ptr<MapCSSCondition>(condition));
0180 }