fix logic
[personal-kdelibs.git] / khtml / css / css_stylesheetimpl.cpp
blob95af545dce671492b095ef1cdefe3235391624fd
1 /**
2 * This file is part of the DOM implementation for KDE.
4 * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org)
5 * (C) 2004 Apple Computer, Inc.
6 * (C) 2008 Germain Garand <germain@ebooksfrance.org>
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Library General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Library General Public License for more details.
18 * You should have received a copy of the GNU Library General Public License
19 * along with this library; see the file COPYING.LIB. If not, write to
20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
25 //#define CSS_STYLESHEET_DEBUG
27 #include "css_stylesheetimpl.h"
28 #include "css_ruleimpl.h"
29 #include "css_valueimpl.h"
30 #include "cssparser.h"
31 #include "css_mediaquery.h"
33 #include <dom/dom_string.h>
34 #include <dom/dom_exception.h>
35 #include <dom/css_stylesheet.h>
36 #include <dom/css_rule.h>
37 #include <dom/dom_exception.h>
39 #include <xml/dom_nodeimpl.h>
40 #include <html/html_documentimpl.h>
41 #include <misc/loader.h>
43 #include <kdebug.h>
45 using namespace DOM;
46 using namespace khtml;
47 // --------------------------------------------------------------------------------
49 StyleSheetImpl::StyleSheetImpl(StyleSheetImpl *parentSheet, DOMString href)
50 : StyleListImpl(parentSheet)
52 m_disabled = false;
53 m_media = 0;
54 m_parentNode = 0;
55 m_strHref = href;
59 StyleSheetImpl::StyleSheetImpl(DOM::NodeImpl *parentNode, DOMString href)
60 : StyleListImpl()
62 m_parentNode = parentNode;
63 m_disabled = false;
64 m_media = 0;
65 m_strHref = href;
68 StyleSheetImpl::StyleSheetImpl(StyleBaseImpl *owner, DOMString href)
69 : StyleListImpl(owner)
71 m_disabled = false;
72 m_media = 0;
73 m_parentNode = 0;
74 m_strHref = href;
77 StyleSheetImpl::~StyleSheetImpl()
79 if(m_media) {
80 m_media->setParent( 0 );
81 m_media->deref();
85 StyleSheetImpl *StyleSheetImpl::parentStyleSheet() const
87 if( !m_parent ) return 0;
88 if( m_parent->isStyleSheet() ) return static_cast<StyleSheetImpl *>(m_parent);
89 if( m_parent->isRule() ) return m_parent->stylesheet();
90 return 0;
93 void StyleSheetImpl::setMedia( MediaListImpl *media )
95 if( media )
96 media->ref();
97 if( m_media ) {
98 m_media->setParent( 0 );
99 m_media->deref();
101 m_media = media;
102 if (m_media)
103 m_media->setParent( this );
106 void StyleSheetImpl::setDisabled( bool disabled )
108 bool updateStyle = isCSSStyleSheet() && m_parentNode && disabled != m_disabled;
109 m_disabled = disabled;
110 if (updateStyle)
111 m_parentNode->document()->updateStyleSelector();
114 // -----------------------------------------------------------------------
117 CSSStyleSheetImpl::CSSStyleSheetImpl(CSSStyleSheetImpl *parentSheet, DOMString href)
118 : StyleSheetImpl(parentSheet, href)
120 m_lstChildren = new QList<StyleBaseImpl*>;
121 m_doc = parentSheet ? parentSheet->doc() : 0;
122 m_implicit = false;
123 m_namespaces = 0;
124 m_defaultNamespace = NamespaceName::fromId(anyNamespace);
125 m_loadedHint = false;
128 CSSStyleSheetImpl::CSSStyleSheetImpl(DOM::NodeImpl *parentNode, DOMString href, bool _implicit)
129 : StyleSheetImpl(parentNode, href)
131 m_lstChildren = new QList<StyleBaseImpl*>;
132 m_doc = parentNode->document();
133 m_implicit = _implicit;
134 m_namespaces = 0;
135 m_defaultNamespace = NamespaceName::fromId(anyNamespace);
136 m_loadedHint = false;
139 CSSStyleSheetImpl::CSSStyleSheetImpl(CSSRuleImpl *ownerRule, DOMString href)
140 : StyleSheetImpl(ownerRule, href)
142 m_lstChildren = new QList<StyleBaseImpl*>;
143 m_doc = static_cast<CSSStyleSheetImpl*>(ownerRule->stylesheet())->doc();
144 m_implicit = false;
145 m_namespaces = 0;
146 m_defaultNamespace = NamespaceName::fromId(anyNamespace);
147 m_loadedHint = false;
150 CSSStyleSheetImpl::CSSStyleSheetImpl(DOM::NodeImpl *parentNode, CSSStyleSheetImpl *orig)
151 : StyleSheetImpl(parentNode, orig->m_strHref)
153 m_lstChildren = new QList<StyleBaseImpl*>;
154 StyleBaseImpl *rule;
155 QListIterator<StyleBaseImpl*> it( *orig->m_lstChildren );
156 while ( it.hasNext() )
158 rule = it.next();
159 m_lstChildren->append(rule);
160 rule->setParent(this);
162 m_doc = parentNode->document();
163 m_implicit = false;
164 m_namespaces = 0;
165 m_defaultNamespace = NamespaceName::fromId(anyNamespace);
166 m_loadedHint = false;
169 CSSStyleSheetImpl::CSSStyleSheetImpl(CSSRuleImpl *ownerRule, CSSStyleSheetImpl *orig)
170 : StyleSheetImpl(ownerRule, orig->m_strHref)
172 // m_lstChildren is deleted in StyleListImpl
173 m_lstChildren = new QList<StyleBaseImpl*>;
174 StyleBaseImpl *rule;
175 QListIterator<StyleBaseImpl*> it( *orig->m_lstChildren );
176 while ( it.hasNext() )
178 rule = it.next();
179 m_lstChildren->append(rule);
180 rule->setParent(this);
182 m_doc = static_cast<CSSStyleSheetImpl*>(ownerRule->stylesheet())->doc();
183 m_implicit = false;
184 m_namespaces = 0;
185 m_defaultNamespace = NamespaceName::fromId(anyNamespace);
186 m_loadedHint = false;
189 CSSRuleImpl *CSSStyleSheetImpl::ownerRule() const
191 if( !m_parent ) return 0;
192 if( m_parent->isRule() ) return static_cast<CSSRuleImpl *>(m_parent);
193 return 0;
196 unsigned long CSSStyleSheetImpl::insertRule( const DOMString &rule, unsigned long index, int &exceptioncode )
198 exceptioncode = 0;
199 if (index > (unsigned) m_lstChildren->count()) {
200 exceptioncode = DOMException::INDEX_SIZE_ERR;
201 return 0;
203 CSSParser p( strictParsing );
204 CSSRuleImpl *r = p.parseRule( this, rule );
206 if(!r) {
207 exceptioncode = CSSException::SYNTAX_ERR + CSSException::_EXCEPTION_OFFSET;
208 return 0;
210 // ###
211 // HIERARCHY_REQUEST_ERR: Raised if the rule cannot be inserted at the specified index e.g. if an
212 //@import rule is inserted after a standard rule set or other at-rule.
213 m_lstChildren->insert(index, r);
214 if (m_doc)
215 m_doc->updateStyleSelector(true /*shallow*/);
216 return index;
219 CSSRuleListImpl *CSSStyleSheetImpl::cssRules(bool omitCharsetRules)
221 return new CSSRuleListImpl(this, omitCharsetRules);
224 void CSSStyleSheetImpl::deleteRule( unsigned long index, int &exceptioncode )
226 exceptioncode = 0;
227 if (index+1 > (unsigned) m_lstChildren->count()) {
228 exceptioncode = DOMException::INDEX_SIZE_ERR;
229 return;
231 StyleBaseImpl *b = m_lstChildren->takeAt(index);
233 // TreeShared requires delete not deref when removed from tree
234 b->setParent(0);
235 if( !b->refCount() ) delete b;
236 if (m_doc)
237 m_doc->updateStyleSelector(true /*shallow*/);
240 void CSSStyleSheetImpl::addNamespace(CSSParser* /*p*/, const DOM::DOMString& prefix, const DOM::DOMString& uri)
242 if (uri.isEmpty())
243 return;
245 m_namespaces = new CSSNamespace(prefix, uri, m_namespaces);
247 if (prefix.isEmpty()) {
248 Q_ASSERT(m_doc != 0);
250 m_defaultNamespace = NamespaceName::fromString(uri);
254 void CSSStyleSheetImpl::determineNamespace(NamespaceName& namespacename, const DOM::DOMString& prefix)
256 // If the stylesheet has no namespaces we can just return. There won't be any need to ever check
257 // namespace values in selectors.
258 //if (!m_namespaces)
259 // return;
261 if (prefix.isEmpty())
262 namespacename = NamespaceName::fromId(emptyNamespace); // No namespace. If an element/attribute has a namespace, we won't match it.
263 else if (prefix == "*")
264 namespacename = NamespaceName::fromId(anyNamespace); // We'll match any namespace.
265 else {
266 if (!m_namespaces)
267 return;
268 CSSNamespace* ns = m_namespaces->namespaceForPrefix(prefix);
269 if (ns) {
270 Q_ASSERT(m_doc != 0);
272 // Look up the id for this namespace URI.
273 namespacename = NamespaceName::fromString(ns->uri());
278 bool CSSStyleSheetImpl::parseString(const DOMString &string, bool strict)
280 #ifdef CSS_STYLESHEET_DEBUG
281 kDebug( 6080 ) << "parsing sheet, len=" << string.length() << ", sheet is " << string.string();
282 #endif
284 strictParsing = strict;
285 CSSParser p( strict );
286 p.parseSheet( this, string );
287 return true;
290 bool CSSStyleSheetImpl::isLoading() const
292 StyleBaseImpl *rule;
293 QListIterator<StyleBaseImpl*> it( *m_lstChildren );
294 while ( it.hasNext() )
296 rule = it.next();
297 if(rule->isImportRule())
299 CSSImportRuleImpl *import = static_cast<CSSImportRuleImpl *>(rule);
300 #ifdef CSS_STYLESHEET_DEBUG
301 kDebug( 6080 ) << "found import";
302 #endif
303 if(import->isLoading())
305 #ifdef CSS_STYLESHEET_DEBUG
306 kDebug( 6080 ) << "--> not loaded";
307 #endif
308 m_loadedHint = false;
309 return true;
313 m_loadedHint = true;
314 return false;
317 void CSSStyleSheetImpl::checkLoaded() const
319 if (isLoading())
320 return;
321 if (m_parent)
322 m_parent->checkLoaded();
323 if (m_parentNode)
324 m_loadedHint = m_parentNode->checkRemovePendingSheet();
325 else if (parentStyleSheet() && parentStyleSheet()->isCSSStyleSheet())
326 m_loadedHint = static_cast<CSSStyleSheetImpl*>(parentStyleSheet())->loadedHint();
327 else
328 m_loadedHint = true;
331 void CSSStyleSheetImpl::checkPending() const
333 if (!m_loadedHint)
334 return;
335 if (m_parent)
336 m_parent->checkPending();
337 else if (m_parentNode)
338 m_parentNode->checkAddPendingSheet();
341 // ---------------------------------------------------------------------------
344 StyleSheetListImpl::~StyleSheetListImpl()
346 foreach (StyleSheetImpl* sh, styleSheets)
347 sh->deref();
350 void StyleSheetListImpl::add( StyleSheetImpl* s )
352 if ( !styleSheets.contains( s ) ) {
353 s->ref();
354 styleSheets.append( s );
358 void StyleSheetListImpl::remove( StyleSheetImpl* s )
360 if ( styleSheets.removeAll( s ) )
361 s->deref();
364 unsigned long StyleSheetListImpl::length() const
366 // hack so implicit BODY stylesheets don't get counted here
367 unsigned long l = 0;
368 foreach (StyleSheetImpl* sh, styleSheets) {
369 if (!sh->isCSSStyleSheet() || !static_cast<CSSStyleSheetImpl*>(sh)->implicit())
370 ++l;
372 return l;
375 StyleSheetImpl *StyleSheetListImpl::item ( unsigned long index )
377 unsigned long l = 0;
378 foreach (StyleSheetImpl* sh, styleSheets) {
379 if (!sh->isCSSStyleSheet() || !static_cast<CSSStyleSheetImpl*>(sh)->implicit()) {
380 if (l == index)
381 return sh;
382 ++l;
385 return 0;
388 // --------------------------------------------------------------------------------------------
390 /* MediaList is used to store 3 types of media related entities which mean the same:
391 * Media Queries, Media Types and Media Descriptors.
392 * Currently MediaList always tries to parse media queries and if parsing fails,
393 * tries to fallback to Media Descriptors if m_fallback flag is set.
394 * Slight problem with syntax error handling:
395 * CSS 2.1 Spec (http://www.w3.org/TR/CSS21/media.html)
396 * specifies that failing media type parsing is a syntax error
397 * CSS 3 Media Queries Spec (http://www.w3.org/TR/css3-mediaqueries/)
398 * specifies that failing media query is a syntax error
399 * HTML 4.01 spec (http://www.w3.org/TR/REC-html40/present/styles.html#adef-media)
400 * specifies that Media Descriptors should be parsed with forward-compatible syntax
401 * DOM Level 2 Style Sheet spec (http://www.w3.org/TR/DOM-Level-2-Style/)
402 * talks about MediaList.mediaText and refers
403 * - to Media Descriptors of HTML 4.0 in context of StyleSheet
404 * - to Media Types of CSS 2.0 in context of CSSMediaRule and CSSImportRule
406 * These facts create situation where same (illegal) media specification may result in
407 * different parses depending on whether it is media attr of style element or part of
408 * css @media rule.
409 * <style media="screen and resolution > 40dpi"> ..</style> will be enabled on screen devices where as
410 * @media screen and resolution > 40dpi {..} will not.
411 * This gets more counter-intuitive in JavaScript:
412 * document.styleSheets[0].media.mediaText = "screen and resolution > 40dpi" will be ok and
413 * enabled, while
414 * document.styleSheets[0].cssRules[0].media.mediaText = "screen and resolution > 40dpi" will
415 * throw SYNTAX_ERR exception.
418 MediaListImpl::MediaListImpl( CSSStyleSheetImpl *parentSheet,
419 const DOMString &media, bool fallbackToDescriptor)
420 : StyleBaseImpl( parentSheet )
421 , m_fallback(fallbackToDescriptor)
423 int ec = 0;
424 setMediaText(media, ec);
425 // FIXME: parsing can fail. The problem with failing constructor is that
426 // we would need additional flag saying MediaList is not valid
427 // Parse can fail only when fallbackToDescriptor == false, i.e when HTML4 media descriptor
428 // forward-compatible syntax is not in use.
429 // DOMImplementationCSS seems to mandate that media descriptors are used
430 // for both html and svg, even though svg:style doesn't use media descriptors
431 // Currently the only places where parsing can fail are
432 // creating <svg:style>, creating css media / import rules from js
433 if (ec)
434 setMediaText("invalid", ec);
437 MediaListImpl::MediaListImpl( CSSRuleImpl *parentRule, const DOMString &media, bool fallbackToDescriptor)
438 : StyleBaseImpl(parentRule)
439 , m_fallback(fallbackToDescriptor)
441 int ec = 0;
442 setMediaText(media, ec);
443 // FIXME: parsing can fail. The problem with failing constructor is that
444 // we would need additional flag saying MediaList is not valid
445 // Parse can fail only when fallbackToDescriptor == false, i.e when HTML4 media descriptor
446 // forward-compatible syntax is not in use.
447 // DOMImplementationCSS seems to mandate that media descriptors are used
448 // for both html and svg, even though svg:style doesn't use media descriptors
449 // Currently the only places where parsing can fail are
450 // creating <svg:style>, creating css media / import rules from js
451 if (ec)
452 setMediaText("invalid", ec);
455 MediaListImpl::~MediaListImpl()
457 qDeleteAll(m_queries);
460 CSSStyleSheetImpl *MediaListImpl::parentStyleSheet() const
462 if( m_parent->isCSSStyleSheet() ) return static_cast<CSSStyleSheetImpl *>(m_parent);
463 return 0;
466 CSSRuleImpl *MediaListImpl::parentRule() const
468 if( m_parent->isRule() ) return static_cast<CSSRuleImpl *>(m_parent);
469 return 0;
472 static DOMString parseMediaDescriptor(const DOMString& s)
474 int len = s.length();
476 // http://www.w3.org/TR/REC-html40/types.html#type-media-descriptors
477 // "Each entry is truncated just before the first character that isn't a
478 // US ASCII letter [a-zA-Z] (ISO 10646 hex 41-5a, 61-7a), digit [0-9] (hex 30-39),
479 // or hyphen (hex 2d)."
480 int i;
481 unsigned short c;
482 for (i = 0; i < len; ++i) {
483 c = s[i].unicode();
484 if (! ((c >= 'a' && c <= 'z')
485 || (c >= 'A' && c <= 'Z')
486 || (c >= '1' && c <= '9')
487 || (c == '-')))
488 break;
490 return s.implementation()->substring(0, len);
493 void MediaListImpl::deleteMedium(const DOMString& oldMedium, int& ec)
495 MediaListImpl tempMediaList;
496 CSSParser p(true);
498 MediaQuery* oldQuery = 0;
499 bool deleteOldQuery = false;
501 if (p.parseMediaQuery(&tempMediaList, oldMedium)) {
502 if (tempMediaList.m_queries.size() > 0)
503 oldQuery = tempMediaList.m_queries[0];
504 } else if (m_fallback) {
505 DOMString medium = parseMediaDescriptor(oldMedium);
506 if (!medium.isNull()) {
507 oldQuery = new MediaQuery(MediaQuery::None, medium, 0);
508 deleteOldQuery = true;
512 // DOM Style Sheets spec doesn't allow SYNTAX_ERR to be thrown in deleteMedium
513 ec = DOMException::NOT_FOUND_ERR;
515 if (oldQuery) {
516 for(int i = 0; i < m_queries.size(); ++i) {
517 MediaQuery* a = m_queries[i];
518 if (*a == *oldQuery) {
519 m_queries.removeAt(i);
520 delete a;
521 ec = 0;
522 break;
525 if (deleteOldQuery)
526 delete oldQuery;
530 DOM::DOMString MediaListImpl::mediaText() const
532 DOMString text;
533 bool first = true;
534 const QList<MediaQuery*>::ConstIterator itEnd = m_queries.end();
536 for ( QList<MediaQuery*>::ConstIterator it = m_queries.begin(); it != itEnd; ++it ) {
537 if (!first)
538 text += ", ";
539 text += (*it)->cssText();
540 first = false;
542 return text;
545 void MediaListImpl::setMediaText(const DOM::DOMString &value, int& ec)
547 MediaListImpl tempMediaList;
548 CSSParser p(true);
550 const QString val = value.string();
551 const QStringList list = val.split( ',' );
553 const QStringList::ConstIterator itEnd = list.end();
555 for ( QStringList::ConstIterator it = list.begin(); it != itEnd; ++it )
557 const DOMString medium = (*it).trimmed();
558 if( !medium.isEmpty() ) {
559 if (!p.parseMediaQuery(&tempMediaList, medium)) {
560 if (m_fallback) {
561 DOMString mediaDescriptor = parseMediaDescriptor(medium);
562 if (!mediaDescriptor.isNull())
563 tempMediaList.m_queries.append(new MediaQuery(MediaQuery::None, mediaDescriptor, 0));
564 } else {
565 ec = CSSException::SYNTAX_ERR;
566 return;
569 } else if (!m_fallback) {
570 ec = CSSException::SYNTAX_ERR;
571 return;
574 // ",,,," falls straight through, but is not valid unless fallback
575 if (!m_fallback && list.begin() == list.end()) {
576 DOMString s = value.string().trimmed();
577 if (!s.isEmpty()) {
578 ec = CSSException::SYNTAX_ERR;
579 return;
583 ec = 0;
584 qDeleteAll(m_queries);
585 m_queries = tempMediaList.m_queries;
586 tempMediaList.m_queries.clear();
590 DOMString MediaListImpl::item(unsigned long index) const
592 if (index < (unsigned)m_queries.size()) {
593 MediaQuery* query = m_queries[index];
594 return query->cssText();
597 return DOMString();
600 void MediaListImpl::appendMedium(const DOMString& newMedium, int& ec)
602 ec = DOMException::INVALID_CHARACTER_ERR;
603 CSSParser p(true);
604 if (p.parseMediaQuery(this, newMedium)) {
605 ec = 0;
606 } else if (m_fallback) {
607 DOMString medium = parseMediaDescriptor(newMedium);
608 if (!medium.isNull()) {
609 m_queries.append(new MediaQuery(MediaQuery::None, medium, 0));
610 ec = 0;
615 void MediaListImpl::appendMediaQuery(MediaQuery* mediaQuery)
617 m_queries.append(mediaQuery);