File indexing completed on 2024-04-28 15:22:38

0001 /*
0002  * This file is part of the DOM implementation for KDE.
0003  *
0004  * Copyright 1999-2003 Lars Knoll (knoll@kde.org)
0005  * Copyright 1999 Waldo Bastian (bastian@kde.org)
0006  * Copyright 2001 Andreas Schlapbach (schlpbch@iam.unibe.ch)
0007  * Copyright 2001-2003 Dirk Mueller (mueller@kde.org)
0008  * Copyright 2002 Apple Computer, Inc.
0009  * Copyright 2004 Allan Sandfeld Jensen (kde@carewolf.com)
0010  *
0011  * This library is free software; you can redistribute it and/or
0012  * modify it under the terms of the GNU Library General Public
0013  * License as published by the Free Software Foundation; either
0014  * version 2 of the License, or (at your option) any later version.
0015  *
0016  * This library is distributed in the hope that it will be useful,
0017  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0018  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0019  * Library General Public License for more details.
0020  *
0021  * You should have received a copy of the GNU Library General Public License
0022  * along with this library; see the file COPYING.LIB.  If not, write to
0023  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0024  * Boston, MA 02110-1301, USA.
0025  */
0026 
0027 //#define CSS_DEBUG
0028 
0029 #include "css_base.h"
0030 
0031 #include <assert.h>
0032 #include "khtml_debug.h"
0033 
0034 #ifdef CSS_DEBUG
0035 #include "cssproperties.h"
0036 #endif
0037 
0038 #include "css_stylesheetimpl.h"
0039 #include <xml/dom_docimpl.h>
0040 #include "css_valueimpl.h"
0041 using namespace DOM;
0042 
0043 void StyleBaseImpl::checkLoaded() const
0044 {
0045     if (m_parent) {
0046         m_parent->checkLoaded();
0047     }
0048 }
0049 
0050 void StyleBaseImpl::checkPending() const
0051 {
0052     if (m_parent) {
0053         m_parent->checkPending();
0054     }
0055 }
0056 
0057 StyleSheetImpl *StyleBaseImpl::stylesheet()
0058 {
0059     StyleBaseImpl *b = this;
0060     while (b && !b->isStyleSheet()) {
0061         b = b->m_parent;
0062     }
0063     return static_cast<StyleSheetImpl *>(b);
0064 }
0065 
0066 QUrl StyleBaseImpl::baseURL()
0067 {
0068     // try to find the style sheet. If found look for its url.
0069     // If it has none, look for the parentsheet, or the parentNode and
0070     // try to find out about their url
0071 
0072     StyleSheetImpl *sheet = stylesheet();
0073 
0074     if (!sheet) {
0075         return QUrl();
0076     }
0077 
0078     if (!sheet->href().isNull()) {
0079         return QUrl(sheet->href().string());
0080     }
0081 
0082     // find parent
0083     if (sheet->parent()) {
0084         return sheet->parent()->baseURL();
0085     }
0086 
0087     if (!sheet->ownerNode()) {
0088         return QUrl();
0089     }
0090 
0091     return sheet->ownerNode()->document()->baseURL();
0092 }
0093 
0094 void StyleBaseImpl::setParsedValue(int propId, const CSSValueImpl *parsedValue,
0095                                    bool important, QList<CSSProperty *> *propList)
0096 {
0097     QMutableListIterator<CSSProperty *> propIt(*propList);
0098     propIt.toBack(); // just remove the top one - not sure what should happen if we have multiple instances of the property
0099     CSSProperty *p;
0100     while (propIt.hasPrevious()) {
0101         p = propIt.previous();
0102         if (p->m_id == propId && p->m_important == important) {
0103             delete propIt.value();
0104             propIt.remove();
0105             break;
0106         }
0107     }
0108 
0109     CSSProperty *prop = new CSSProperty();
0110     prop->m_id = propId;
0111     prop->setValue(const_cast<CSSValueImpl *>(parsedValue));
0112     prop->m_important = important;
0113 
0114     propList->append(prop);
0115 #ifdef CSS_DEBUG
0116     qCDebug(KHTML_LOG) << "added property: " << getPropertyName(propId).string()
0117              // non implemented yet << ", value: " << parsedValue->cssText().string()
0118              << " important: " << prop->m_important;
0119 #endif
0120 }
0121 
0122 // ------------------------------------------------------------------------------
0123 
0124 StyleListImpl::~StyleListImpl()
0125 {
0126     StyleBaseImpl *n;
0127 
0128     if (!m_lstChildren) {
0129         return;
0130     }
0131 
0132     QListIterator<StyleBaseImpl *> it(*m_lstChildren);
0133     while (it.hasNext()) {
0134         n = it.next();
0135         n->setParent(nullptr);
0136         if (!n->refCount()) {
0137             delete n;
0138         }
0139     }
0140     delete m_lstChildren;
0141 }
0142 
0143 // --------------------------------------------------------------------------------
0144 
0145 void CSSSelector::print(void)
0146 {
0147     // qCDebug(KHTML_LOG) << "[Selector: tag = " <<       QString::number(makeId(tagNamespace.id(), tagLocalName.id()),16) << ", attr = \"" << makeId(attrNamespace.id(), attrLocalName.id()) << "\", match = \"" << match
0148     //    << "\" value = \"" << value.string().string().toLatin1().constData() << "\" relation = " << (int)relation
0149     //    << "]";
0150     if (tagHistory) {
0151         tagHistory->print();
0152     }
0153     // qCDebug(KHTML_LOG) << "    specificity = " << specificity();
0154 }
0155 
0156 unsigned int CSSSelector::specificity() const
0157 {
0158 
0159     int s = ((tagLocalName.id() == anyLocalName) ? 0 : 1);
0160     switch (match) {
0161     case Id:
0162         s += 0x10000;
0163         break;
0164     case Exact:
0165     case Set:
0166     case List:
0167     case Class:
0168     case Hyphen:
0169     case PseudoClass:
0170     case PseudoElement:
0171     case Contain:
0172     case Begin:
0173     case End:
0174         s += 0x100;
0175     case None:
0176         break;
0177     }
0178     if (tagHistory) {
0179         s += tagHistory->specificity();
0180     }
0181     // make sure it doesn't overflow
0182     return s & 0xffffff;
0183 }
0184 
0185 void CSSSelector::extractPseudoType() const
0186 {
0187     if (match != PseudoClass && match != PseudoElement) {
0188         return;
0189     }
0190     _pseudoType = PseudoOther;
0191     bool element = false;
0192     bool compat = false;
0193     if (!value.isEmpty()) {
0194         value = value.string().lower();
0195         switch (value[0].unicode()) {
0196         case '-':
0197             if (value == "-khtml-replaced") {
0198                 _pseudoType = PseudoReplaced;
0199             } else if (value == "-khtml-marker") {
0200                 _pseudoType = PseudoMarker;
0201             }
0202             element = true;
0203             break;
0204         case 'a':
0205             if (value == "active") {
0206                 _pseudoType = PseudoActive;
0207             } else if (value == "after") {
0208                 _pseudoType = PseudoAfter;
0209                 element = compat = true;
0210             }
0211             break;
0212         case 'b':
0213             if (value == "before") {
0214                 _pseudoType = PseudoBefore;
0215                 element = compat = true;
0216             }
0217             break;
0218         case 'c':
0219             if (value == "checked") {
0220                 _pseudoType = PseudoChecked;
0221             } else if (value == "contains(") {
0222                 _pseudoType = PseudoContains;
0223             }
0224             break;
0225         case 'd':
0226             if (value == "disabled") {
0227                 _pseudoType = PseudoDisabled;
0228             }
0229             if (value == "default") {
0230                 _pseudoType = PseudoDefault;
0231             }
0232             break;
0233         case 'e':
0234             if (value == "empty") {
0235                 _pseudoType = PseudoEmpty;
0236             } else if (value == "enabled") {
0237                 _pseudoType = PseudoEnabled;
0238             }
0239             break;
0240         case 'f':
0241             if (value == "first-child") {
0242                 _pseudoType = PseudoFirstChild;
0243             } else if (value == "first-letter") {
0244                 _pseudoType = PseudoFirstLetter;
0245                 element = compat = true;
0246             } else if (value == "first-line") {
0247                 _pseudoType = PseudoFirstLine;
0248                 element = compat = true;
0249             } else if (value == "first-of-type") {
0250                 _pseudoType = PseudoFirstOfType;
0251             } else if (value == "focus") {
0252                 _pseudoType = PseudoFocus;
0253             }
0254             break;
0255         case 'h':
0256             if (value == "hover") {
0257                 _pseudoType = PseudoHover;
0258             }
0259             break;
0260         case 'i':
0261             if (value == "indeterminate") {
0262                 _pseudoType = PseudoIndeterminate;
0263             }
0264             break;
0265         case 'l':
0266             if (value == "link") {
0267                 _pseudoType = PseudoLink;
0268             } else if (value == "lang(") {
0269                 _pseudoType = PseudoLang;
0270             } else if (value == "last-child") {
0271                 _pseudoType = PseudoLastChild;
0272             } else if (value == "last-of-type") {
0273                 _pseudoType = PseudoLastOfType;
0274             }
0275             break;
0276         case 'n':
0277             if (value == "not(") {
0278                 _pseudoType = PseudoNot;
0279             } else if (value == "nth-child(") {
0280                 _pseudoType = PseudoNthChild;
0281             } else if (value == "nth-last-child(") {
0282                 _pseudoType = PseudoNthLastChild;
0283             } else if (value == "nth-of-type(") {
0284                 _pseudoType = PseudoNthOfType;
0285             } else if (value == "nth-last-of-type(") {
0286                 _pseudoType = PseudoNthLastOfType;
0287             }
0288             break;
0289         case 'o':
0290             if (value == "only-child") {
0291                 _pseudoType = PseudoOnlyChild;
0292             } else if (value == "only-of-type") {
0293                 _pseudoType = PseudoOnlyOfType;
0294             }
0295             break;
0296         case 'r':
0297             if (value == "root") {
0298                 _pseudoType = PseudoRoot;
0299             } else if (value == "read-only") {
0300                 _pseudoType = PseudoReadOnly;
0301             } else if (value == "read-write") {
0302                 _pseudoType = PseudoReadWrite;
0303             }
0304             break;
0305         case 's':
0306             if (value == "selection") {
0307                 _pseudoType = PseudoSelection;
0308                 element = true;
0309             }
0310             break;
0311         case 't':
0312             if (value == "target") {
0313                 _pseudoType = PseudoTarget;
0314             }
0315             break;
0316         case 'v':
0317             if (value == "visited") {
0318                 _pseudoType = PseudoVisited;
0319             }
0320             break;
0321         }
0322     }
0323     if (match == PseudoClass && element)
0324         if (!compat) {
0325             _pseudoType = PseudoOther;
0326         } else {
0327             match = PseudoElement;
0328         }
0329     else if (match == PseudoElement && !element) {
0330         _pseudoType = PseudoOther;
0331     }
0332 }
0333 
0334 bool CSSSelector::operator == (const CSSSelector &other) const
0335 {
0336     const CSSSelector *sel1 = this;
0337     const CSSSelector *sel2 = &other;
0338 
0339     while (sel1 && sel2) {
0340         //assert(sel1->_pseudoType != PseudoNotParsed);
0341         //assert(sel2->_pseudoType != PseudoNotParsed);
0342         if (sel1->tagLocalName.id() != sel2->tagLocalName.id() || sel1->attrLocalName.id() != sel2->attrLocalName.id() ||
0343                 sel1->tagNamespace.id() != sel2->tagNamespace.id() || sel1->attrNamespace.id() != sel2->attrNamespace.id() ||
0344                 sel1->relation != sel2->relation || sel1->match != sel2->match ||
0345                 sel1->value != sel2->value ||
0346                 sel1->pseudoType() != sel2->pseudoType() ||
0347                 sel1->string_arg != sel2->string_arg) {
0348             return false;
0349         }
0350         sel1 = sel1->tagHistory;
0351         sel2 = sel2->tagHistory;
0352     }
0353     if (sel1 || sel2) {
0354         return false;
0355     }
0356     return true;
0357 }
0358 
0359 DOMString CSSSelector::selectorText() const
0360 {
0361     // FIXME: Support namespaces when dumping the selector text.  This requires preserving
0362     // the original namespace prefix used. Ugh. -dwh
0363     DOMString str;
0364     const CSSSelector *cs = this;
0365     quint16 tag = cs->tagLocalName.id();
0366     if (tag == anyLocalName && cs->match == CSSSelector::None) {
0367         str = "*";
0368     } else if (tag != anyLocalName) {
0369         str = LocalName::fromId(tag).toString();
0370     }
0371 
0372     const CSSSelector *op = nullptr;
0373     while (true) {
0374         if (makeId(cs->attrNamespace.id(), cs->attrLocalName.id()) == ATTR_ID && cs->match == CSSSelector::Id) {
0375             str += "#";
0376             str += cs->value;
0377         } else if (cs->match == CSSSelector::Class) {
0378             str += ".";
0379             str += cs->value;
0380         } else if (cs->match == CSSSelector::PseudoClass) {
0381             str += ":";
0382             str += cs->value;
0383             if (!cs->string_arg.isEmpty()) { // e.g :nth-child(...)
0384                 str += cs->string_arg;
0385                 str += ")";
0386             } else if (cs->simpleSelector && !op) { // :not(...)
0387                 op = cs;
0388                 cs = cs->simpleSelector;
0389                 continue;
0390             }
0391         } else if (cs->match == CSSSelector::PseudoElement) {
0392             str += "::";
0393             str += cs->value;
0394         }
0395         // optional attribute
0396         else if (cs->attrLocalName.id()) {
0397             DOMString attrName = LocalName::fromId(cs->attrLocalName.id()).toString();
0398             str += "[";
0399             str += attrName;
0400             switch (cs->match) {
0401             case CSSSelector::Exact:
0402                 str += "=";
0403                 break;
0404             case CSSSelector::Set:
0405                 break;
0406             case CSSSelector::List:
0407                 str += "~=";
0408                 break;
0409             case CSSSelector::Hyphen:
0410                 str += "|=";
0411                 break;
0412             case CSSSelector::Begin:
0413                 str += "^=";
0414                 break;
0415             case CSSSelector::End:
0416                 str += "$=";
0417                 break;
0418             case CSSSelector::Contain:
0419                 str += "*=";
0420                 break;
0421             default:
0422                 qCWarning(KHTML_LOG) << "Unhandled case in CSSStyleRuleImpl::selectorText : match=" << cs->match;
0423             }
0424             if (cs->match != CSSSelector::Set) {
0425                 str += "\"";
0426                 str += cs->value;
0427                 str += "\"";
0428             }
0429             str += "]";
0430         }
0431         if (op && !cs->tagHistory) {
0432             cs = op;
0433             op = nullptr;
0434             str += ")";
0435         }
0436 
0437         if ((cs->relation != CSSSelector::SubSelector && !op) || !cs->tagHistory) {
0438             break;
0439         }
0440         cs = cs->tagHistory;
0441     }
0442 
0443     if (cs->tagHistory) {
0444         DOMString tagHistoryText = cs->tagHistory->selectorText();
0445         if (cs->relation == DirectAdjacent) {
0446             str = tagHistoryText + DOMString(" + ") + str;
0447         } else if (cs->relation == IndirectAdjacent) {
0448             str = tagHistoryText + DOMString(" ~ ") + str;
0449         } else if (cs->relation == Child) {
0450             str = tagHistoryText + DOMString(" > ") + str;
0451         } else { // Descendant
0452             str = tagHistoryText + DOMString(" ") + str;
0453         }
0454     }
0455     return str;
0456 }
0457 
0458 // ----------------------------------------------------------------------------