0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041: package org.netbeans.modules.visualweb.designer.cssengine;
0042:
0043: import org.netbeans.modules.visualweb.api.designer.cssengine.StyleRefreshable;
0044: import org.netbeans.modules.visualweb.api.designer.cssengine.XhtmlCss;
0045: import org.netbeans.modules.visualweb.spi.designer.cssengine.CssUserAgentInfo;
0046: import java.io.*;
0047: import java.net.URL;
0048: import java.util.ArrayList;
0049: import java.util.HashMap;
0050: import java.util.Iterator;
0051: import java.util.LinkedHashMap;
0052: import java.util.Map;
0053:
0054: import org.apache.batik.css.engine.CSSContext;
0055: import org.apache.batik.css.engine.CSSEngine;
0056: import org.apache.batik.css.engine.CSSEngineUserAgent;
0057: import org.apache.batik.css.engine.CSSStylableElement;
0058: import org.apache.batik.css.engine.StyleDeclaration;
0059: import org.apache.batik.css.engine.StyleMap;
0060: import org.apache.batik.css.engine.StyleSheet;
0061: import org.apache.batik.css.engine.value.*;
0062: import org.apache.batik.css.engine.value.ShorthandManager;
0063: import org.apache.batik.css.engine.value.ValueManager;
0064: import org.apache.batik.css.engine.value.css2.ClipManager;
0065: import org.apache.batik.css.engine.value.css2.DirectionManager;
0066: import org.apache.batik.css.engine.value.css2.DisplayManager;
0067: import org.apache.batik.css.engine.value.css2.FontFamilyManager;
0068: import org.apache.batik.css.engine.value.css2.FontSizeAdjustManager;
0069: import org.apache.batik.css.engine.value.css2.FontSizeManager;
0070: import org.apache.batik.css.engine.value.css2.FontStretchManager;
0071: import org.apache.batik.css.engine.value.css2.FontStyleManager;
0072: import org.apache.batik.css.engine.value.css2.FontVariantManager;
0073: import org.apache.batik.css.engine.value.css2.FontWeightManager;
0074: import org.apache.batik.css.engine.value.css2.OverflowManager;
0075: import org.apache.batik.css.engine.value.css2.TextDecorationManager;
0076: import org.apache.batik.css.engine.value.css2.UnicodeBidiManager;
0077: import org.apache.batik.css.engine.value.css2.VisibilityManager;
0078: import org.apache.batik.css.engine.value.svg.ColorManager;
0079: import org.apache.batik.css.parser.ExtendedParser;
0080: import org.apache.batik.css.parser.ExtendedParserWrapper;
0081: import org.apache.batik.css.parser.Parser;
0082: import org.openide.ErrorManager;
0083: import org.openide.util.NbBundle;
0084: import org.w3c.css.sac.CSSParseException;
0085: import org.w3c.css.sac.ErrorHandler;
0086: import org.w3c.css.sac.InputSource;
0087: import org.w3c.css.sac.SACMediaList;
0088: import org.w3c.dom.Attr;
0089: import org.w3c.dom.DOMException;
0090: import org.w3c.dom.Document;
0091: import org.w3c.dom.DocumentFragment;
0092: import org.w3c.dom.Element;
0093: import org.w3c.dom.Node;
0094:
0095: import org.netbeans.modules.visualweb.designer.html.HtmlAttribute;
0096: import org.netbeans.modules.visualweb.designer.html.HtmlTag;
0097: import org.w3c.dom.css.CSSPrimitiveValue;
0098:
0099: /**
0100: * This class plugs in CSS support for XHTML
0101: *
0102: * @author Tor Norbye
0103: * @author Carl Quinn
0104: */
0105: class XhtmlCssEngine extends CSSEngine {
0106: private static StyleSheet defaultRules = null;
0107:
0108: /**
0109: * The value managers for XHTML - KEEP IN SYNC WITH PROPERTY INDICES IN XhtmlCss.java
0110: */
0111: public static final ValueManager[] XHTML_VALUE_MANAGERS = {
0112:
0113: // Try to keep the order alphabetical, because in
0114: // StyleMap.toStyleString I put the user's properties back out
0115: // in the below order (e.g. when manipulating a style string
0116: // indirectly such as by dragging a component, the styles
0117: // get reordered to the below order.)
0118: new BackgroundColorManager(),
0119: new BackgroundImageManager(),
0120:
0121: // If you add this:
0122: // new BackgroundAttachmentManager(),
0123: // then update the BackgroundShorthandManager too
0124: new BackgroundPositionManager(),
0125: new BackgroundRepeatManager(),
0126: new BorderCollapseManager(),
0127: new BorderColorManager(
0128: CssConstants.CSS_BORDER_LEFT_COLOR_PROPERTY),
0129: new BorderColorManager(
0130: CssConstants.CSS_BORDER_RIGHT_COLOR_PROPERTY),
0131: new BorderColorManager(
0132: CssConstants.CSS_BORDER_TOP_COLOR_PROPERTY),
0133: new BorderColorManager(
0134: CssConstants.CSS_BORDER_BOTTOM_COLOR_PROPERTY),
0135: new BorderStyleManager(
0136: CssConstants.CSS_BORDER_LEFT_STYLE_PROPERTY),
0137: new BorderStyleManager(
0138: CssConstants.CSS_BORDER_RIGHT_STYLE_PROPERTY),
0139: new BorderStyleManager(
0140: CssConstants.CSS_BORDER_TOP_STYLE_PROPERTY),
0141: new BorderStyleManager(
0142: CssConstants.CSS_BORDER_BOTTOM_STYLE_PROPERTY),
0143: new BorderWidthManager(
0144: CssConstants.CSS_BORDER_LEFT_WIDTH_PROPERTY),
0145: new BorderWidthManager(
0146: CssConstants.CSS_BORDER_RIGHT_WIDTH_PROPERTY),
0147: new BorderWidthManager(
0148: CssConstants.CSS_BORDER_TOP_WIDTH_PROPERTY),
0149: new BorderWidthManager(
0150: CssConstants.CSS_BORDER_BOTTOM_WIDTH_PROPERTY),
0151: new CaptionSideManager(),
0152: new ClearManager(),
0153: new ClipManager(),
0154: new ColorManager(),
0155: new DirectionManager(),
0156: new DisplayManager(),
0157: new FloatManager(),
0158: new FontFamilyManager(),
0159: new FontSizeManager(),
0160: new FontSizeAdjustManager(),
0161: new FontStretchManager(),
0162: new FontStyleManager(),
0163: new FontVariantManager(),
0164: new FontWeightManager(),
0165: new HeightManager(),
0166: new LineHeightManager(),
0167: new ListStyleImageManager(),
0168: new ListStyleTypeManager(),
0169:
0170: // ListStylePositionManager: if you insert this, adjust
0171: // ListStyleShorthandManager to reference it too
0172: new MarginManager(CssConstants.CSS_MARGIN_LEFT_PROPERTY),
0173: new MarginManager(CssConstants.CSS_MARGIN_RIGHT_PROPERTY),
0174: new MarginManager(CssConstants.CSS_MARGIN_TOP_PROPERTY),
0175: new MarginManager(CssConstants.CSS_MARGIN_BOTTOM_PROPERTY),
0176: new OffsetManager(CssConstants.CSS_LEFT_PROPERTY,
0177: OffsetManager.HORIZONTAL_ORIENTATION),
0178: new OffsetManager(CssConstants.CSS_RIGHT_PROPERTY,
0179: OffsetManager.HORIZONTAL_ORIENTATION),
0180: new OffsetManager(CssConstants.CSS_TOP_PROPERTY,
0181: OffsetManager.VERTICAL_ORIENTATION),
0182: new OffsetManager(CssConstants.CSS_BOTTOM_PROPERTY,
0183: OffsetManager.VERTICAL_ORIENTATION),
0184: new OverflowManager(),
0185: new PaddingManager(CssConstants.CSS_PADDING_LEFT_PROPERTY),
0186: new PaddingManager(CssConstants.CSS_PADDING_RIGHT_PROPERTY),
0187: new PaddingManager(CssConstants.CSS_PADDING_TOP_PROPERTY),
0188: new PaddingManager(CssConstants.CSS_PADDING_BOTTOM_PROPERTY),
0189: new PositionManager(), new TableLayoutManager(),
0190: new TextAlignManager(), new TextDecorationManager(),
0191: new TextIndentManager(), new TextTransformManager(),
0192: new UnicodeBidiManager(), new VerticalAlignmentManager(),
0193: new VisibilityManager(), new WhitespaceManager(),
0194: new WidthManager(), new ZIndexManager(),
0195: new RaveLayoutManager(), new RaveLinkColorManager() };
0196:
0197: /**
0198: * The shorthand managers for XHTML.
0199: */
0200: public static final ShorthandManager[] XHTML_SHORTHAND_MANAGERS = {
0201: new BackgroundShorthandManager(),
0202: new BorderShorthandManager(),
0203: new BorderColorShorthandManager(),
0204: new BorderStyleShorthandManager(),
0205: new BorderWidthShorthandManager(),
0206: new BorderSideShorthandManager(
0207: CssConstants.CSS_BORDER_LEFT_PROPERTY,
0208: CssConstants.CSS_BORDER_LEFT_WIDTH_PROPERTY,
0209: CssConstants.CSS_BORDER_LEFT_STYLE_PROPERTY,
0210: CssConstants.CSS_BORDER_LEFT_COLOR_PROPERTY),
0211: new BorderSideShorthandManager(
0212: CssConstants.CSS_BORDER_RIGHT_PROPERTY,
0213: CssConstants.CSS_BORDER_RIGHT_WIDTH_PROPERTY,
0214: CssConstants.CSS_BORDER_RIGHT_STYLE_PROPERTY,
0215: CssConstants.CSS_BORDER_RIGHT_COLOR_PROPERTY),
0216: new BorderSideShorthandManager(
0217: CssConstants.CSS_BORDER_TOP_PROPERTY,
0218: CssConstants.CSS_BORDER_TOP_WIDTH_PROPERTY,
0219: CssConstants.CSS_BORDER_TOP_STYLE_PROPERTY,
0220: CssConstants.CSS_BORDER_TOP_COLOR_PROPERTY),
0221: new BorderSideShorthandManager(
0222: CssConstants.CSS_BORDER_BOTTOM_PROPERTY,
0223: CssConstants.CSS_BORDER_BOTTOM_WIDTH_PROPERTY,
0224: CssConstants.CSS_BORDER_BOTTOM_STYLE_PROPERTY,
0225: CssConstants.CSS_BORDER_BOTTOM_COLOR_PROPERTY),
0226: new FontShorthandManager(),
0227: new ListStyleShorthandManager(),
0228: new MarginShorthandManager(), new PaddingShorthandManager() };
0229:
0230: /** Shared instance of an index map into the value manager names */
0231: static org.apache.batik.css.engine.StringIntMap valueManagerIndex;
0232:
0233: /** Shared instance of an index map into the shorthand manager names */
0234: static org.apache.batik.css.engine.StringIntMap shorthandManagerIndex;
0235:
0236: static {
0237: int len = XHTML_VALUE_MANAGERS.length;
0238: valueManagerIndex = new org.apache.batik.css.engine.StringIntMap(
0239: len);
0240:
0241: for (int i = len - 1; i >= 0; --i) {
0242: String pn = XHTML_VALUE_MANAGERS[i].getPropertyName();
0243: valueManagerIndex.put(pn, i);
0244: }
0245:
0246: len = XHTML_SHORTHAND_MANAGERS.length;
0247: shorthandManagerIndex = new org.apache.batik.css.engine.StringIntMap(
0248: len);
0249:
0250: for (int i = len - 1; i >= 0; --i) {
0251: String pn = XHTML_SHORTHAND_MANAGERS[i].getPropertyName();
0252: shorthandManagerIndex.put(pn, i);
0253: }
0254: }
0255:
0256: // Map for quick lookup of attribute aliases
0257: protected static final StringIntMap attributes = new StringIntMap(
0258: 20);
0259:
0260: static {
0261: attributes.put(HtmlAttribute.LINK, HtmlAttribute.LINK_ID);
0262: attributes.put(HtmlAttribute.ALIGN, HtmlAttribute.ALIGN_ID);
0263: attributes.put(HtmlAttribute.VALIGN, HtmlAttribute.VALIGN_ID);
0264: attributes.put(HtmlAttribute.BGCOLOR, HtmlAttribute.BGCOLOR_ID);
0265: attributes.put(HtmlAttribute.CLEAR, HtmlAttribute.CLEAR_ID);
0266: attributes.put(HtmlAttribute.BACKGROUND,
0267: HtmlAttribute.BACKGROUND_ID);
0268: attributes.put(HtmlAttribute.TEXT, HtmlAttribute.TEXT_ID);
0269: attributes.put(HtmlAttribute.WIDTH, HtmlAttribute.WIDTH_ID);
0270: attributes.put(HtmlAttribute.HEIGHT, HtmlAttribute.HEIGHT_ID);
0271: attributes.put(HtmlAttribute.NOWRAP, HtmlAttribute.NOWRAP_ID);
0272: attributes.put(HtmlAttribute.BORDER, HtmlAttribute.BORDER_ID);
0273: attributes.put(HtmlAttribute.COLOR, HtmlAttribute.COLOR_ID);
0274: attributes.put(HtmlAttribute.SIZE, HtmlAttribute.SIZE_ID);
0275: attributes.put(HtmlAttribute.FACE, HtmlAttribute.FACE_ID);
0276: attributes.put(HtmlAttribute.TYPE, HtmlAttribute.TYPE_ID);
0277: }
0278:
0279: /** Error Handler which does nothing */
0280: public static final ErrorHandler SILENT_ERROR_HANDLER = new ErrorHandler() {
0281: public void error(CSSParseException cp) {
0282: }
0283:
0284: public void fatalError(CSSParseException cp) {
0285: }
0286:
0287: public void warning(CSSParseException cp) {
0288: }
0289: };
0290:
0291: // <markup_separation>
0292: // private MarkupUnit unit;
0293: // </markup_separation>
0294: private ErrorHandler errorHandler;
0295:
0296: /**
0297: * Creates a new XhtmlCssEngine
0298: * @param doc The associated document.
0299: * @param uri The document URI.
0300: * @param p The CSS parser to use.
0301: * @param ctx The CSS context.
0302: */
0303: // <markup_separation>
0304: // private XhtmlCssEngine(Document doc, URL uri, ExtendedParser p, CSSContext ctx, MarkupUnit unit) {
0305: // ====
0306: private XhtmlCssEngine(Document doc, URL uri, ExtendedParser p,
0307: CSSContext ctx) {
0308: // </markup_separation>
0309: super (doc, uri, p, XHTML_VALUE_MANAGERS,
0310: XHTML_SHORTHAND_MANAGERS, null, null,
0311: HtmlAttribute.STYLE, null, HtmlAttribute.CLASS, true,
0312: null, ctx);
0313:
0314: // SVG defines line-height to be font-size.
0315: // What about XHTML?
0316: //lineHeightIndex = fontSizeIndex;
0317: // <markup_separation>
0318: // this.unit = unit;
0319: // </markup_separation>
0320:
0321: assert (XhtmlCss.FINAL_INDEX + 1) == XHTML_VALUE_MANAGERS.length;
0322:
0323: // Initialize indices since we've commented that out from
0324: // the superclass
0325: indexes = valueManagerIndex;
0326: fontSizeIndex = XhtmlCss.FONT_SIZE_INDEX;
0327: lineHeightIndex = XhtmlCss.LINE_HEIGHT_INDEX;
0328: colorIndex = XhtmlCss.COLOR_INDEX;
0329: shorthandIndexes = shorthandManagerIndex;
0330: }
0331:
0332: /*
0333: * TODO: Consider a performance optimization described by David Hyatt of Gecko and
0334: * later Safari fame, listed here: http://weblogs.mozillazine.org/hyatt/archives/2005_05.html#007507
0335:
0336: One of the most interesting problems (to me at least) in browser
0337: layout engines is how to implement a style system that can
0338: determine the style information for elements on a page
0339: efficiently. I worked on this extensively in the Gecko layout
0340: engine during my time at AOL and I've also done a lot of work on
0341: it for WebCore at Apple. My ideal implementation would actually be
0342: a hybrid of the two systems, since some of the optimizations I've
0343: done exist only in one engine or the other.
0344:
0345: When dealing with style information like font size or text color,
0346: you have both the concept of back end information, what was
0347: specified in the style rule, and the concept of front end
0348: information, the computed result that you'll actually use when
0349: rendering. The interesting problem is how to compute this front
0350: end information for a given element efficiently.
0351:
0352: Back end information can be specified in two different ways. It
0353: can either be specified using CSS syntax, whether in a stylesheet
0354: or in an inline style attribute on the element itself, or it is
0355: implicitly present because another attribute on the element
0356: specified presentational information. An example of such an
0357: attribute would be the color attribute on the font tag. Both
0358: WebCore and Gecko use the term mapped attribute to describe an
0359: attribute whose value (or even mere presence) maps to some
0360: implicit style declaration.
0361:
0362: A rule in CSS consists of two pieces. There is the selector, that
0363: bit of information that says under what conditions the rule should
0364: match a given element, and there is the declaration, a list of
0365: property/value pairs that should be applied to the element should
0366: the selector be matched.
0367:
0368: All back end information can ultimately be thought of as supplying
0369: a declaration. A normal rule in a stylesheet that is matched has
0370: the declaration specified as part of the rule. An inline style
0371: attribute on an element has no selector and is simply a
0372: declaration that always applies to that element. Similarly each
0373: individual mapped attribute (like the color and face attributes on
0374: the font tag) can be thought of as supplying a declaration as
0375: well.
0376:
0377: Therefore the process of computing the style information for an
0378: element can be broken down into two phases. The first phase is to
0379: determine what set of declarations apply to an element. Once that
0380: back end information has been determined, the second phase is to
0381: take that back end information and quickly determine the
0382: information that should be used when rendering.
0383:
0384: WebCore (in upcoming Safari releases) has a really cool
0385: optimization that I came up with to avoid even having to compute
0386: the set of declarations that apply to an element. This
0387: optimization in practice results in not even having to match style
0388: for about 60% of the elements on your page.
0389:
0390: The idea behind the optimization is to recognize when two elements
0391: in a page are going to have the same style through DOM (and other
0392: state) inspection and to simply share the front end style
0393: information between those two elements whenever possible.
0394:
0395: There are a number of conditions that must be met in order for
0396: this sharing to be possible:
0397:
0398: (1) The elements must be in the same mouse state (e.g., one can't
0399: be in :hover while the other isn't)
0400: (2) Neither element should have an id
0401: (3) The tag names should match
0402: (4) The class attributes should match
0403: (5) The set of mapped attributes must be identical
0404: (6) The link states must match
0405: (7) The focus states must match
0406: (8) Neither element should be affected by attribute selectors,
0407: where affected is defined as having any selector match that
0408: uses an attribute selector in any position within the selector
0409: at all
0410: (9) There must be no inline style attribute on the elements
0411: (10) There must be no sibling selectors in use at all. WebCore
0412: simply throws a global switch when any sibling selector is
0413: encountered and disables style sharing for the entire
0414: document when they are present. This includes the + selector
0415: and selectors like :first-child and :last-child.
0416:
0417: The algorithm to locate a shared style then goes something like
0418: this. You walk through your previous siblings and for each one see
0419: if the above 10 conditions are met. If you find a match, then
0420: simply share your style information with the other element. Such a
0421: system obviously assumes a reference counting model for your front
0422: end style information.
0423:
0424: Where this optimization kicks into high gear, however, is that it
0425: doesn't have to give up if no siblings can be located. Because the
0426: detection of identical style contexts is essentially O(1), nothing
0427: more than a straight pointer comparison, you can easily look for
0428: cousins of your element and still share style with those elements.
0429:
0430: The way this works is that if you can't locate a sibling, you can
0431: go up to a parent element and attempt to find a sibling or cousin
0432: of the parent element that has the same style pointer. If you find
0433: such an element, you can then drill back down into its children
0434: and attempt to find a match.
0435:
0436: This means that for HTML like the following:
0437:
0438: <table>
0439: <tr class='row'>
0440: <td class='cell' width=300 nowrap>Cell One</td>
0441: </tr>
0442: <tr class='row'>
0443: <td class='cell' width=300 nowrap>Cell Two</td>
0444: </tr>
0445:
0446: In the above example, not only do the two rows share the same
0447: style information, but the two cells do as well. This optimization
0448: works extremely well for both old-school HTML (in which many
0449: deprecated presentational tags are used) and newer HTML (in which
0450: class attributes might figure more prominently).
0451:
0452: Once the engine determines that a style can't be shared, i.e.,
0453: that no pre-existing front end style pointer is available, then
0454: it's time to figure out the set of declarations that match a given
0455: element. It is obvious that for inline style attributes and mapped
0456: attributes that you can find the corresponding declaration
0457: quickly. The inline style declaration can be owned by the element,
0458: and the mapped attributes can be kept in a document-level
0459: hash. WebCore has a bit of an edge over Gecko here in that it
0460: treats each individual mapped attribute on an element as a
0461: separate declaration, whereas Gecko hashes all of the mapped
0462: attributes on an element as a single "rule." This means that Gecko
0463: will not be able to share the mapped attribute declaration for the
0464: following two elements:
0465:
0466: <img width=300 border=0>
0467: <img width=500 border=0>
0468:
0469: WebCore creates three unique declarations and hashes them, one for
0470: a width of 300, one for a width of 500, and one for a border of
0471: 0. Gecko creates two different "rules," one for
0472: (width=300,border=0) and another for (width=500,border=0). As you
0473: can see in such a system, you will frequently not be able to treat
0474: the identical border attributes as the same.
0475:
0476: Aside from this difference in mapped attribute handling, the two
0477: engines employ a similar optimization for quickly determining
0478: matching stylesheet rules called rule filtering.
0479: [removed - this is done.]
0480:
0481: This brings us to the final phase of the style computation, which
0482: is taking the set of matches and quickly computing the appropriate
0483: front end style information. It is here that Gecko really
0484: shines. What I implemented in Gecko was a data structure called
0485: the rule tree for efficient storing of cached style information
0486: that can be shared *even when* two elements are not necessarily
0487: the same.
0488:
0489: The idea behind the rule tree is as follows. You can think of the
0490: universe of possible rules in your document as an alphabet and the
0491: set of rules that are matched by an element as a given input
0492: word. For example, imagine that you had 26 rules in a stylesheet
0493: and you labeled them A-Z. One element might match three rules in
0494: the sheet, thus forming the input word "C-A-T" or another might
0495: form the input word "D-O-G."
0496:
0497: There are several important observations one can make once you
0498: formulate the problem this way. The first is that words that are
0499: prefixes of a larger word will end up applying the same set of
0500: rules. All additional letters in the word do is result in the
0501: application of more declarations. Thus the rule tree is
0502: effectively a lexicographic tree of nodes, with each node in a
0503: tree being created lazily as you walk the tree spelling out a
0504: given word.
0505:
0506: This system allows you to cache style information at each node in
0507: the tree. This means that once you've looked up the word
0508: "C-A-T-E-R-W-A-U-L", and cached information at all of the nodes,
0509: then looking up the word "C-A-T" becomes more efficient.
0510:
0511: In order to make the caching efficient, properties can be grouped
0512: into categories, with the primary criterion for categorization
0513: being whether the property inherits by default. It's also
0514: important to group properties together that would logically be
0515: specified together, so that when a fault occurs and you have to
0516: make a copy of a given struct, you do so knowing that the other
0517: values in the struct were probably going to be different anyway.
0518:
0519: Once you have the properties grouped into categories like the
0520: border struct or the background struct, then you can either store
0521: these structs in the rule tree or as part of a style tree that
0522: more or less matches the structure of the document. Inheritance
0523: has to apply down the style tree and tends to force a fault,
0524: whereas non-inherited properties can usually be cached in the rule
0525: tree for easy access.
0526:
0527: WebCore doesn't contain a rule tree, but it is smart enough to
0528: refcount the structs and share them as long as no properties have
0529: been set in the struct. In practice this works pretty well but is
0530: not as ideal as the rule tree solution.
0531: */
0532:
0533: /** Create a new engine and associate it with the given document */
0534: // <markup_separation>
0535: // public static XhtmlCssEngine create(RaveDocument doc, MarkupUnit unit, URL url) {
0536: // ====
0537: static XhtmlCssEngine create(Document doc, URL url,
0538: CssUserAgentInfo userAgentInfo) {
0539: if (doc == null) {
0540: throw new NullPointerException("Document many not be null!"); // NOI18N
0541: }
0542:
0543: // </markup_separation>
0544: UserAgent userAgent = new UserAgent(userAgentInfo);
0545: DesignerContext ctx = new DesignerContext(doc, userAgent,
0546: userAgentInfo);
0547: // Parser p = new org.apache.batik.css.parser.Parser()
0548: Parser p = new RaveParser();
0549: ExtendedParser ep = ExtendedParserWrapper.wrap(p);
0550: // <markup_separation>
0551: // XhtmlCssEngine engine = new XhtmlCssEngine(doc, url, ep, ctx, unit);
0552: // ====
0553: XhtmlCssEngine engine = new XhtmlCssEngine(doc, url, ep, ctx);
0554: // </markup_separation>
0555: ctx.setEngine(engine);
0556: engine.setUserAgentStyleSheet(getUserAgentStyleSheet(engine));
0557:
0558: // <moved to caller this is not interesting for the engine itself>
0559: // if (doc != null) {
0560: // doc.setCssEngine(engine);
0561: // }
0562: // </moved to caller>
0563:
0564: engine.setCSSEngineUserAgent(userAgent);
0565: engine.setMedia(userAgent.getMedia());
0566:
0567: /* TODO User defined stylesheets not yet supported
0568: String uri = userAgent.getUserStyleSheetURI();
0569: if (uri != null) {
0570: try {
0571: URL url = new URL(uri);
0572: eng.setUserAgentStyleSheet
0573: (eng.parseStyleSheet(url, "all"));
0574: } catch (MalformedURLException e) {
0575: userAgent.displayError(e);
0576: }
0577: }
0578: engine.setAlternateStyleSheet(userAgent.getAlternateStyleSheet());
0579: */
0580: return engine;
0581: }
0582:
0583: private static StyleSheet getUserAgentStyleSheet(CSSEngine engine) {
0584: if (defaultRules == null) {
0585: URL url = XhtmlCssEngine.class.getResource("default.css"); // TODO: reuse UserAgentStylesheet
0586:
0587: //URL url = null;
0588: //
0589: //try {
0590: // //url = new URL("file:" + System.getProperty("netbeans.home") + "/ua.css"); // NOI18N
0591: // url = new URL("file:/tmp/default.css"); // NOI18N
0592: //} catch (java.net.MalformedURLException mue) {
0593: // mue.printStackTrace();
0594: //}
0595: if (url != null) {
0596: InputSource is = new InputSource(url.toString());
0597: defaultRules = engine.parseStyleSheet(is, url, "all",
0598: url);
0599: defaultRules.setupFilters();
0600: }
0601: }
0602:
0603: return defaultRules;
0604: }
0605:
0606: // XXX Not used.
0607: // /** Reinitialize the engine by re-reading the document stylesheets
0608: // * @todo Attach to my DocumentFragment instead!
0609: // */
0610: // public void setDocument(/*RaveDocument*/Document doc) {
0611: // DesignerContext ctx = (DesignerContext)getCSSContext();
0612: //
0613: // if (document != null) {
0614: // refreshStyles(document);
0615: // }
0616: //
0617: // ctx.setDocument(doc);
0618: // this.document = doc;
0619: //// <moving RaveDoc refs outside> engine is not interested in registering itself somewhere.
0620: //// doc.setCssEngine(this);
0621: //// </moving RaveDoc refs outside>
0622: // }
0623:
0624: // private void handleError(String message, Object location, int lineno, int column, Exception e) {
0625: // if (errorHandler != null) {
0626: // String filename = null;
0627: // int line = lineno;
0628: //
0629: // if (location != null) {
0630: // final String fullname = computeFilename(location);
0631: // filename = fullname.substring(fullname.lastIndexOf('/') + 1);
0632: // line = computeLineNumber(location, lineno);
0633: //
0634: // if (filename != null) {
0635: // message = filename + ":" + line + /* ":" + column + */
0636: // ": " + message;
0637: // }
0638: // }
0639: //
0640: // CSSParseException cpe = new CSSParseException(message, filename, line, column, e);
0641: // errorHandler.error(cpe);
0642: // }
0643: // }
0644: private static CSSParseException createCSSParseException(
0645: String message, Object location, int lineno, int column,
0646: Exception e) {
0647: String filename = null;
0648: int line = lineno;
0649:
0650: if (location != null) {
0651: // final String fullname = computeFilename(location);
0652: // filename = fullname.substring(fullname.lastIndexOf('/') + 1);
0653: // line = computeLineNumber(location, lineno);
0654: //
0655: // if (filename != null) {
0656: // message = filename + ":" + line + /* ":" + column + */
0657: // ": " + message;
0658: // }
0659: message = location + ":" + lineno + ": " + message; // NOI18N
0660: }
0661:
0662: return new CSSParseException(message, filename, line, column, e);
0663: }
0664:
0665: protected void warnCircularReference(URL uri, Object location) {
0666: String message = NbBundle.getMessage(XhtmlCssEngine.class,
0667: "MSG_CircularReference", uri); // NOI18N
0668:
0669: if (errorHandler != null) {
0670: // handleError(message, location, -1, -1, null);
0671: errorHandler.error(createCSSParseException(message,
0672: location, -1, -1, null));
0673: } else {
0674: // OutputListener listener = getListener(location, -1, -1);
0675: // MarkupService.displayError(message, listener);
0676: if (location instanceof AbstractValue) {
0677: location = ((AbstractValue) location).getLocation();
0678: }
0679: // InSyncService.getProvider().getRaveErrorHandler().displayErrorForLocation(message, location, -1, -1);
0680: UserAgent userAgent = getUserAgent();
0681: if (userAgent == null) {
0682: // XXX What to do now, log it?
0683: } else {
0684: userAgent.displayErrorForLocation(message, location,
0685: -1, -1);
0686: }
0687: }
0688: }
0689:
0690: /**
0691: * Scan attributes for an element and transcribe deprecated
0692: * style-type attributes into real style properties
0693: */
0694: protected void applyNonCSSPresentationalHints(CSSStylableElement e,
0695: StyleMap map) {
0696: String tname = e.getTagName(); // should already be lowercase
0697: assert (tname != null) && (tname.length() > 0);
0698:
0699: if (tname.indexOf(':') != -1) {
0700: // Attributes on JSF elements etc. should not alias.
0701: // For example, the Braveheart button component has a "text"
0702: // attribute which has nothing to do with the text attribute
0703: // in html which affects screen color.
0704: return;
0705: }
0706:
0707: org.w3c.dom.NamedNodeMap atts = e.getAttributes();
0708:
0709: if (atts == null) {
0710: return;
0711: }
0712:
0713: int ac = atts.getLength();
0714:
0715: for (int i = 0; i < ac; i++) {
0716: Attr att = (Attr) atts.item(i);
0717: String aname = att.getName().toLowerCase().intern();
0718: int attribute = attributes.get(aname);
0719:
0720: if (attribute == -1) {
0721: continue;
0722: }
0723:
0724: String aval = att.getValue();
0725:
0726: if ((aval == null) || (aval.length() == 0)) {
0727: // Avoid attributes like this:
0728: // <body color="">
0729: // Here we'll try to CSS parse "", and we get a parse
0730: // error for this:
0731: // 'The attribute "?" represents an invalid CSS value ("").'
0732: continue;
0733: }
0734:
0735: int pname = -1;
0736: String pval = aval;
0737:
0738: switch (attribute) {
0739: // Todo: ALINK/VLINK attributes -> e.g. LINK=blue becomes
0740: // a[href] {color: blue;}
0741: case HtmlAttribute.ALIGN_ID:
0742:
0743: // <table>: align = left|center|right [CI]
0744: // <object>/<image>: align = bottom|middle|top|left|right
0745: // <td>: align = left|center|right|justify|char [CI]
0746: if (tname.equals(HtmlTag.IMG.name)
0747: || // XXX just switch to block tag check instead?
0748: tname.equals(HtmlTag.DIV.name)
0749: || tname.equals(HtmlTag.FORM.name)
0750: || tname.equals(HtmlTag.IFRAME.name)
0751: || tname.equals(HtmlTag.TABLE.name)
0752: || tname.equals(HtmlTag.APPLET.name)
0753: || tname.equals(HtmlTag.OBJECT.name)) {
0754: if (aval.equals("left") || aval.equals("right")) { //!CQ need to tokenize aval?
0755: pname = XhtmlCss.FLOAT_INDEX;
0756: } else if (aval.equals("center")) { // NOI18N
0757: applyNonCSSPresentationalHint(e, map,
0758: XhtmlCss.TEXT_ALIGN_INDEX,
0759: CssConstants.CSS_RAVECENTER_VALUE);
0760: } else {
0761: // top/texttop,middle,bottom/baseline,absbottom
0762: pname = XhtmlCss.VERTICAL_ALIGN_INDEX;
0763:
0764: if (aval.equals("texttop")) { // NOI18N
0765: pval = CssConstants.CSS_TEXT_TOP_VALUE;
0766: } else if (aval.equals("absmiddle")) { // NOI18N
0767:
0768: // This is NOT right but from a quick google
0769: // there's no direct equivalent value for
0770: // vertical align to emulate the absmiddle behavior
0771: pval = CssConstants.CSS_MIDDLE_VALUE;
0772: } else if (aval.equals("absbottom")) { // NOI18N
0773: pval = CssConstants.CSS_BOTTOM_VALUE;
0774: }
0775: }
0776: } else {
0777: pname = XhtmlCss.TEXT_ALIGN_INDEX; // P, TD, TH, THEAD, TFOOT, TBODY
0778: }
0779:
0780: break;
0781:
0782: case HtmlAttribute.LINK_ID:
0783:
0784: if (tname.equals(HtmlTag.BODY.name)) {
0785: pname = XhtmlCss.RAVELINKCOLOR_INDEX;
0786: }
0787:
0788: break;
0789:
0790: case HtmlAttribute.VALIGN_ID: // TD, TH, THEAD, TFOOT, TBODY
0791:
0792: // valign = top|middle|bottom|baseline [CI]
0793: pname = XhtmlCss.VERTICAL_ALIGN_INDEX;
0794:
0795: break;
0796:
0797: case HtmlAttribute.CLEAR_ID: // BR, possibly others
0798:
0799: // clear = none|left|right|all [CI]
0800: if (aval.equals("all")) { // NOI18N
0801: pval = "both"; // NOI18N
0802: } // none, left, right are the same for both
0803:
0804: pname = XhtmlCss.CLEAR_INDEX;
0805:
0806: break;
0807:
0808: case HtmlAttribute.BGCOLOR_ID: // BODY, TABLE
0809: pname = XhtmlCss.BACKGROUND_COLOR_INDEX;
0810:
0811: break;
0812:
0813: case HtmlAttribute.BACKGROUND_ID: // BODY
0814: pname = XhtmlCss.BACKGROUND_IMAGE_INDEX;
0815:
0816: // XXX #6457821 Encode the url string, to fix cases when there are spaces.
0817: if (!isEncodedUrl(aval)) {
0818: aval = encodeUrl(aval);
0819: }
0820:
0821: //plu = new LexicalUnitImpl.createURI(null, aval);
0822: pval = "url(" + aval + ")";
0823:
0824: break;
0825:
0826: case HtmlAttribute.TEXT_ID: // BODY
0827: pname = XhtmlCss.COLOR_INDEX;
0828:
0829: break;
0830:
0831: case HtmlAttribute.WIDTH_ID: //!CQ PRE, TH, TD, IMG only?
0832: pname = XhtmlCss.WIDTH_INDEX;
0833:
0834: if (pval.indexOf('%') > 0) { // XXXX should this be aval????
0835: pval = aval;
0836: } else if (hasNoUnits(pval)) {
0837: pval = aval + "px";
0838: }
0839:
0840: break;
0841:
0842: case HtmlAttribute.HEIGHT_ID: //!CQ PRE, TH, TD, IMG only?
0843: pname = XhtmlCss.HEIGHT_INDEX;
0844:
0845: if (pval.indexOf('%') > 0) { // XXXX should this be aval????
0846: pval = aval;
0847: } else if (hasNoUnits(pval)) {
0848: pval = aval + "px";
0849: }
0850:
0851: break;
0852:
0853: case HtmlAttribute.NOWRAP_ID:
0854:
0855: // MSDN docs on dhtml lists the nowrap attribute as applicable
0856: // to body, dd, div, dt, td, th. However, Mozilla doesn't
0857: // seem to recognize it on div.
0858: if (tname.equals(HtmlTag.BODY.name)
0859: || tname.equals(HtmlTag.TD.name)
0860: || tname.equals(HtmlTag.TH.name)
0861: || tname.equals(HtmlTag.DD.name)
0862: || tname.equals(HtmlTag.DT.name)
0863: || tname.equals(HtmlTag.DIV.name)) {
0864: pname = XhtmlCss.WHITE_SPACE_INDEX;
0865: pval = CssConstants.CSS_NOWRAP_VALUE;
0866: }
0867:
0868: break;
0869:
0870: case HtmlAttribute.BORDER_ID:
0871:
0872: if (hasNoUnits(pval)) { // XXXX should this be aval????
0873: pval = aval + "px";
0874: }
0875:
0876: // Property translates to multiple properties,
0877: // so set them directly and continue
0878: applyNonCSSPresentationalHint(e, map,
0879: XhtmlCss.BORDER_TOP_WIDTH_INDEX, pval);
0880: applyNonCSSPresentationalHint(e, map,
0881: XhtmlCss.BORDER_LEFT_WIDTH_INDEX, pval);
0882: applyNonCSSPresentationalHint(e, map,
0883: XhtmlCss.BORDER_RIGHT_WIDTH_INDEX, pval);
0884: applyNonCSSPresentationalHint(e, map,
0885: XhtmlCss.BORDER_BOTTOM_WIDTH_INDEX, pval);
0886:
0887: continue; // NOTE - continue, not break
0888:
0889: case HtmlAttribute.COLOR_ID: // generally applicable
0890: pname = XhtmlCss.COLOR_INDEX;
0891:
0892: break;
0893:
0894: case HtmlAttribute.SIZE_ID:
0895:
0896: if (tname.equals(HtmlTag.FONT.name)
0897: || tname.equals(HtmlTag.BASEFONT.name)) {
0898: pname = XhtmlCss.FONT_SIZE_INDEX;
0899: pval = CssConstants.CSS_MEDIUM_VALUE;
0900:
0901: if (aval.length() > 0) {
0902: if (aval.charAt(0) == '+') {
0903: // XXX We could try to read the actual relative
0904: // value here, and adjust - but the <font> tag
0905: // is already deprecated and discouraged. If it
0906: // was easy I'd do it but with CSS there isn't
0907: // a way to mage the font size bigger/smaller
0908: // by more than a factor of 1 so let's just
0909: // leave it at this: <font size="+n"> is
0910: // will be equivalent to <font size="+1">
0911: pval = CssConstants.CSS_LARGER_VALUE;
0912: } else if (aval.charAt(0) == '-') {
0913: // Same comment as under '+' handling above
0914: pval = CssConstants.CSS_SMALLER_VALUE;
0915: } else {
0916: // Map to absolute font size
0917: try {
0918: int n = Integer.parseInt(aval);
0919:
0920: switch (n) {
0921: case 1:
0922: pval = CssConstants.CSS_XX_SMALL_VALUE;
0923:
0924: break;
0925:
0926: case 2:
0927: pval = CssConstants.CSS_X_SMALL_VALUE;
0928:
0929: break;
0930:
0931: case 3:
0932: pval = CssConstants.CSS_SMALL_VALUE;
0933:
0934: break;
0935:
0936: case 4:
0937: pval = CssConstants.CSS_MEDIUM_VALUE;
0938:
0939: break;
0940:
0941: case 5:
0942: pval = CssConstants.CSS_LARGE_VALUE;
0943:
0944: break;
0945:
0946: case 6:
0947: pval = CssConstants.CSS_X_LARGE_VALUE;
0948:
0949: break;
0950:
0951: case 7:
0952: pval = CssConstants.CSS_XX_LARGE_VALUE;
0953:
0954: break;
0955: }
0956: } catch (NumberFormatException ex) {
0957: // The attribute is set to a bogus value.
0958: // In this case we should simply use the
0959: // default of "medium".
0960: // pval was set to "medium" already.
0961: }
0962: }
0963: }
0964: } else if (tname.equals(HtmlTag.HR.name)) { //!CQ HR only?
0965: pname = XhtmlCss.HEIGHT_INDEX;
0966:
0967: if (pval.indexOf('%') > 0) {
0968: pval = aval;
0969: } else if (hasNoUnits(pval)) {
0970: pval = aval + "px";
0971: }
0972: }
0973:
0974: break;
0975:
0976: case HtmlAttribute.FACE_ID: // FONT
0977: pname = XhtmlCss.FONT_FAMILY_INDEX;
0978:
0979: break;
0980:
0981: case HtmlAttribute.TYPE_ID:
0982:
0983: if (tname.equals(HtmlTag.UL.name)
0984: || tname.equals(HtmlTag.OL.name)
0985: || tname.equals(HtmlTag.DL.name)
0986: || tname.equals(HtmlTag.LI.name)) {
0987: pname = XhtmlCss.LIST_STYLE_TYPE_INDEX;
0988: }
0989:
0990: break;
0991:
0992: //hspace, vspace => margin-[left+right], margin-[top+bottom] ?
0993: // What about this:
0994: // <basefont> => "font"
0995: }
0996:
0997: // set the style property iff it was found
0998: if (pname != -1) {
0999: applyNonCSSPresentationalHint(e, map, pname, pval);
1000: }
1001: }
1002: }
1003:
1004: /**
1005: * Return true if the string parameter contains a number without any units at the end. For this
1006: * purpose a unit will be considered any alphabetical characters or non space character.
1007: */
1008: public static boolean hasNoUnits(String s) {
1009: for (int i = 0, n = s.length(); i < n; i++) {
1010: char c = s.charAt(i);
1011:
1012: if (!Character.isDigit(c) && !Character.isWhitespace(c)) {
1013: return false;
1014: }
1015: }
1016:
1017: return true;
1018: }
1019:
1020: // XXX Ugly method to test whether the url string is encoded already.
1021: // FIXME How these things should be done correctly?
1022: private static boolean isEncodedUrl(String url) {
1023: if (url == null || url.length() == 0) {
1024: return false;
1025: }
1026: // XXX Checking for the same like used in encodeUrl.
1027: if (url.indexOf("%09") != -1 // NOI18N
1028: || url.indexOf("%20") != -1 // NOI18N
1029: || url.indexOf("%23") != -1 // NOI18N
1030: || url.indexOf("%25") != -1 // NOI18N
1031: || url.indexOf("%3C") != -1 // NOI18N
1032: || url.indexOf("%3E") != -1 // NOI18N
1033: || url.indexOf("%5B") != -1 // NOI18N
1034: || url.indexOf("%5D") != -1 // NOI18N
1035: || url.indexOf("%7B") != -1 // NOI18N
1036: || url.indexOf("%7D") != -1 // NOI18N
1037: || url.indexOf("%7E") != -1) { // NOI18N
1038: return true;
1039: }
1040: return false;
1041: }
1042:
1043: // XXX Copied from propertyeditors/UrlPropertyEditor.
1044: // There should be common utility method.
1045: /**
1046: * Convert a file system path to a URL by converting unsafe characters into
1047: * numeric character entity references. The unsafe characters are listed in
1048: * in the IETF specification of URLs
1049: * (<a href="http://www.ietf.org/rfc/rfc1738.txt">RFC 1738</a>). Safe URL
1050: * characters are all printable ASCII characters, with the exception of the
1051: * space characters, '#', <', '>', '%', '[', ']', '{', '}', and '~'. This
1052: * method differs from {@link java.net.URLEncoder#encode(String)}, in that
1053: * it is intended for encoding the path portion of a URL, not the query
1054: * string.
1055: */
1056: private static String encodeUrl(String url) {
1057: if (url == null || url.length() == 0)
1058: return url;
1059: StringBuffer buffer = new StringBuffer();
1060: String anchor = null;
1061: int index = url.lastIndexOf('#');
1062: if (index >= 0) {
1063: anchor = url.substring(index + 1);
1064: url = url.substring(0, index);
1065: }
1066: char[] chars = url.toCharArray();
1067: for (int i = 0; i < chars.length; i++) {
1068: if (chars[i] <= '\u0020') {
1069: buffer.append('%');
1070: buffer.append(Integer.toHexString((int) chars[i]));
1071: } else {
1072: switch (chars[i]) {
1073: case '\u0009': // Tab
1074: buffer.append("%09");
1075: break;
1076: case '\u0020': // Space
1077: buffer.append("%20");
1078: break;
1079: case '#':
1080: buffer.append("%23");
1081: break;
1082: case '%':
1083: buffer.append("%25");
1084: break;
1085: case '<':
1086: buffer.append("%3C");
1087: break;
1088: case '>':
1089: buffer.append("%3E");
1090: break;
1091: case '[':
1092: buffer.append("%5B");
1093: break;
1094: case ']':
1095: buffer.append("%5D");
1096: break;
1097: case '{':
1098: buffer.append("%7B");
1099: break;
1100: case '}':
1101: buffer.append("%7D");
1102: break;
1103: case '~':
1104: buffer.append("%7E");
1105: break;
1106: default:
1107: buffer.append(chars[i]);
1108: }
1109: }
1110: }
1111: if (anchor != null) {
1112: buffer.append('#');
1113: buffer.append(anchor);
1114: }
1115: if (buffer.length() == url.length())
1116: return url;
1117: return buffer.toString();
1118: }
1119:
1120: /** Refresh all styles
1121: * @todo Rename to disposeStyles to reflect what this method
1122: * actually does.
1123: */
1124: public void refreshStyles() {
1125: if (styleSheetNodes != null) {
1126: // Since elements hang on to their stylesheets, and on a pure
1127: // refresh we don't recreate the elements, we've gotta clear them
1128: // out
1129: Iterator it = styleSheetNodes.iterator();
1130:
1131: while (it.hasNext()) {
1132: Object o = it.next();
1133:
1134: // if (o instanceof StyleElement) {
1135: // ((StyleElement)o).refresh();
1136: // } else if (o instanceof StylesheetLinkElement) {
1137: // ((StylesheetLinkElement)o).refresh();
1138: // }
1139: if (o instanceof StyleRefreshable) {
1140: ((StyleRefreshable) o).refresh();
1141: }
1142: }
1143:
1144: styleSheetNodes = null;
1145: }
1146:
1147: disposeStyleMaps(document.getDocumentElement());
1148:
1149: // XXX Is this still needed?
1150: if (document.getDocumentElement() != document) {
1151: disposeStyleMaps(document);
1152: }
1153: }
1154:
1155: // Moved to API.
1156: // /** XXX To be implemented by style elements in order the engine calls refresh on them.
1157: // * TODO the refreshing smells badly, revise entire func. */
1158: // public interface StyleRefreshable {
1159: // public void refresh();
1160: // } // Enf of StyleRefreshable.
1161:
1162: /** Remove precomputed styles for the given element - compute them
1163: * over again.
1164: * @todo Do I have to clear computed styles on all the children too?
1165: */
1166: public void clearComputedStyles(Element element/*, String pseudo*/) {
1167: disposeStyleMaps(element);
1168: }
1169:
1170: // <removing design bean manipulation in engine>
1171: // protected void setAttributeValue(Element elt, String name, String value) {
1172: // RaveElement xel = (RaveElement)elt;
1173: //
1174: // if (xel.getDesignBean() != null) {
1175: // DesignProperty property = xel.getDesignBean().getProperty("style"); // NOI18N
1176: //
1177: // if (property != null) {
1178: // try {
1179: // if ((value != null) && (value.length() > 0)) {
1180: // property.setValue(value);
1181: // } else {
1182: // property.unset();
1183: // }
1184: //
1185: // return;
1186: // } catch (Exception ex) {
1187: // ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, ex);
1188: // // For some reason the above throws exceptions
1189: // // sometimes, not sure why org.w3c.dom.DOMException:
1190: // // NOT_FOUND_ERR: An attempt is made to reference a
1191: // // node in a context where it does not exist. TODO
1192: // // - figure out WHY! For now just swallow since
1193: // // there's nothing more we can do about it.
1194: // // (Update: It think this may be fixed now)
1195: // }
1196: // }
1197: // }
1198: //
1199: // // If the above fails (shouldn't)
1200: // super.setAttributeValue(elt, name, value);
1201: // }
1202: // ====
1203: /** XXX Only a fallback for the original suspicious pattern. */
1204: public void setStyleAttributeValue(Element elt, String value) {
1205: setAttributeValue(elt, HtmlAttribute.STYLE, value);
1206: }
1207:
1208: // </removing design bean manipulation in engine>
1209:
1210: /**
1211: * Set the error handler to be used in the case of parse errors.
1212: * If not set, the default handler will be used, which emits warnings
1213: * in the output window.
1214: */
1215: public void setErrorHandler(ErrorHandler handler) {
1216: this .errorHandler = handler;
1217: }
1218:
1219: // protected OutputListener getListener(Object location, int lineno, final int column) {
1220: // OutputListener listener = null; // TODO - provide clickable errors
1221: //
1222: // if (location != null) {
1223: // final String fullname = computeFilename(location);
1224: // String filename = fullname.substring(fullname.lastIndexOf('/') + 1);
1225: // final int line = computeLineNumber(location, lineno);
1226: //
1227: // if (filename != null) {
1228: // listener =
1229: // new OutputListener() {
1230: // public void outputLineSelected(OutputEvent ev) {
1231: // }
1232: //
1233: // public void outputLineAction(OutputEvent ev) {
1234: // // <markup_separation>
1235: //// Util.show(fullname, null, (line >= 1) ? line : 1, column, true);
1236: // // ====
1237: // MarkupService.show(fullname, (line >= 1) ? line : 1, column, true);
1238: // // </markup_separation>
1239: // }
1240: //
1241: // public void outputLineCleared(OutputEvent ev) {
1242: // }
1243: // };
1244: // }
1245: // }
1246: //
1247: // return listener;
1248: // }
1249:
1250: protected void displayError(DOMException e, Object location,
1251: int lineno, final int column) {
1252: String message = e.getLocalizedMessage();
1253:
1254: if ((message == null) || (message.length() == 0)) {
1255: return;
1256: }
1257:
1258: if (errorHandler != null) {
1259: // handleError(message, location, lineno, column, null);
1260: errorHandler.error(createCSSParseException(message,
1261: location, lineno, column, null));
1262: } else {
1263: // OutputListener listener = getListener(location, lineno, column);
1264: // MarkupService.displayError(message, listener);
1265: // String fileName = computeFilename(location);
1266: // int line = computeLineNumber(location, lineno);
1267: if (location instanceof AbstractValue) {
1268: location = ((AbstractValue) location).getLocation();
1269: }
1270: // InSyncService.getProvider().getRaveErrorHandler().displayErrorForLocation(message, location, lineno, column);
1271: UserAgent userAgent = getUserAgent();
1272: if (userAgent == null) {
1273: // XXX Log it?
1274: } else {
1275: userAgent.displayErrorForLocation(message, location,
1276: lineno, column);
1277: }
1278: }
1279: }
1280:
1281: protected void displayMissingStyleSheet(String uri) {
1282: String message = NbBundle.getMessage(XhtmlCssEngine.class,
1283: "MissingStylesheet", // NOI18N
1284: uri);
1285:
1286: if (errorHandler != null) {
1287: // handleError(message, null, -1, 0, null);
1288: errorHandler.error(createCSSParseException(message, null,
1289: -1, 0, null));
1290: } else {
1291: // OutputListener listener = null; // TODO - provide clickable errors
1292: // MarkupService.displayError(message, listener);
1293: // InSyncService.getProvider().getRaveErrorHandler().displayError(message);
1294: UserAgent userAgent = getUserAgent();
1295: if (userAgent == null) {
1296: // XXX Log it?
1297: } else {
1298: userAgent
1299: .displayErrorForLocation(message, null, -1, -1);
1300: }
1301: }
1302: }
1303:
1304: /** Initialize all values for a given element (this is otherwise done
1305: * lazily) on a getComputedStyle call for a particular property).
1306: */
1307: public void precomputeAllStyles(CSSStylableElement elt) {
1308: for (int i = 0, n = XHTML_VALUE_MANAGERS.length; i < n; i++) {
1309: getComputedStyle(elt, null, i); // throwing away result
1310: }
1311: }
1312:
1313: // /**
1314: // * The filename where this CSS value is defined. This could be a
1315: // * CSS file but doesn't have to be -- it could point to a markup file
1316: // * with an inline style attribute.
1317: // */
1318: // public static String computeFilename(AbstractValue av) {
1319: // return computeFilename(av.getLocation());
1320: // }
1321: //
1322: // /** Given a general location object provided from the CSS parser,
1323: // * compute the correct file name to use.
1324: // */
1325: // public static String computeFilename(Object location) {
1326: // if (location instanceof String) {
1327: // return (String)location;
1328: // } else if (location instanceof URL) {
1329: // // <markup_separation>
1330: //// return MarkupUnit.fromURL(((URL)location).toExternalForm());
1331: // // ====
1332: // return MarkupService.fromURL(((URL)location).toExternalForm());
1333: // // </markup_separation>
1334: // } else if (location instanceof Element) {
1335: // // Locate the filename for a given element
1336: // Element element = (Element)location;
1337: // element = MarkupService.getCorrespondingSourceElement(element);
1338: //
1339: // // <markup_separation>
1340: //// // XXX I should derive this from the engine instead, after all
1341: //// // the engine can know the unit! (Since engines cannot be used
1342: //// // with multiple DOMs anyway)
1343: //// FileObject fo = unit.getFileObject();
1344: // // ====
1345: // FileObject fo = InSyncService.getProvider().getFileObject(element.getOwnerDocument());
1346: // // </markup_separation>
1347: // File f = FileUtil.toFile(fo);
1348: //
1349: // return f.toString();
1350: // } else if (location != null) {
1351: // return location.toString();
1352: // }
1353: //
1354: // return "";
1355: // }
1356: //
1357: // /**
1358: // * The line number where this CSS value is defined, within the file
1359: // * returned by computeFilename. The first line is number 0.
1360: // */
1361: // public static int computeLineNumber(AbstractValue av) {
1362: // return computeLineNumber(av.getLocation(), av.getLineNumber());
1363: // }
1364: //
1365: // public static int computeLineNumber(Object location, int lineno) {
1366: // if (location instanceof Element) {
1367: // /*
1368: // // The location is an XhtmlElement -- so the line number
1369: // // needs to be relative to it.... compute the line number
1370: // // of the element
1371: // if (lineno == -1)
1372: // lineno = 0;
1373: // Element element = (Element)location;
1374: // RaveDocument doc = (RaveDocument)element.getOwnerDocument();
1375: // lineno += doc.getLineNumber(element);
1376: // */
1377: // if (lineno == -1) {
1378: // lineno = 0;
1379: // }
1380: //
1381: // Element element = (Element)location;
1382: // element = MarkupService.getCorrespondingSourceElement(element);
1383: // // <markup_separation>
1384: //// lineno += unit.computeLine(element);
1385: // // ====
1386: // lineno += InSyncService.getProvider().computeLine(element.getOwnerDocument(), element);
1387: // // </markup_separation>
1388: // }
1389: //
1390: // return lineno;
1391: // }
1392:
1393: /** Get the Link Color to use in this document */
1394: Value getLinkColor() {
1395: // Element body = DesignerService.getDefault().getBody(document);
1396: // Element body = InSyncService.getProvider().getHtmlBodyForMarkupFile(InSyncService.getProvider().getFileObject(document));
1397:
1398: UserAgent userAgent = getUserAgent();
1399: Element body = userAgent == null ? null : userAgent
1400: .getHtmlBodyForDocument(document);
1401: if (body == null) {
1402: // XXX Is it OK?
1403: return new RGBColorValue(new FloatValue(
1404: CSSPrimitiveValue.CSS_NUMBER, 0), new FloatValue(
1405: CSSPrimitiveValue.CSS_NUMBER, 0), new FloatValue(
1406: CSSPrimitiveValue.CSS_NUMBER, 200));
1407: }
1408:
1409: return getComputedStyle((CSSStylableElement) body, null,
1410: XhtmlCss.RAVELINKCOLOR_INDEX);
1411: }
1412:
1413: /** For use by page import */
1414: public boolean mediaMatch(SACMediaList ml) {
1415: return super .mediaMatch(ml);
1416: }
1417:
1418: /**
1419: * Given a Map of style properties, serialize the set and compress
1420: * properties into shorthands, when possible. See styleToMap.
1421: */
1422: public String mapToStyle(Map<String, String> map) {
1423: StyleMap styleMap = new StyleMap(getNumberOfProperties());
1424: StringBuffer unknown = null;
1425: Iterator<String> it = map.keySet().iterator();
1426:
1427: while (it.hasNext()) {
1428: String key = it.next();
1429: String value = map.get(key);
1430:
1431: int index = getXhtmlPropertyIndex(key);
1432:
1433: if (index == -1) {
1434: // Unknown property.... what to do, what to do... remember it
1435: // so we can attach it to the end
1436: if (unknown == null) {
1437: unknown = new StringBuffer(60);
1438: }
1439:
1440: unknown.append(key);
1441: unknown.append(':');
1442: unknown.append(' ');
1443: unknown.append(value);
1444: unknown.append(';');
1445: } else {
1446: styleMap.putValue(index, new MapStringValue(value));
1447: }
1448: }
1449:
1450: String style = toMinimalStyleString(styleMap);
1451:
1452: if (unknown != null) {
1453: if (style.length() > 0) {
1454: style = style + "; " + unknown;
1455: } else {
1456: style = unknown.toString();
1457: }
1458: }
1459:
1460: return style;
1461: }
1462:
1463: /** Parse the given style declaration and return a map of properties
1464: * stored in it. The Map will have String keys which correspond to
1465: * property names, and String values which correspond to CSS
1466: * raw text for the values.
1467: */
1468: public Map<String, String> styleToMap(String style) {
1469: CSSStylableElement old = element;
1470: StyleDeclaration sd = null;
1471: int size = 0;
1472:
1473: try {
1474: // There needs to be a current element for resolving
1475: // urls, for example for a background-image property.
1476: // The URL won't be right, but that's okay for now since
1477: // we won't try to access it.
1478: element = (CSSStylableElement) document
1479: .getDocumentElement();
1480: unknownPropertyNames = new ArrayList<String>();
1481: unknownPropertyValues = new ArrayList<String>();
1482: sd = parseStyleDeclaration(element, style);
1483: size = sd.size();
1484:
1485: if (size == 0) {
1486: return new HashMap<String, String>(0);
1487: }
1488:
1489: // XXX Pick a better data structure that preserves order?
1490: // Yeah, we don't really need to Hash aspect here; we're mostly
1491: // iterating!
1492: // LinkedHashMap map = new LinkedHashMap(2 * size);
1493: Map<String, String> map = new LinkedHashMap<String, String>(
1494: 2 * size);
1495:
1496: for (int j = 0, m = size; j < m; j++) {
1497: int idx = sd.getIndex(j);
1498: String key = getPropertyName(idx);
1499: Value value = sd.getValue(j);
1500:
1501: if (value != null) {
1502: map.put(key, value.getCssText());
1503: }
1504: }
1505:
1506: // Add unknown properties
1507: for (int i = 0, n = unknownPropertyNames.size(); i < n; i++) {
1508: map.put((String) unknownPropertyNames.get(i),
1509: (String) unknownPropertyValues.get(i));
1510: }
1511:
1512: return map;
1513: } finally {
1514: unknownPropertyNames = null;
1515: unknownPropertyValues = null;
1516: element = old;
1517: }
1518: }
1519:
1520: /**
1521: * Returns the property index, or -1.
1522: */
1523: public static int getXhtmlPropertyIndex(String name) {
1524: return valueManagerIndex.get(name);
1525: }
1526:
1527: /**
1528: * Returns the shorthand property index, or -1.
1529: */
1530: public static int getXhtmlShorthandIndex(String name) {
1531: return shorthandManagerIndex.get(name);
1532: }
1533:
1534: protected void findStyleSheetNodes() {
1535: // <removing set/getRoot from RaveDocument>
1536: // Node root = ((RaveDocument)document).getRoot();
1537: // findStyleSheetNodes(root);
1538: // ====
1539: // XXX FIXME Here we need to work with the rendered HTML DOM directly,
1540: // there is no interest in the original JSP DOM.
1541: // FileObject markupFile = InSyncService.getProvider().getFileObject(document);
1542: // if (markupFile != null) {
1543: // DocumentFragment df = InSyncService.getProvider().getHtmlDomFragmentForMarkupFile(markupFile);
1544: // DocumentFragment df = InSyncService.getProvider().getHtmlDomFragmentForDocument(document);
1545: UserAgent userAgent = getUserAgent();
1546: DocumentFragment df = userAgent == null ? null : userAgent
1547: .getHtmlDomFragmentForDocument(document);
1548: if (df != null) {
1549: findStyleSheetNodes(df);
1550: return;
1551: }
1552: // }
1553:
1554: // XXX Log problem?
1555: super .findStyleSheetNodes();
1556: // <removing set/getRoot from RaveDocument>
1557: }
1558:
1559: // Moved to service impl.
1560: // /**
1561: // * Return true iff the given CSS property for the given element
1562: // * is set by an inline property setting
1563: // */
1564: // public static boolean isInlineValue(CSSStylableElement elt, int propidx) {
1565: // String pseudo = ""; // Pending
1566: // StyleMap sm = elt.getComputedStyleMap(pseudo);
1567: //
1568: // if (sm == null) {
1569: // return false;
1570: // }
1571: //
1572: // Value value = sm.getValue(propidx);
1573: //
1574: // if (value == null) {
1575: // return false;
1576: // }
1577: //
1578: // return sm.getOrigin(propidx) == StyleMap.INLINE_AUTHOR_ORIGIN;
1579: // }
1580:
1581: /**
1582: * Try to create a minimal (as short as possible) style string
1583: * from this map, by using shorthands when possible.
1584: * This will for example compress border-color-left/right/bottom/top
1585: * into border-color: one two three four, and so on.
1586: * @todo Handle the font shorthand property.
1587: */
1588: protected String toMinimalStyleString(StyleMap map) {
1589: int size = map.getSize(true);
1590: boolean[] used = new boolean[size];
1591: StringBuffer sb = new StringBuffer();
1592:
1593: // TODO how does isImportant on an individual property get translated
1594: // into a shorthand??? Try the reverse!
1595: // First see if I can use border. That's true when
1596: // all of
1597: boolean allBorderWidths = (map
1598: .getValue(XhtmlCss.BORDER_LEFT_WIDTH_INDEX) != null)
1599: && (map.getValue(XhtmlCss.BORDER_TOP_WIDTH_INDEX) != null)
1600: && (map.getValue(XhtmlCss.BORDER_RIGHT_WIDTH_INDEX) != null)
1601: && (map.getValue(XhtmlCss.BORDER_BOTTOM_WIDTH_INDEX) != null);
1602: boolean sameBorderWidth = allBorderWidths
1603: && map
1604: .getValue(XhtmlCss.BORDER_LEFT_WIDTH_INDEX)
1605: .equals(
1606: map
1607: .getValue(XhtmlCss.BORDER_TOP_WIDTH_INDEX))
1608: && map
1609: .getValue(XhtmlCss.BORDER_TOP_WIDTH_INDEX)
1610: .equals(
1611: map
1612: .getValue(XhtmlCss.BORDER_RIGHT_WIDTH_INDEX))
1613: && map
1614: .getValue(XhtmlCss.BORDER_RIGHT_WIDTH_INDEX)
1615: .equals(
1616: map
1617: .getValue(XhtmlCss.BORDER_BOTTOM_WIDTH_INDEX));
1618: boolean allBorderColors = (map
1619: .getValue(XhtmlCss.BORDER_LEFT_COLOR_INDEX) != null)
1620: && (map.getValue(XhtmlCss.BORDER_TOP_COLOR_INDEX) != null)
1621: && (map.getValue(XhtmlCss.BORDER_RIGHT_COLOR_INDEX) != null)
1622: && (map.getValue(XhtmlCss.BORDER_BOTTOM_COLOR_INDEX) != null);
1623: boolean sameBorderColor = allBorderColors
1624: && map
1625: .getValue(XhtmlCss.BORDER_LEFT_COLOR_INDEX)
1626: .equals(
1627: map
1628: .getValue(XhtmlCss.BORDER_TOP_COLOR_INDEX))
1629: && map
1630: .getValue(XhtmlCss.BORDER_TOP_COLOR_INDEX)
1631: .equals(
1632: map
1633: .getValue(XhtmlCss.BORDER_RIGHT_COLOR_INDEX))
1634: && map
1635: .getValue(XhtmlCss.BORDER_RIGHT_COLOR_INDEX)
1636: .equals(
1637: map
1638: .getValue(XhtmlCss.BORDER_BOTTOM_COLOR_INDEX));
1639: boolean allBorderStyles = (map
1640: .getValue(XhtmlCss.BORDER_LEFT_STYLE_INDEX) != null)
1641: && (map.getValue(XhtmlCss.BORDER_TOP_STYLE_INDEX) != null)
1642: && (map.getValue(XhtmlCss.BORDER_RIGHT_STYLE_INDEX) != null)
1643: && (map.getValue(XhtmlCss.BORDER_BOTTOM_STYLE_INDEX) != null);
1644: boolean sameBorderStyle = allBorderStyles
1645: && map
1646: .getValue(XhtmlCss.BORDER_LEFT_STYLE_INDEX)
1647: .equals(
1648: map
1649: .getValue(XhtmlCss.BORDER_TOP_STYLE_INDEX))
1650: && map
1651: .getValue(XhtmlCss.BORDER_TOP_STYLE_INDEX)
1652: .equals(
1653: map
1654: .getValue(XhtmlCss.BORDER_RIGHT_STYLE_INDEX))
1655: && map
1656: .getValue(XhtmlCss.BORDER_RIGHT_STYLE_INDEX)
1657: .equals(
1658: map
1659: .getValue(XhtmlCss.BORDER_BOTTOM_STYLE_INDEX));
1660:
1661: if (sameBorderStyle && sameBorderWidth && sameBorderColor) {
1662: sb.append(CssConstants.CSS_BORDER_PROPERTY);
1663: sb.append(": "); // NOI18N
1664: sb.append(map.getValue(XhtmlCss.BORDER_LEFT_WIDTH_INDEX));
1665: sb.append(" "); // NOI18N
1666: sb.append(map.getValue(XhtmlCss.BORDER_LEFT_STYLE_INDEX));
1667: sb.append(" "); // NOI18N
1668: sb.append(map.getValue(XhtmlCss.BORDER_LEFT_COLOR_INDEX));
1669: used[XhtmlCss.BORDER_LEFT_WIDTH_INDEX] = true;
1670: used[XhtmlCss.BORDER_TOP_WIDTH_INDEX] = true;
1671: used[XhtmlCss.BORDER_RIGHT_WIDTH_INDEX] = true;
1672: used[XhtmlCss.BORDER_BOTTOM_WIDTH_INDEX] = true;
1673: used[XhtmlCss.BORDER_LEFT_COLOR_INDEX] = true;
1674: used[XhtmlCss.BORDER_TOP_COLOR_INDEX] = true;
1675: used[XhtmlCss.BORDER_RIGHT_COLOR_INDEX] = true;
1676: used[XhtmlCss.BORDER_BOTTOM_COLOR_INDEX] = true;
1677: used[XhtmlCss.BORDER_LEFT_STYLE_INDEX] = true;
1678: used[XhtmlCss.BORDER_TOP_STYLE_INDEX] = true;
1679: used[XhtmlCss.BORDER_RIGHT_STYLE_INDEX] = true;
1680: used[XhtmlCss.BORDER_BOTTOM_STYLE_INDEX] = true;
1681: sb.append("; ");
1682: } else {
1683: if (allBorderWidths) {
1684: sb.append(CssConstants.CSS_BORDER_WIDTH_PROPERTY);
1685: sb.append(": "); // NOI18N
1686:
1687: if (sameBorderWidth) {
1688: sb
1689: .append(map
1690: .getValue(XhtmlCss.BORDER_LEFT_WIDTH_INDEX));
1691: } else {
1692: sb.append(map
1693: .getValue(XhtmlCss.BORDER_TOP_WIDTH_INDEX));
1694: sb.append(" "); // NOI18N
1695: sb
1696: .append(map
1697: .getValue(XhtmlCss.BORDER_RIGHT_WIDTH_INDEX));
1698: sb.append(" "); // NOI18N
1699: sb
1700: .append(map
1701: .getValue(XhtmlCss.BORDER_BOTTOM_WIDTH_INDEX));
1702: sb.append(" "); // NOI18N
1703: sb
1704: .append(map
1705: .getValue(XhtmlCss.BORDER_LEFT_WIDTH_INDEX));
1706: }
1707:
1708: used[XhtmlCss.BORDER_LEFT_WIDTH_INDEX] = true;
1709: used[XhtmlCss.BORDER_TOP_WIDTH_INDEX] = true;
1710: used[XhtmlCss.BORDER_RIGHT_WIDTH_INDEX] = true;
1711: used[XhtmlCss.BORDER_BOTTOM_WIDTH_INDEX] = true;
1712: sb.append("; ");
1713: }
1714:
1715: if (allBorderStyles) {
1716: sb.append(CssConstants.CSS_BORDER_STYLE_PROPERTY);
1717: sb.append(": "); // NOI18N
1718:
1719: if (sameBorderStyle) {
1720: sb
1721: .append(map
1722: .getValue(XhtmlCss.BORDER_LEFT_STYLE_INDEX));
1723: } else {
1724: sb.append(map
1725: .getValue(XhtmlCss.BORDER_TOP_STYLE_INDEX));
1726: sb.append(" "); // NOI18N
1727: sb
1728: .append(map
1729: .getValue(XhtmlCss.BORDER_RIGHT_STYLE_INDEX));
1730: sb.append(" "); // NOI18N
1731: sb
1732: .append(map
1733: .getValue(XhtmlCss.BORDER_BOTTOM_STYLE_INDEX));
1734: sb.append(" "); // NOI18N
1735: sb
1736: .append(map
1737: .getValue(XhtmlCss.BORDER_LEFT_STYLE_INDEX));
1738: }
1739:
1740: used[XhtmlCss.BORDER_LEFT_STYLE_INDEX] = true;
1741: used[XhtmlCss.BORDER_TOP_STYLE_INDEX] = true;
1742: used[XhtmlCss.BORDER_RIGHT_STYLE_INDEX] = true;
1743: used[XhtmlCss.BORDER_BOTTOM_STYLE_INDEX] = true;
1744: sb.append("; ");
1745: }
1746:
1747: if (allBorderColors) {
1748: sb.append(CssConstants.CSS_BORDER_COLOR_PROPERTY);
1749: sb.append(": "); // NOI18N
1750:
1751: if (sameBorderColor) {
1752: sb
1753: .append(map
1754: .getValue(XhtmlCss.BORDER_LEFT_COLOR_INDEX));
1755: } else {
1756: sb.append(map
1757: .getValue(XhtmlCss.BORDER_TOP_COLOR_INDEX));
1758: sb.append(" "); // NOI18N
1759: sb
1760: .append(map
1761: .getValue(XhtmlCss.BORDER_RIGHT_COLOR_INDEX));
1762: sb.append(" "); // NOI18N
1763: sb
1764: .append(map
1765: .getValue(XhtmlCss.BORDER_BOTTOM_COLOR_INDEX));
1766: sb.append(" "); // NOI18N
1767: sb
1768: .append(map
1769: .getValue(XhtmlCss.BORDER_LEFT_COLOR_INDEX));
1770: }
1771:
1772: used[XhtmlCss.BORDER_LEFT_COLOR_INDEX] = true;
1773: used[XhtmlCss.BORDER_TOP_COLOR_INDEX] = true;
1774: used[XhtmlCss.BORDER_RIGHT_COLOR_INDEX] = true;
1775: used[XhtmlCss.BORDER_BOTTOM_COLOR_INDEX] = true;
1776: sb.append("; ");
1777: }
1778:
1779: // TODO - do something about border-left, border-right, etc.?
1780: boolean allBorderTop = !used[XhtmlCss.BORDER_TOP_STYLE_INDEX]
1781: && !used[XhtmlCss.BORDER_TOP_COLOR_INDEX]
1782: && !used[XhtmlCss.BORDER_TOP_WIDTH_INDEX]
1783: && (map.getValue(XhtmlCss.BORDER_TOP_STYLE_INDEX) != null)
1784: && (map.getValue(XhtmlCss.BORDER_TOP_COLOR_INDEX) != null)
1785: && (map.getValue(XhtmlCss.BORDER_TOP_WIDTH_INDEX) != null);
1786:
1787: if (allBorderTop) {
1788: sb.append(CssConstants.CSS_BORDER_TOP_PROPERTY);
1789: sb.append(": "); // NOI18N
1790: sb
1791: .append(map
1792: .getValue(XhtmlCss.BORDER_TOP_STYLE_INDEX));
1793: sb.append(" "); // NOI18N
1794: sb
1795: .append(map
1796: .getValue(XhtmlCss.BORDER_TOP_COLOR_INDEX));
1797: sb.append(" "); // NOI18N
1798: sb
1799: .append(map
1800: .getValue(XhtmlCss.BORDER_TOP_WIDTH_INDEX));
1801: sb.append("; ");
1802: used[XhtmlCss.BORDER_TOP_STYLE_INDEX] = true;
1803: used[XhtmlCss.BORDER_TOP_COLOR_INDEX] = true;
1804: used[XhtmlCss.BORDER_TOP_WIDTH_INDEX] = true;
1805: }
1806:
1807: boolean allBorderRight = !used[XhtmlCss.BORDER_RIGHT_STYLE_INDEX]
1808: && !used[XhtmlCss.BORDER_RIGHT_COLOR_INDEX]
1809: && !used[XhtmlCss.BORDER_RIGHT_WIDTH_INDEX]
1810: && (map.getValue(XhtmlCss.BORDER_RIGHT_STYLE_INDEX) != null)
1811: && (map.getValue(XhtmlCss.BORDER_RIGHT_COLOR_INDEX) != null)
1812: && (map.getValue(XhtmlCss.BORDER_RIGHT_WIDTH_INDEX) != null);
1813:
1814: if (allBorderRight) {
1815: sb.append(CssConstants.CSS_BORDER_RIGHT_PROPERTY);
1816: sb.append(": "); // NOI18N
1817: sb.append(map
1818: .getValue(XhtmlCss.BORDER_RIGHT_STYLE_INDEX));
1819: sb.append(" "); // NOI18N
1820: sb.append(map
1821: .getValue(XhtmlCss.BORDER_RIGHT_COLOR_INDEX));
1822: sb.append(" "); // NOI18N
1823: sb.append(map
1824: .getValue(XhtmlCss.BORDER_RIGHT_WIDTH_INDEX));
1825: sb.append("; ");
1826: used[XhtmlCss.BORDER_RIGHT_STYLE_INDEX] = true;
1827: used[XhtmlCss.BORDER_RIGHT_COLOR_INDEX] = true;
1828: used[XhtmlCss.BORDER_RIGHT_WIDTH_INDEX] = true;
1829: }
1830:
1831: boolean allBorderBottom = !used[XhtmlCss.BORDER_BOTTOM_STYLE_INDEX]
1832: && !used[XhtmlCss.BORDER_BOTTOM_COLOR_INDEX]
1833: && !used[XhtmlCss.BORDER_BOTTOM_WIDTH_INDEX]
1834: && (map
1835: .getValue(XhtmlCss.BORDER_BOTTOM_STYLE_INDEX) != null)
1836: && (map
1837: .getValue(XhtmlCss.BORDER_BOTTOM_COLOR_INDEX) != null)
1838: && (map
1839: .getValue(XhtmlCss.BORDER_BOTTOM_WIDTH_INDEX) != null);
1840:
1841: if (allBorderBottom) {
1842: sb.append(CssConstants.CSS_BORDER_BOTTOM_PROPERTY);
1843: sb.append(": "); // NOI18N
1844: sb.append(map
1845: .getValue(XhtmlCss.BORDER_BOTTOM_STYLE_INDEX));
1846: sb.append(" "); // NOI18N
1847: sb.append(map
1848: .getValue(XhtmlCss.BORDER_BOTTOM_COLOR_INDEX));
1849: sb.append(" "); // NOI18N
1850: sb.append(map
1851: .getValue(XhtmlCss.BORDER_BOTTOM_WIDTH_INDEX));
1852: sb.append("; ");
1853: used[XhtmlCss.BORDER_BOTTOM_STYLE_INDEX] = true;
1854: used[XhtmlCss.BORDER_BOTTOM_COLOR_INDEX] = true;
1855: used[XhtmlCss.BORDER_BOTTOM_WIDTH_INDEX] = true;
1856: }
1857:
1858: boolean allBorderLeft = !used[XhtmlCss.BORDER_LEFT_STYLE_INDEX]
1859: && !used[XhtmlCss.BORDER_LEFT_COLOR_INDEX]
1860: && !used[XhtmlCss.BORDER_LEFT_WIDTH_INDEX]
1861: && (map.getValue(XhtmlCss.BORDER_LEFT_STYLE_INDEX) != null)
1862: && (map.getValue(XhtmlCss.BORDER_LEFT_COLOR_INDEX) != null)
1863: && (map.getValue(XhtmlCss.BORDER_LEFT_WIDTH_INDEX) != null);
1864:
1865: if (allBorderLeft) {
1866: sb.append(CssConstants.CSS_BORDER_LEFT_PROPERTY);
1867: sb.append(": "); // NOI18N
1868: sb.append(map
1869: .getValue(XhtmlCss.BORDER_LEFT_STYLE_INDEX));
1870: sb.append(" "); // NOI18N
1871: sb.append(map
1872: .getValue(XhtmlCss.BORDER_LEFT_COLOR_INDEX));
1873: sb.append(" "); // NOI18N
1874: sb.append(map
1875: .getValue(XhtmlCss.BORDER_LEFT_WIDTH_INDEX));
1876: sb.append("; ");
1877: used[XhtmlCss.BORDER_LEFT_STYLE_INDEX] = true;
1878: used[XhtmlCss.BORDER_LEFT_COLOR_INDEX] = true;
1879: used[XhtmlCss.BORDER_LEFT_WIDTH_INDEX] = true;
1880: }
1881: }
1882:
1883: // Look for collapsible margins
1884: boolean allMargins = (map.getValue(XhtmlCss.MARGIN_TOP_INDEX) != null)
1885: && (map.getValue(XhtmlCss.MARGIN_RIGHT_INDEX) != null)
1886: && (map.getValue(XhtmlCss.MARGIN_BOTTOM_INDEX) != null)
1887: && (map.getValue(XhtmlCss.MARGIN_LEFT_INDEX) != null);
1888:
1889: if (allMargins) {
1890: sb.append(CssConstants.CSS_MARGIN_PROPERTY);
1891: sb.append(": "); // NOI18N
1892:
1893: // TODO - compress to two or one values if sides are the same
1894: sb.append(map.getValue(XhtmlCss.MARGIN_TOP_INDEX));
1895:
1896: if (map.getValue(XhtmlCss.MARGIN_TOP_INDEX).equals(
1897: map.getValue(XhtmlCss.MARGIN_RIGHT_INDEX))
1898: && map.getValue(XhtmlCss.MARGIN_TOP_INDEX).equals(
1899: map.getValue(XhtmlCss.MARGIN_BOTTOM_INDEX))
1900: && map.getValue(XhtmlCss.MARGIN_TOP_INDEX).equals(
1901: map.getValue(XhtmlCss.MARGIN_LEFT_INDEX))) {
1902: // Use a single value to represent all four sides.
1903: // I could also look to see if the horizontal and vertical sides are identical
1904: // and compress to two values but I'm getting bored
1905: } else {
1906: sb.append(" "); // NOI18N
1907: sb.append(map.getValue(XhtmlCss.MARGIN_RIGHT_INDEX));
1908: sb.append(" "); // NOI18N
1909: sb.append(map.getValue(XhtmlCss.MARGIN_BOTTOM_INDEX));
1910: sb.append(" "); // NOI18N
1911: sb.append(map.getValue(XhtmlCss.MARGIN_LEFT_INDEX));
1912: }
1913:
1914: sb.append("; ");
1915: used[XhtmlCss.MARGIN_TOP_INDEX] = true;
1916: used[XhtmlCss.MARGIN_RIGHT_INDEX] = true;
1917: used[XhtmlCss.MARGIN_BOTTOM_INDEX] = true;
1918: used[XhtmlCss.MARGIN_LEFT_INDEX] = true;
1919: }
1920:
1921: // Look for collapsible padding
1922: boolean allPadding = (map.getValue(XhtmlCss.PADDING_TOP_INDEX) != null)
1923: && (map.getValue(XhtmlCss.PADDING_RIGHT_INDEX) != null)
1924: && (map.getValue(XhtmlCss.PADDING_BOTTOM_INDEX) != null)
1925: && (map.getValue(XhtmlCss.PADDING_LEFT_INDEX) != null);
1926:
1927: if (allPadding) {
1928: sb.append(CssConstants.CSS_PADDING_PROPERTY);
1929: sb.append(": "); // NOI18N
1930:
1931: // TODO - compress to two or one values if sides are the same
1932: sb.append(map.getValue(XhtmlCss.PADDING_TOP_INDEX));
1933:
1934: if (map.getValue(XhtmlCss.PADDING_TOP_INDEX).equals(
1935: map.getValue(XhtmlCss.PADDING_RIGHT_INDEX))
1936: && map
1937: .getValue(XhtmlCss.PADDING_TOP_INDEX)
1938: .equals(
1939: map
1940: .getValue(XhtmlCss.PADDING_BOTTOM_INDEX))
1941: && map.getValue(XhtmlCss.PADDING_TOP_INDEX).equals(
1942: map.getValue(XhtmlCss.PADDING_LEFT_INDEX))) {
1943: // Use a single value to represent all four sides.
1944: // I could also look to see if the horizontal and vertical sides are identical
1945: // and compress to two values but I'm getting bored
1946: } else {
1947: sb.append(" "); // NOI18N
1948: sb.append(map.getValue(XhtmlCss.PADDING_RIGHT_INDEX));
1949: sb.append(" "); // NOI18N
1950: sb.append(map.getValue(XhtmlCss.PADDING_BOTTOM_INDEX));
1951: sb.append(" "); // NOI18N
1952: sb.append(map.getValue(XhtmlCss.PADDING_LEFT_INDEX));
1953: }
1954:
1955: sb.append("; ");
1956: used[XhtmlCss.PADDING_TOP_INDEX] = true;
1957: used[XhtmlCss.PADDING_RIGHT_INDEX] = true;
1958: used[XhtmlCss.PADDING_BOTTOM_INDEX] = true;
1959: used[XhtmlCss.PADDING_LEFT_INDEX] = true;
1960: }
1961:
1962: // Background
1963: boolean allBackground = (map
1964: .getValue(XhtmlCss.BACKGROUND_COLOR_INDEX) != null)
1965: && // OR: same as default
1966: (map.getValue(XhtmlCss.BACKGROUND_IMAGE_INDEX) != null)
1967: && (map.getValue(XhtmlCss.BACKGROUND_POSITION_INDEX) != null)
1968: &&
1969: // TODO: (map.getValue(XhtmlCss.BACKGROUND_ATTACHMENT_INDEX) != null) &&
1970: (map.getValue(XhtmlCss.BACKGROUND_REPEAT_INDEX) != null);
1971:
1972: if (allBackground) {
1973: sb.append(CssConstants.CSS_BACKGROUND_PROPERTY);
1974: sb.append(": "); // NOI18N
1975:
1976: // TODO -- only do if different from the default!
1977: sb.append(map.getValue(XhtmlCss.BACKGROUND_COLOR_INDEX));
1978: sb.append(" "); // NOI18N
1979: sb.append(map.getValue(XhtmlCss.BACKGROUND_IMAGE_INDEX));
1980: sb.append(" "); // NOI18N
1981: sb.append(map.getValue(XhtmlCss.BACKGROUND_REPEAT_INDEX));
1982:
1983: //sb.append(" "); // NOI18N
1984: //sb.append(map.getValue(XhtmlCss.BACKGROUND_ATTACHMENT_INDEX));
1985: sb.append(" "); // NOI18N
1986: sb.append(map.getValue(XhtmlCss.BACKGROUND_POSITION_INDEX));
1987: sb.append("; ");
1988: used[XhtmlCss.BACKGROUND_COLOR_INDEX] = true;
1989: used[XhtmlCss.BACKGROUND_IMAGE_INDEX] = true;
1990: used[XhtmlCss.BACKGROUND_POSITION_INDEX] = true;
1991: used[XhtmlCss.BACKGROUND_REPEAT_INDEX] = true;
1992:
1993: //used[XhtmlCss.BACKGROUND_ATTACHMENT_INDEX] = true;
1994: }
1995:
1996: // List Style
1997: boolean allListStyles = (map
1998: .getValue(XhtmlCss.LIST_STYLE_TYPE_INDEX) != null)
1999: &&
2000: //(map.getValue(XhtmlCss.LIST_STYLE_POSITION_INDEX) != null) &&
2001: (map.getValue(XhtmlCss.LIST_STYLE_IMAGE_INDEX) != null);
2002:
2003: if (allListStyles) {
2004: sb.append(CssConstants.CSS_LIST_STYLE_PROPERTY);
2005: sb.append(": "); // NOI18N
2006: sb.append(map.getValue(XhtmlCss.LIST_STYLE_TYPE_INDEX));
2007: sb.append(" "); // NOI18N
2008: sb.append(map.getValue(XhtmlCss.LIST_STYLE_IMAGE_INDEX));
2009:
2010: //sb.append(" "); // NOI18N
2011: //sb.append(map.getValue(XhtmlCss.LIST_STYLE_POSITION_INDEX));
2012: sb.append("; ");
2013: used[XhtmlCss.LIST_STYLE_TYPE_INDEX] = true;
2014: used[XhtmlCss.LIST_STYLE_IMAGE_INDEX] = true;
2015:
2016: //used[XhtmlCss.LIST_STYLE_POSITION_INDEX] = true;
2017: }
2018:
2019: // TODO: font
2020: // Write out all items in the map we haven't already covered by a shorthand property
2021: boolean first = true;
2022:
2023: for (int i = 0; i < size; i++) {
2024: if (used[i]) {
2025: // Already processed as a shorthand
2026: continue;
2027: }
2028:
2029: Value v = map.getValue(i);
2030:
2031: if (v != null) {
2032: if (first) {
2033: first = false;
2034: } else {
2035: sb.append("; ");
2036: }
2037:
2038: sb.append(getPropertyName(i));
2039: sb.append(": ");
2040: sb.append(v);
2041:
2042: if (map.isImportant(i)) {
2043: sb.append(" !important");
2044: }
2045: }
2046: }
2047:
2048: return sb.toString();
2049: }
2050:
2051: String computeFileName(Object location) {
2052: UserAgent userAgent = getUserAgent();
2053: if (userAgent == null) {
2054: return location == null ? null : location.toString();
2055: }
2056:
2057: return userAgent.computeFileName(location);
2058: }
2059:
2060: int computeLineNumber(Object location, int lineno) {
2061: UserAgent userAgent = getUserAgent();
2062: if (userAgent == null) {
2063: return lineno;
2064: }
2065:
2066: return userAgent.computeLineNumber(location, lineno);
2067: }
2068:
2069: URL getDocumentUrl() {
2070: UserAgent userAgent = getUserAgent();
2071: if (userAgent == null) {
2072: return null;
2073: }
2074:
2075: return userAgent.getDocumentUrl(getDocument());
2076: }
2077:
2078: private UserAgent getUserAgent() {
2079: CSSEngineUserAgent cssEngineUserAgent = getCSSEngineUserAgent();
2080: if (cssEngineUserAgent instanceof UserAgent) {
2081: return (UserAgent) cssEngineUserAgent;
2082: }
2083: return null;
2084: }
2085:
2086: /** Class used to wrap serialized Strings as batik values and get
2087: * the right behavior out of the style map serialization routines
2088: * without having the Strings modified in anyway. These Strings
2089: * aren't necessarily Strings in the style - the may represent
2090: * floats, urls, etc.
2091: */
2092: class MapStringValue extends AbstractValue {
2093: private String s;
2094:
2095: MapStringValue(String s) {
2096: this .s = s;
2097: }
2098:
2099: public String getCssText() {
2100: return s;
2101: }
2102:
2103: public boolean equals(Object obj) {
2104: if (obj instanceof MapStringValue) {
2105: return s.equals(((MapStringValue) obj).s);
2106: }
2107:
2108: return false;
2109: }
2110:
2111: public String toString() {
2112: return s;
2113: }
2114: }
2115:
2116: }
|