1 /*
2  * This file is part of the DOM implementation for KDE.
3  *
4  * Copyright 1999-2003 Lars Knoll (knoll@kde.org)
5  * Copyright 1999 Waldo Bastian (bastian@kde.org)
6  * Copyright 2001 Andreas Schlapbach (schlpbch@iam.unibe.ch)
7  * Copyright 2001-2003 Dirk Mueller (mueller@kde.org)
8  * Copyright 2002 Apple Computer, Inc.
9  * Copyright 2004 Allan Sandfeld Jensen (kde@carewolf.com)
10  *
11  * This library is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU Library General Public
13  * License as published by the Free Software Foundation; either
14  * version 2 of the License, or (at your option) any later version.
15  *
16  * This library is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19  * Library General Public License for more details.
20  *
21  * You should have received a copy of the GNU Library General Public License
22  * along with this library; see the file COPYING.LIB.  If not, write to
23  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
24  * Boston, MA 02110-1301, USA.
25  */
26 
27 //#define CSS_DEBUG
28 
29 #include "css_base.h"
30 
31 #include <assert.h>
32 #include "khtml_debug.h"
33 
34 #ifdef CSS_DEBUG
35 #include "cssproperties.h"
36 #endif
37 
38 #include "css_stylesheetimpl.h"
39 #include <xml/dom_docimpl.h>
40 #include "css_valueimpl.h"
41 using namespace DOM;
42 
checkLoaded() const43 void StyleBaseImpl::checkLoaded() const
44 {
45     if (m_parent) {
46         m_parent->checkLoaded();
47     }
48 }
49 
checkPending() const50 void StyleBaseImpl::checkPending() const
51 {
52     if (m_parent) {
53         m_parent->checkPending();
54     }
55 }
56 
stylesheet()57 StyleSheetImpl *StyleBaseImpl::stylesheet()
58 {
59     StyleBaseImpl *b = this;
60     while (b && !b->isStyleSheet()) {
61         b = b->m_parent;
62     }
63     return static_cast<StyleSheetImpl *>(b);
64 }
65 
baseURL()66 QUrl StyleBaseImpl::baseURL()
67 {
68     // try to find the style sheet. If found look for its url.
69     // If it has none, look for the parentsheet, or the parentNode and
70     // try to find out about their url
71 
72     StyleSheetImpl *sheet = stylesheet();
73 
74     if (!sheet) {
75         return QUrl();
76     }
77 
78     if (!sheet->href().isNull()) {
79         return QUrl(sheet->href().string());
80     }
81 
82     // find parent
83     if (sheet->parent()) {
84         return sheet->parent()->baseURL();
85     }
86 
87     if (!sheet->ownerNode()) {
88         return QUrl();
89     }
90 
91     return sheet->ownerNode()->document()->baseURL();
92 }
93 
setParsedValue(int propId,const CSSValueImpl * parsedValue,bool important,QList<CSSProperty * > * propList)94 void StyleBaseImpl::setParsedValue(int propId, const CSSValueImpl *parsedValue,
95                                    bool important, QList<CSSProperty *> *propList)
96 {
97     QMutableListIterator<CSSProperty *> propIt(*propList);
98     propIt.toBack(); // just remove the top one - not sure what should happen if we have multiple instances of the property
99     CSSProperty *p;
100     while (propIt.hasPrevious()) {
101         p = propIt.previous();
102         if (p->m_id == propId && p->m_important == important) {
103             delete propIt.value();
104             propIt.remove();
105             break;
106         }
107     }
108 
109     CSSProperty *prop = new CSSProperty();
110     prop->m_id = propId;
111     prop->setValue(const_cast<CSSValueImpl *>(parsedValue));
112     prop->m_important = important;
113 
114     propList->append(prop);
115 #ifdef CSS_DEBUG
116     qCDebug(KHTML_LOG) << "added property: " << getPropertyName(propId).string()
117              // non implemented yet << ", value: " << parsedValue->cssText().string()
118              << " important: " << prop->m_important;
119 #endif
120 }
121 
122 // ------------------------------------------------------------------------------
123 
~StyleListImpl()124 StyleListImpl::~StyleListImpl()
125 {
126     StyleBaseImpl *n;
127 
128     if (!m_lstChildren) {
129         return;
130     }
131 
132     QListIterator<StyleBaseImpl *> it(*m_lstChildren);
133     while (it.hasNext()) {
134         n = it.next();
135         n->setParent(nullptr);
136         if (!n->refCount()) {
137             delete n;
138         }
139     }
140     delete m_lstChildren;
141 }
142 
143 // --------------------------------------------------------------------------------
144 
print(void)145 void CSSSelector::print(void)
146 {
147     // qCDebug(KHTML_LOG) << "[Selector: tag = " <<       QString::number(makeId(tagNamespace.id(), tagLocalName.id()),16) << ", attr = \"" << makeId(attrNamespace.id(), attrLocalName.id()) << "\", match = \"" << match
148     //    << "\" value = \"" << value.string().string().toLatin1().constData() << "\" relation = " << (int)relation
149     //    << "]";
150     if (tagHistory) {
151         tagHistory->print();
152     }
153     // qCDebug(KHTML_LOG) << "    specificity = " << specificity();
154 }
155 
specificity() const156 unsigned int CSSSelector::specificity() const
157 {
158 
159     int s = ((tagLocalName.id() == anyLocalName) ? 0 : 1);
160     switch (match) {
161     case Id:
162         s += 0x10000;
163         break;
164     case Exact:
165     case Set:
166     case List:
167     case Class:
168     case Hyphen:
169     case PseudoClass:
170     case PseudoElement:
171     case Contain:
172     case Begin:
173     case End:
174         s += 0x100;
175     case None:
176         break;
177     }
178     if (tagHistory) {
179         s += tagHistory->specificity();
180     }
181     // make sure it doesn't overflow
182     return s & 0xffffff;
183 }
184 
extractPseudoType() const185 void CSSSelector::extractPseudoType() const
186 {
187     if (match != PseudoClass && match != PseudoElement) {
188         return;
189     }
190     _pseudoType = PseudoOther;
191     bool element = false;
192     bool compat = false;
193     if (!value.isEmpty()) {
194         value = value.string().lower();
195         switch (value[0].unicode()) {
196         case '-':
197             if (value == "-khtml-replaced") {
198                 _pseudoType = PseudoReplaced;
199             } else if (value == "-khtml-marker") {
200                 _pseudoType = PseudoMarker;
201             }
202             element = true;
203             break;
204         case 'a':
205             if (value == "active") {
206                 _pseudoType = PseudoActive;
207             } else if (value == "after") {
208                 _pseudoType = PseudoAfter;
209                 element = compat = true;
210             }
211             break;
212         case 'b':
213             if (value == "before") {
214                 _pseudoType = PseudoBefore;
215                 element = compat = true;
216             }
217             break;
218         case 'c':
219             if (value == "checked") {
220                 _pseudoType = PseudoChecked;
221             } else if (value == "contains(") {
222                 _pseudoType = PseudoContains;
223             }
224             break;
225         case 'd':
226             if (value == "disabled") {
227                 _pseudoType = PseudoDisabled;
228             }
229             if (value == "default") {
230                 _pseudoType = PseudoDefault;
231             }
232             break;
233         case 'e':
234             if (value == "empty") {
235                 _pseudoType = PseudoEmpty;
236             } else if (value == "enabled") {
237                 _pseudoType = PseudoEnabled;
238             }
239             break;
240         case 'f':
241             if (value == "first-child") {
242                 _pseudoType = PseudoFirstChild;
243             } else if (value == "first-letter") {
244                 _pseudoType = PseudoFirstLetter;
245                 element = compat = true;
246             } else if (value == "first-line") {
247                 _pseudoType = PseudoFirstLine;
248                 element = compat = true;
249             } else if (value == "first-of-type") {
250                 _pseudoType = PseudoFirstOfType;
251             } else if (value == "focus") {
252                 _pseudoType = PseudoFocus;
253             }
254             break;
255         case 'h':
256             if (value == "hover") {
257                 _pseudoType = PseudoHover;
258             }
259             break;
260         case 'i':
261             if (value == "indeterminate") {
262                 _pseudoType = PseudoIndeterminate;
263             }
264             break;
265         case 'l':
266             if (value == "link") {
267                 _pseudoType = PseudoLink;
268             } else if (value == "lang(") {
269                 _pseudoType = PseudoLang;
270             } else if (value == "last-child") {
271                 _pseudoType = PseudoLastChild;
272             } else if (value == "last-of-type") {
273                 _pseudoType = PseudoLastOfType;
274             }
275             break;
276         case 'n':
277             if (value == "not(") {
278                 _pseudoType = PseudoNot;
279             } else if (value == "nth-child(") {
280                 _pseudoType = PseudoNthChild;
281             } else if (value == "nth-last-child(") {
282                 _pseudoType = PseudoNthLastChild;
283             } else if (value == "nth-of-type(") {
284                 _pseudoType = PseudoNthOfType;
285             } else if (value == "nth-last-of-type(") {
286                 _pseudoType = PseudoNthLastOfType;
287             }
288             break;
289         case 'o':
290             if (value == "only-child") {
291                 _pseudoType = PseudoOnlyChild;
292             } else if (value == "only-of-type") {
293                 _pseudoType = PseudoOnlyOfType;
294             }
295             break;
296         case 'r':
297             if (value == "root") {
298                 _pseudoType = PseudoRoot;
299             } else if (value == "read-only") {
300                 _pseudoType = PseudoReadOnly;
301             } else if (value == "read-write") {
302                 _pseudoType = PseudoReadWrite;
303             }
304             break;
305         case 's':
306             if (value == "selection") {
307                 _pseudoType = PseudoSelection;
308                 element = true;
309             }
310             break;
311         case 't':
312             if (value == "target") {
313                 _pseudoType = PseudoTarget;
314             }
315             break;
316         case 'v':
317             if (value == "visited") {
318                 _pseudoType = PseudoVisited;
319             }
320             break;
321         }
322     }
323     if (match == PseudoClass && element)
324         if (!compat) {
325             _pseudoType = PseudoOther;
326         } else {
327             match = PseudoElement;
328         }
329     else if (match == PseudoElement && !element) {
330         _pseudoType = PseudoOther;
331     }
332 }
333 
operator ==(const CSSSelector & other) const334 bool CSSSelector::operator == (const CSSSelector &other) const
335 {
336     const CSSSelector *sel1 = this;
337     const CSSSelector *sel2 = &other;
338 
339     while (sel1 && sel2) {
340         //assert(sel1->_pseudoType != PseudoNotParsed);
341         //assert(sel2->_pseudoType != PseudoNotParsed);
342         if (sel1->tagLocalName.id() != sel2->tagLocalName.id() || sel1->attrLocalName.id() != sel2->attrLocalName.id() ||
343                 sel1->tagNamespace.id() != sel2->tagNamespace.id() || sel1->attrNamespace.id() != sel2->attrNamespace.id() ||
344                 sel1->relation != sel2->relation || sel1->match != sel2->match ||
345                 sel1->value != sel2->value ||
346                 sel1->pseudoType() != sel2->pseudoType() ||
347                 sel1->string_arg != sel2->string_arg) {
348             return false;
349         }
350         sel1 = sel1->tagHistory;
351         sel2 = sel2->tagHistory;
352     }
353     if (sel1 || sel2) {
354         return false;
355     }
356     return true;
357 }
358 
selectorText() const359 DOMString CSSSelector::selectorText() const
360 {
361     // FIXME: Support namespaces when dumping the selector text.  This requires preserving
362     // the original namespace prefix used. Ugh. -dwh
363     DOMString str;
364     const CSSSelector *cs = this;
365     quint16 tag = cs->tagLocalName.id();
366     if (tag == anyLocalName && cs->match == CSSSelector::None) {
367         str = "*";
368     } else if (tag != anyLocalName) {
369         str = LocalName::fromId(tag).toString();
370     }
371 
372     const CSSSelector *op = nullptr;
373     while (true) {
374         if (makeId(cs->attrNamespace.id(), cs->attrLocalName.id()) == ATTR_ID && cs->match == CSSSelector::Id) {
375             str += "#";
376             str += cs->value;
377         } else if (cs->match == CSSSelector::Class) {
378             str += ".";
379             str += cs->value;
380         } else if (cs->match == CSSSelector::PseudoClass) {
381             str += ":";
382             str += cs->value;
383             if (!cs->string_arg.isEmpty()) { // e.g :nth-child(...)
384                 str += cs->string_arg;
385                 str += ")";
386             } else if (cs->simpleSelector && !op) { // :not(...)
387                 op = cs;
388                 cs = cs->simpleSelector;
389                 continue;
390             }
391         } else if (cs->match == CSSSelector::PseudoElement) {
392             str += "::";
393             str += cs->value;
394         }
395         // optional attribute
396         else if (cs->attrLocalName.id()) {
397             DOMString attrName = LocalName::fromId(cs->attrLocalName.id()).toString();
398             str += "[";
399             str += attrName;
400             switch (cs->match) {
401             case CSSSelector::Exact:
402                 str += "=";
403                 break;
404             case CSSSelector::Set:
405                 break;
406             case CSSSelector::List:
407                 str += "~=";
408                 break;
409             case CSSSelector::Hyphen:
410                 str += "|=";
411                 break;
412             case CSSSelector::Begin:
413                 str += "^=";
414                 break;
415             case CSSSelector::End:
416                 str += "$=";
417                 break;
418             case CSSSelector::Contain:
419                 str += "*=";
420                 break;
421             default:
422                 qCWarning(KHTML_LOG) << "Unhandled case in CSSStyleRuleImpl::selectorText : match=" << cs->match;
423             }
424             if (cs->match != CSSSelector::Set) {
425                 str += "\"";
426                 str += cs->value;
427                 str += "\"";
428             }
429             str += "]";
430         }
431         if (op && !cs->tagHistory) {
432             cs = op;
433             op = nullptr;
434             str += ")";
435         }
436 
437         if ((cs->relation != CSSSelector::SubSelector && !op) || !cs->tagHistory) {
438             break;
439         }
440         cs = cs->tagHistory;
441     }
442 
443     if (cs->tagHistory) {
444         DOMString tagHistoryText = cs->tagHistory->selectorText();
445         if (cs->relation == DirectAdjacent) {
446             str = tagHistoryText + DOMString(" + ") + str;
447         } else if (cs->relation == IndirectAdjacent) {
448             str = tagHistoryText + DOMString(" ~ ") + str;
449         } else if (cs->relation == Child) {
450             str = tagHistoryText + DOMString(" > ") + str;
451         } else { // Descendant
452             str = tagHistoryText + DOMString(" ") + str;
453         }
454     }
455     return str;
456 }
457 
458 // ----------------------------------------------------------------------------
459