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