Added: incubator/harmony/enhanced/classlib/trunk/modules/swing/src/main/java/common/javax/swing/text/html/CSS.java
URL: http://svn.apache.org/viewvc/incubator/harmony/enhanced/classlib/trunk/modules/swing/src/main/java/common/javax/swing/text/html/CSS.java?rev=427121&view=auto
==============================================================================
--- incubator/harmony/enhanced/classlib/trunk/modules/swing/src/main/java/common/javax/swing/text/html/CSS.java (added)
+++ incubator/harmony/enhanced/classlib/trunk/modules/swing/src/main/java/common/javax/swing/text/html/CSS.java Mon Jul 31 07:08:47 2006
@@ -0,0 +1,3179 @@
+/*
+ * Copyright 2005 - 2006 The Apache Software Software Foundation or its licensors, as applicable.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * @author Alexey A. Ivanov
+ * @version $Revision$
+ */
+package javax.swing.text.html;
+
+import java.awt.Color;
+import java.awt.GraphicsEnvironment;
+import java.awt.Image;
+import java.io.Serializable;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.swing.text.AttributeSet;
+import javax.swing.text.BoxView;
+import javax.swing.text.MutableAttributeSet;
+import javax.swing.text.StyleConstants;
+import javax.swing.text.StyleContext;
+import javax.swing.text.View;
+
+import org.apache.harmony.x.swing.Utilities;
+
+public class CSS implements Serializable {
+ public static final class Attribute {
+ public static final Attribute BACKGROUND =
+ new Attribute("background", null, false,
+ new BackgroundExpander());
+ public static final Attribute BACKGROUND_ATTACHMENT =
+ new Attribute("background-attachment", "scroll", false,
+ BackgroundAttachment.factory);
+ public static final Attribute BACKGROUND_COLOR =
+ new Attribute("background-color", "transparent", false,
+ BackgroundColor.factory);
+ public static final Attribute BACKGROUND_IMAGE =
+ new Attribute("background-image", "none", false,
+ ImageValue.NONE);
+ public static final Attribute BACKGROUND_POSITION =
+ new Attribute("background-position", null, false,
+ new BackgroundPosition());
+ public static final Attribute BACKGROUND_REPEAT =
+ new Attribute("background-repeat", "repeat", false,
+ BackgroundRepeat.factory);
+ public static final Attribute BORDER =
+ new Attribute("border", null, false,
+ new BorderExpander());
+ public static final Attribute BORDER_BOTTOM_WIDTH =
+ new Attribute("border-bottom-width", "medium", false,
+ BorderWidthValue.factory);
+ public static final Attribute BORDER_BOTTOM =
+ new Attribute("border-bottom", null, false,
+ new BorderSideExpander(
+ BorderSideExpander.BOTTOM_SIDE,
+ BORDER_BOTTOM_WIDTH));
+ public static final Attribute BORDER_COLOR =
+ new Attribute("border-color", null, false, new BorderColor());
+ public static final Attribute BORDER_LEFT_WIDTH =
+ new Attribute("border-left-width", "medium", false,
+ BorderWidthValue.factory);
+ public static final Attribute BORDER_LEFT =
+ new Attribute("border-left", null, false,
+ new BorderSideExpander(
+ BorderSideExpander.LEFT_SIDE,
+ BORDER_LEFT_WIDTH));
+ public static final Attribute BORDER_RIGHT_WIDTH =
+ new Attribute("border-right-width", "medium", false,
+ BorderWidthValue.factory);
+ public static final Attribute BORDER_RIGHT =
+ new Attribute("border-right", null, false,
+ new BorderSideExpander(
+ BorderSideExpander.RIGHT_SIDE,
+ BORDER_RIGHT_WIDTH));
+ public static final Attribute BORDER_STYLE =
+ new Attribute("border-style", "none", false, new BorderStyle());
+ public static final Attribute BORDER_TOP_WIDTH =
+ new Attribute("border-top-width", "medium", false,
+ BorderWidthValue.factory);
+ public static final Attribute BORDER_TOP =
+ new Attribute("border-top", null, false,
+ new BorderSideExpander(
+ BorderSideExpander.TOP_SIDE,
+ BORDER_TOP_WIDTH));
+ public static final Attribute BORDER_WIDTH =
+ new Attribute("border-width", "medium", false,
+ new SpaceExpander(BORDER_TOP_WIDTH,
+ BORDER_RIGHT_WIDTH,
+ BORDER_BOTTOM_WIDTH,
+ BORDER_LEFT_WIDTH,
+ BorderWidthValue.factory));
+ public static final Attribute CLEAR =
+ new Attribute("clear", "none", false, new Clear());
+ public static final Attribute COLOR =
+ new Attribute("color", null, true, ColorProperty.factory);
+ public static final Attribute DISPLAY =
+ new Attribute("display", "block", false, new Display());
+ public static final Attribute FLOAT =
+ new Attribute("float", "none", false, new FloatProperty());
+ public static final Attribute FONT =
+ new Attribute("font", null, true, new FontExpander());
+ public static final Attribute FONT_FAMILY =
+ new Attribute("font-family", null, true, new FontFamily());
+ public static final Attribute FONT_SIZE =
+ new Attribute("font-size", "medium", true, new FontSize());
+ public static final Attribute FONT_STYLE =
+ new Attribute("font-style", "normal", true, new FontStyle());
+ public static final Attribute FONT_VARIANT =
+ new Attribute("font-variant", "normal", true,
+ new FontVariant());
+ public static final Attribute FONT_WEIGHT =
+ new Attribute("font-weight", "normal", true, new FontWeight());
+ public static final Attribute HEIGHT =
+ new Attribute("height", "auto", false, new Height());
+ public static final Attribute LETTER_SPACING =
+ new Attribute("letter-spacing", "normal", true,
+ SpacingValue.factory);
+ public static final Attribute LINE_HEIGHT =
+ new Attribute("line-height", "normal", true,
+ LineHeight.normal);
+ public static final Attribute LIST_STYLE =
+ new Attribute("list-style", null, true,
+ new ListStyleExpander());
+ public static final Attribute LIST_STYLE_IMAGE =
+ new Attribute("list-style-image", "none", true,
+ ImageValue.NONE);
+ public static final Attribute LIST_STYLE_POSITION =
+ new Attribute("list-style-position", "outside", true,
+ ListStylePosition.factory);
+ public static final Attribute LIST_STYLE_TYPE =
+ new Attribute("list-style-type", "disc", true,
+ ListStyleType.factory);
+ public static final Attribute MARGIN_BOTTOM =
+ new Attribute("margin-bottom", "0", false, FloatValue.factory);
+ public static final Attribute MARGIN_LEFT =
+ new Attribute("margin-left", "0", false, FloatValue.factory);
+ public static final Attribute MARGIN_RIGHT =
+ new Attribute("margin-right", "0", false, FloatValue.factory);
+ public static final Attribute MARGIN_TOP =
+ new Attribute("margin-top", "0", false, FloatValue.factory);
+ public static final Attribute MARGIN =
+ new Attribute("margin", null, false,
+ new SpaceExpander(MARGIN_TOP, MARGIN_RIGHT,
+ MARGIN_BOTTOM, MARGIN_LEFT));
+ public static final Attribute PADDING_BOTTOM =
+ new Attribute("padding-bottom", "0", false, FloatValue.factory);
+ public static final Attribute PADDING_LEFT =
+ new Attribute("padding-left", "0", false, FloatValue.factory);
+ public static final Attribute PADDING_RIGHT =
+ new Attribute("padding-right", "0", false, FloatValue.factory);
+ public static final Attribute PADDING_TOP =
+ new Attribute("padding-top", "0", false, FloatValue.factory);
+ public static final Attribute PADDING =
+ new Attribute("padding", null, false,
+ new SpaceExpander(PADDING_TOP, PADDING_RIGHT,
+ PADDING_BOTTOM, PADDING_LEFT));
+ public static final Attribute TEXT_ALIGN =
+ new Attribute("text-align", null, true, new TextAlign());
+ public static final Attribute TEXT_DECORATION =
+ new Attribute("text-decoration", "none", true,
+ new TextDecoration());
+ public static final Attribute TEXT_INDENT =
+ new Attribute("text-indent", "0", true, FloatValue.factory);
+ public static final Attribute TEXT_TRANSFORM =
+ new Attribute("text-transform", "none", true,
+ new TextTransform());
+ public static final Attribute VERTICAL_ALIGN =
+ new Attribute("vertical-align", "baseline", false,
+ new VerticalAlign());
+ public static final Attribute WHITE_SPACE =
+ new Attribute("white-space", "normal", true,
+ WhiteSpace.factory);
+ public static final Attribute WIDTH =
+ new Attribute("width", "auto", false, Width.auto);
+ public static final Attribute WORD_SPACING =
+ new Attribute("word-spacing", "normal", true,
+ SpacingValue.factory);
+
+ private final String name;
+ private final String defValue;
+ private final boolean inherit;
+ private final PropertyValueConverter converter;
+ private final ShorthandPropertyExpander expander;
+
+ private Attribute(final String name, final String defValue,
+ final boolean inherit,
+ final PropertyValueConverter converter) {
+ this(name, defValue, inherit, converter, null);
+ }
+
+ private Attribute(final String name, final String defValue,
+ final boolean inherit,
+ final ShorthandPropertyExpander expander) {
+ this(name, defValue, inherit, null, expander);
+ }
+
+ private Attribute(final String name, final String defValue,
+ final boolean inherit,
+ final PropertyValueConverter converter,
+ final ShorthandPropertyExpander expander) {
+ this.name = name;
+ this.defValue = defValue;
+ this.inherit = inherit;
+ this.converter = converter;
+ this.expander = expander;
+ }
+
+ public String getDefaultValue() {
+ return defValue;
+ }
+
+ public boolean isInherited() {
+ return inherit;
+ }
+
+ public String toString() {
+ return name;
+ }
+
+ /**
+ * Returns the converter to translate property values from
+ * {@link StyleConstants} to <code>CSS</code> and vice versa.
+ *
+ * @return the converter.
+ */
+ PropertyValueConverter getConverter() {
+ return converter;
+ }
+
+ /**
+ * Returns the expander to convert a shorthand property to its
+ * distinct parts.
+ *
+ * @return the expander.
+ */
+ ShorthandPropertyExpander getExpander() {
+ return expander;
+ }
+ }
+
+ interface ShorthandPropertyExpander {
+ void parseAndExpandProperty(MutableAttributeSet attrs,
+ String value);
+ }
+
+ /**
+ * The implementation is based on CSS1 spec for
+ * <a href="http://www.w3.org/TR/CSS1#background">'background'</a>.
+ */
+ static final class BackgroundExpander implements ShorthandPropertyExpander {
+
+ public void parseAndExpandProperty(final MutableAttributeSet attrs,
+ final String value) {
+ ColorProperty color = null;
+ ImageValue image = null;
+ BackgroundRepeat repeat = null;
+ BackgroundAttachment attachment = null;
+ BackgroundPosition[] position = new BackgroundPosition[2];
+ String[] parts = split(value.trim());
+ if (parts == null) {
+ return;
+ }
+ for (int i = 0; i < parts.length; i++) {
+ if (color == null) {
+ color = (ColorProperty)BackgroundColor.factory
+ .toCSS(parts[i]);
+ if (color != null) {
+ continue;
+ }
+ }
+
+ if (image == null) {
+ image = (ImageValue)ImageValue.NONE.toCSS(parts[i]);
+ if (image != null) {
+ continue;
+ }
+ }
+
+ if (repeat == null) {
+ repeat = (BackgroundRepeat)BackgroundRepeat.factory
+ .toCSS(parts[i]);
+ if (repeat != null) {
+ continue;
+ }
+ }
+
+ if (attachment == null) {
+ attachment = (BackgroundAttachment)BackgroundAttachment
+ .factory.toCSS(parts[i]);
+ if (attachment != null) {
+ continue;
+ }
+ }
+
+ if (position[0] == null) {
+ position[0] =
+ (BackgroundPosition)Attribute.BACKGROUND_POSITION
+ .getConverter().toCSS(parts[i]);
+ if (position[0] != null) {
+ continue;
+ }
+ }
+
+ if (position[1] == null) {
+ position[1] =
+ (BackgroundPosition)Attribute.BACKGROUND_POSITION
+ .getConverter().toCSS(parts[i]);
+ if (position[1] != null) {
+ continue;
+ }
+ }
+
+ return;
+ }
+
+ final PropertyValueConverter bgPosConverter =
+ Attribute.BACKGROUND_POSITION.getConverter();
+ // Attribute.BACKGROUND_POSITION.getDefaultValue() must be "0% 0%"
+ PropertyValueConverter bgPosition =
+ position[0] == null
+ ? bgPosConverter.toCSS("0% 0%"/*Attribute.BACKGROUND_POSITION
+ .getDefaultValue()*/)
+ : (position[1] != null
+ ? bgPosConverter.toCSS(position[0] + " " + position[1])
+ : position[0]);
+
+ if (bgPosition == null) {
+ return;
+ }
+
+ attrs.addAttribute(Attribute.BACKGROUND_COLOR,
+ color != null ? color : BackgroundColor.factory);
+ attrs.addAttribute(Attribute.BACKGROUND_IMAGE,
+ image != null ? image : ImageValue.NONE);
+ attrs.addAttribute(Attribute.BACKGROUND_REPEAT,
+ repeat != null ? repeat
+ : BackgroundRepeat.factory);
+ attrs.addAttribute(Attribute.BACKGROUND_ATTACHMENT,
+ attachment != null
+ ? attachment : BackgroundAttachment.factory);
+ attrs.addAttribute(Attribute.BACKGROUND_POSITION, bgPosition);
+ }
+
+ private static String[] split(final String value) {
+ if (value.indexOf("url(") != -1) {
+ Matcher matcher = ListStyleExpander.URL_PATTERN.matcher(value);
+ if (!matcher.find()) {
+ return null;
+ }
+ final int urlStart = matcher.start();
+ final int urlEnd = matcher.end();
+ final String url = value.substring(urlStart, urlEnd);
+ final String rest = (value.substring(0, urlStart)
+ + value.substring(urlEnd)).trim();
+ if (rest.length() != 0) {
+ if (rest.indexOf("url(") != -1) {
+ return null;
+ }
+
+ String[] parts = BorderColor.SPLIT_PATTERN.split(rest);
+ String[] result = new String[parts.length + 1];
+ System.arraycopy(parts, 0, result, 0, parts.length);
+ result[parts.length] = url;
+
+ return result;
+ }
+
+ return new String[] {url};
+ }
+
+ return BorderColor.SPLIT_PATTERN.split(value);
+ }
+ }
+
+ /**
+ * Describes converters of attribute values from {@link StyleConstants}
+ * notation to CSS and vice versa.
+ * <p>
+ * The convertion may be not single-valued transformation, i.e.
+ * the following expression is generally speaking <code>false</code>:
+ * <pre>
+ * value.equals(toCSS(value).fromCSS())
+ * </pre>
+ * <p>
+ * The result of convertion to CSS notation is instance of
+ * <code>PropertyValueConverter</code>. This object holds values for
+ * both notations. It returns its <code>StyleConstants</code> representation
+ * from {@link CSS.PropertyValueConverter#fromCSS() fromCSS()} method.
+ * The CSS-styled value should be returned from <code>toString()</code>.
+ */
+ interface PropertyValueConverter {
+ /**
+ * Converts an attribute value from <code>StyleConstants</code> to
+ * <code>CSS</code>.
+ *
+ * @param value the value to convert.
+ * @return wrapper object which holds both values.
+ */
+ PropertyValueConverter toCSS(Object value);
+
+ /**
+ * Returns the <code>StyleConstants</code> representation of a CSS
+ * property value stored.
+ *
+ * @return the converted value.
+ */
+ Object fromCSS();
+ }
+
+ interface RelativeValueResolver {
+ Object getComputedValue(View view);
+ }
+
+ /**
+ * The implementation is based on CSS1 spec for
+ * <a href="http://www.w3.org/TR/CSS1#background-attachment">'background-attachment'</a>.
+ */
+ static final class BackgroundAttachment extends FixedSetValues {
+ static final BackgroundAttachment factory = new BackgroundAttachment(0);
+
+ private static final String[] VALID_VALUES = {
+ "scroll", "fixed"
+ };
+ private static final BackgroundAttachment[] VALUE_HOLDERS =
+ new BackgroundAttachment[VALID_VALUES.length];
+
+ static {
+ VALUE_HOLDERS[0] = factory;
+ }
+
+ private BackgroundAttachment(final int index) {
+ super(index);
+ }
+
+ String[] getValidValues() {
+ return VALID_VALUES;
+ }
+
+ PropertyValueConverter[] getValueHolders() {
+ return VALUE_HOLDERS;
+ }
+
+ PropertyValueConverter createValueHolder(final int valueIndex) {
+ return new BackgroundAttachment(valueIndex);
+ }
+ }
+
+ /**
+ * The implementation is based on CSS1 spec for
+ * <a href="http://www.w3.org/TR/CSS1#background-color">'background-color'</a>.
+ */
+ static final class BackgroundColor extends ColorProperty {
+ static final BackgroundColor factory = new BackgroundColor();
+
+ private BackgroundColor() {
+ super("transparent", null);
+ }
+
+ public PropertyValueConverter toCSS(final Object value) {
+ return "transparent".equals(value) ? factory : super.toCSS(value);
+ }
+ }
+
+ /**
+ * The implementation is based on CSS1 spec for
+ * <a href="http://www.w3.org/TR/CSS1#background-image">'background-image'</a>
+ * and
+ * <a href="http://www.w3.org/TR/CSS1#list-style-image">'list-style-image'</a>.
+ */
+ static final class ImageValue implements PropertyValueConverter {
+ static final ImageValue NONE = new ImageValue();
+
+ private final String value;
+ private final String path;
+ private BackgroundImageLoader imageLoader;
+
+ private final List listeners = new ArrayList();
+
+ private ImageValue() {
+ this("none", null);
+ }
+
+ private ImageValue(final String value, final String path) {
+ this.value = value;
+ this.path = path;
+ }
+
+ public PropertyValueConverter toCSS(final Object value) {
+ if ("none".equals(value)) {
+ return NONE;
+ }
+
+ String path = extractPath((String)value);
+ return path != null ? new ImageValue((String)value, path)
+ : null;
+ }
+
+ public Object fromCSS() {
+ return null;
+ }
+
+ public String toString() {
+ return value;
+ }
+
+ void loadImage(final URL base) {
+ if (path != null && imageLoader == null) {
+
+ final URL url = HTML.resolveURL(path, base);
+ imageLoader = new BackgroundImageLoader(url, -1, -1) {
+ protected void onReady() {
+ super.onReady();
+ notifyViews();
+ }
+ };
+ }
+ }
+
+ int getWidth() {
+ return imageLoader != null ? imageLoader.getWidth() : -1;
+ }
+
+ int getHeight() {
+ return imageLoader != null ? imageLoader.getHeight() : -1;
+ }
+
+ Image getImage() {
+ return imageLoader != null && imageLoader.isReady()
+ ? imageLoader.getImage() : null;
+ }
+
+ synchronized void addListener(final ViewUpdater viewUpdater) {
+ if (imageLoader != null
+ && !imageLoader.isReady() && !imageLoader.isError()) {
+
+ listeners.add(viewUpdater);
+ }
+ }
+
+ private static String extractPath(final String url) {
+ if (!(url.startsWith("url(") && url.endsWith(")"))) {
+ return null;
+ }
+ char c = url.charAt(4);
+ boolean quoted = c == '\'' || c == '"';
+ if (quoted && url.charAt(url.length() - 2) != c) {
+ return null;
+ }
+ return url.substring(4 + (quoted ? 1 : 0),
+ url.length() - 1 - (quoted ? 1 : 0));
+ }
+
+ private synchronized void notifyViews() {
+ Iterator it = listeners.iterator();
+ while (it.hasNext()) {
+ ((ViewUpdater)it.next()).updateView();
+ }
+ listeners.clear();
+ }
+ }
+
+ /**
+ * The implementation is based on CSS1 spec for
+ * <a href="http://www.w3.org/TR/CSS1#background-position">'background-position'</a>.
+ */
+ static final class BackgroundPosition implements PropertyValueConverter {
+ private static final String[] KEYWORDS = new String[] {
+ "top", "center", "bottom", "left", "right"
+ };
+
+ private static final String[] KEYWORD_VALUE_STRS =
+ new String[] {"0%", "50%", "100%"};
+ private static final FloatValue[] KEYWORD_VALUES = new FloatValue[3];
+
+ private static final int KV_0 = 0;
+ private static final int KV_50 = 1;
+ private static final int KV_100 = 2;
+
+ private String theValue;
+ private FloatValue horz;
+ private FloatValue vert;
+
+ BackgroundPosition() {
+ this(null, null, null);
+ }
+
+ private BackgroundPosition(final String theValue,
+ final int hz,
+ final int vt) {
+ this(theValue, getKeywordValue(hz), getKeywordValue(vt));
+ }
+
+ private BackgroundPosition(final String theValue,
+ final FloatValue horz,
+ final FloatValue vert) {
+ this.theValue = theValue;
+ this.horz = horz;
+ this.vert = vert;
+ }
+
+ public PropertyValueConverter toCSS(final Object value) {
+ return parseValue((String)value);
+ }
+
+ public Object fromCSS() {
+ return null;
+ }
+
+ public String toString() {
+ return theValue;
+ }
+
+ private PropertyValueConverter parseValue(final String value) {
+ String[] parts = value.split("\\s+");
+ if (parts.length > 2) {
+ return null;
+ }
+
+ if (parts.length == 1) {
+ int index = getKeywordIndex(parts[0]);
+ if (index != -1) {
+ switch (index) {
+ case 0: // top
+ horz = getKeywordValue(KV_50);
+ vert = getKeywordValue(KV_0);
+ break;
+
+ case 1: // center
+ horz = getKeywordValue(KV_50);
+ vert = getKeywordValue(KV_50);
+ break;
+
+ case 2: // bottom
+ horz = getKeywordValue(KV_50);
+ vert = getKeywordValue(KV_100);
+ break;
+
+ case 3: // left
+ horz = getKeywordValue(KV_0);
+ vert = getKeywordValue(KV_50);
+ break;
+
+ case 4: // right
+ horz = getKeywordValue(KV_100);
+ vert = getKeywordValue(KV_50);
+ break;
+
+ default:
+ return null;
+ }
+ return new BackgroundPosition(value, horz, vert);
+ }
+ horz = (FloatValue)FloatValue.factory.toCSS(parts[0]);
+ return horz == null
+ ? null
+ : new BackgroundPosition(value, horz,
+ getKeywordValue(KV_50));
+ }
+
+ int hzIndex = getKeywordIndex(parts[0]);
+ int vtIndex = getKeywordIndex(parts[1]);
+ if (hzIndex == -1 && vtIndex != -1
+ || hzIndex != -1 && vtIndex == -1) {
+
+ // Combinations of keywords and length or percentage values
+ // are not allowed according to CSS1 spec, 'background-position'.
+ return null;
+ }
+
+ if (hzIndex == -1 && vtIndex == -1) {
+ horz = (FloatValue)FloatValue.factory.toCSS(parts[0]);
+ vert = (FloatValue)FloatValue.factory.toCSS(parts[1]);
+ return horz == null || vert == null
+ ? null
+ : new BackgroundPosition(value, horz, vert);
+ }
+
+ if (isHorizontalIndex(hzIndex) && isVerticalIndex(vtIndex)) {
+ return new BackgroundPosition(value,
+ kwToValue(hzIndex),
+ kwToValue(vtIndex));
+ }
+
+ if (isHorizontalIndex(vtIndex) && isVerticalIndex(hzIndex)) {
+ return new BackgroundPosition(value,
+ kwToValue(vtIndex),
+ kwToValue(hzIndex));
+ }
+
+ return null;
+ }
+
+ private static int getKeywordIndex(final String kw) {
+ for (int i = 0; i < KEYWORDS.length; i++) {
+ if (KEYWORDS[i].equals(kw)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ private static boolean isHorizontalIndex(final int kwIndex) {
+ return kwIndex == 1 || kwIndex == 3 || kwIndex == 4;
+ }
+
+ private static boolean isVerticalIndex(final int kwIndex) {
+ return kwIndex <= 2;
+ }
+
+ private static int kwToValue(final int kwIndex) {
+ switch (kwIndex) {
+ case 0: // top
+ case 3: // left
+ return 0;
+
+ case 1: // center
+ return 1;
+
+ case 2: // bottom
+ case 4: // right
+ return 2;
+
+ default:
+ return -1;
+ }
+ }
+
+ private static FloatValue getKeywordValue(final int valueIndex) {
+ if (KEYWORD_VALUES[valueIndex] == null) {
+ // valueIndex must be in the range 0..2
+ KEYWORD_VALUES[valueIndex] =
+ new FloatValue(KEYWORD_VALUE_STRS[valueIndex],
+ 50 * valueIndex,
+ Length.RELATIVE_UNITS_PERCENTAGE);
+ }
+ return KEYWORD_VALUES[valueIndex];
+ }
+ }
+
+ /**
+ * The implementation is based on CSS1 spec for
+ * <a href="http://www.w3.org/TR/CSS1#background-repeat">'background-repeat'</a>.
+ */
+ static final class BackgroundRepeat extends FixedSetValues {
+ static final BackgroundRepeat factory = new BackgroundRepeat(0);
+
+ static final int REPEAT = 0;
+ static final int REPEAT_X = 1;
+ static final int REPEAT_Y = 2;
+ static final int NO_REPEAT = 3;
+
+ private static final String[] VALID_VALUES = {
+ "repeat", "repeat-x", "repeat-y", "no-repeat"
+ };
+ private static final BackgroundRepeat[] VALUE_HOLDERS =
+ new BackgroundRepeat[VALID_VALUES.length];
+
+ static {
+ VALUE_HOLDERS[0] = factory;
+ }
+
+ private BackgroundRepeat(final int index) {
+ super(index);
+ }
+
+ String[] getValidValues() {
+ return VALID_VALUES;
+ }
+
+ PropertyValueConverter[] getValueHolders() {
+ return VALUE_HOLDERS;
+ }
+
+ PropertyValueConverter createValueHolder(final int valueIndex) {
+ return new BackgroundRepeat(valueIndex);
+ }
+ }
+
+
+ /**
+ * The implementation is based on CSS1 spec for
+ * <a href="http://www.w3.org/TR/CSS1#border-color">'border-color'</a>.
+ */
+ static final class BorderColor implements PropertyValueConverter {
+ static final Pattern SPLIT_PATTERN =
+ Pattern.compile("(?<!,)\\s+");
+
+ private static final PropertyValueConverter converter =
+ ColorProperty.factory;
+
+ private final ColorProperty[] colors;
+
+ private String value;
+
+ BorderColor() {
+ this(null, null);
+ }
+
+ BorderColor(final ColorProperty[] colors) {
+ this(colors, getDeclaration(colors));
+ }
+
+ BorderColor(final ColorProperty[] colors, final String value) {
+ this.colors = colors;
+ this.value = value;
+ }
+
+ public PropertyValueConverter toCSS(final Object value) {
+ final ColorProperty[] cp = new ColorProperty[4];
+ final String[] values = SPLIT_PATTERN.split((String)value);
+ for (int i = 0; i < cp.length; i++) {
+ if (i < values.length) {
+ cp[i] = (ColorProperty)converter.toCSS(values[i]);
+ if (cp[i] == null) {
+ return null;
+ }
+ } else {
+ cp[i] = cp[i >= 3 ? 1 : 0];
+ }
+ }
+ return new BorderColor(cp, (String)value);
+ }
+
+ public Object fromCSS() {
+ return null;
+ }
+
+ public String toString() {
+ return value;
+ }
+
+ void setSideColor(final int sideIndex, final ColorProperty color) {
+ colors[sideIndex] = color;
+ value = getDeclaration(colors);
+ }
+
+ ColorProperty getSideColor(final int sideIndex) {
+ return colors[sideIndex];
+ }
+
+ private static String getDeclaration(final ColorProperty[] colors) {
+ StringBuffer result = new StringBuffer();
+ for (int i = 0; i < colors.length; i++) {
+ if (i > 0) {
+ result.append(' ');
+ }
+ result.append(colors[i] == null ? "white"
+ : colors[i].toString());
+ }
+ return result.toString();
+ }
+ }
+
+ /**
+ * The implementation is based on CSS1 spec for
+ * <a href="http://www.w3.org/TR/CSS1#border-style">'border-style'</a>.
+ * <p>This class stores four {@link BorderStyleValue} for each side of the box.
+ */
+ static final class BorderStyle implements PropertyValueConverter {
+ private static final PropertyValueConverter converter =
+ BorderStyleValue.factory;
+
+ private final BorderStyleValue[] styles;
+
+ private String value;
+
+ BorderStyle() {
+ this(null, null);
+ }
+
+ BorderStyle(final BorderStyleValue[] styles) {
+ this(styles, getDeclaration(styles));
+ }
+
+ BorderStyle(final BorderStyleValue[] styles,
+ final String value) {
+ this.styles = styles;
+ this.value = value;
+ }
+
+ public PropertyValueConverter toCSS(final Object value) {
+ final BorderStyleValue[] bs = new BorderStyleValue[4];
+ final String[] values = ((String)value).split("\\s+");
+ for (int i = 0; i < bs.length; i++) {
+ if (i < values.length) {
+ bs[i] = (BorderStyleValue)converter.toCSS(values[i]);
+ if (bs[i] == null) {
+ return null;
+ }
+ } else {
+ bs[i] = bs[i >= 3 ? 1 : 0];
+ }
+ }
+ return new BorderStyle(bs, (String)value);
+ }
+
+ public Object fromCSS() {
+ return null;
+ }
+
+ public String toString() {
+ return value;
+ }
+
+ void setSideStyle(final int sideIndex, final BorderStyleValue style) {
+ styles[sideIndex] = style;
+ value = getDeclaration(styles);
+ }
+
+ BorderStyleValue getSideStyle(final int sideIndex) {
+ return styles[sideIndex] != null ? styles[sideIndex]
+ : (BorderStyleValue)converter;
+ }
+
+ private static String getDeclaration(final BorderStyleValue[] styles) {
+ StringBuffer result = new StringBuffer();
+ for (int i = 0; i < styles.length; i++) {
+ if (i > 0) {
+ result.append(' ');
+ }
+ result.append(styles[i] == null ? "none"
+ : styles[i].toString());
+ }
+ return result.toString();
+ }
+ }
+
+ /**
+ * The implementation is based on CSS1 spec for
+ * <a href="http://www.w3.org/TR/CSS1#border-style">'border-style'</a>.
+ * <p>Defines the particular value for a side of the box.
+ */
+ static final class BorderStyleValue extends FixedSetValues {
+ static final int NONE = 0;
+ static final int SOLID = 1;
+ static final int DOTTED = 2;
+ static final int DASHED = 3;
+ static final int DOUBLE = 4;
+ static final int GROOVE = 5;
+ static final int RIDGE = 6;
+ static final int INSET = 7;
+ static final int OUTSET = 8;
+
+ static final BorderStyleValue factory = new BorderStyleValue(0);
+
+ private static final String[] VALID_VALUES = {
+ "none", "solid", "dotted", "dashed",
+ "double", "groove", "ridge", "inset",
+ "outset"
+ };
+ private static final BorderStyleValue[] VALUE_HOLDERS =
+ new BorderStyleValue[VALID_VALUES.length];
+
+ static {
+ VALUE_HOLDERS[0] = factory;
+ }
+
+ private BorderStyleValue(final int index) {
+ super(index);
+ }
+
+ String[] getValidValues() {
+ return VALID_VALUES;
+ }
+
+ PropertyValueConverter[] getValueHolders() {
+ return VALUE_HOLDERS;
+ }
+
+ PropertyValueConverter createValueHolder(final int valueIndex) {
+ return new BorderStyleValue(valueIndex);
+ }
+ }
+
+ /**
+ * The implementation is based on CSS1.
+ * It defines the particular value for a side of the box:
+ * <ul>
+ * <li><a href="http://www.w3.org/TR/CSS1#border-width-top">'border-width-top'</a>
+ * <li><a href="http://www.w3.org/TR/CSS1#border-width-right">'border-width-right'</a>
+ * <li><a href="http://www.w3.org/TR/CSS1#border-width-bottom">'border-width-bottom'</a>
+ * <li><a href="http://www.w3.org/TR/CSS1#border-width-left">'border-width-left'</a>
+ * </ul>
+ */
+ static final class BorderWidthValue extends FloatValue {
+ static final BorderWidthValue factory =
+ new BorderWidthValue("medium", 3);
+
+ private static final BorderWidthValue[] WIDTHS = {
+ new BorderWidthValue("thin", 1),
+ factory,
+ new BorderWidthValue("thick", 5)
+ };
+
+ private BorderWidthValue(final String value, final float theValue) {
+ super(value, theValue, RELATIVE_UNITS_UNDEFINED);
+ }
+
+ private BorderWidthValue(final String value, final float theValue,
+ final int rUnits) {
+ super(value, theValue, rUnits);
+ }
+
+ public PropertyValueConverter toCSS(final Object value) {
+ for (int i = 0; i < WIDTHS.length; i++) {
+ if (WIDTHS[i].sValue.equals(value)) {
+ return WIDTHS[i];
+ }
+ }
+
+ return super.toCSS(value);
+ }
+
+ PropertyValueConverter create(final String strValue,
+ final float theValue, final int rUnits) {
+ return theValue < 0 || rUnits == RELATIVE_UNITS_PERCENTAGE
+ ? null
+ : new BorderWidthValue(strValue, theValue, rUnits);
+ }
+ }
+
+ /**
+ * The implementation is based on CSS1 spec for
+ * <ul>
+ * <li><a href="http://www.w3.org/TR/CSS1#border-top">'border-top'</a>
+ * <li><a href="http://www.w3.org/TR/CSS1#border-right">'border-right'</a>
+ * <li><a href="http://www.w3.org/TR/CSS1#border-bottom">'border-bottom'</a>
+ * <li><a href="http://www.w3.org/TR/CSS1#border-left">'border-left'</a>
+ * </ul>
+ */
+ static final class BorderSideExpander implements ShorthandPropertyExpander {
+ static final int TOP_SIDE = 0;
+ static final int RIGHT_SIDE = 1;
+ static final int BOTTOM_SIDE = 2;
+ static final int LEFT_SIDE = 3;
+
+ private final int sideIndex;
+ private final Attribute widthKey;
+
+ BorderSideExpander(final int sideIndex, final Attribute widthKey) {
+ this.sideIndex = sideIndex;
+ this.widthKey = widthKey;
+ }
+
+ public void parseAndExpandProperty(final MutableAttributeSet attrs,
+ final String value) {
+ BorderStyleValue style = null;
+ ColorProperty color = null;
+ BorderWidthValue width = null;
+
+ final String[] parts = BorderColor.SPLIT_PATTERN.split(value);
+ for (int i = 0; i < parts.length; i++) {
+ if (style == null) {
+ style = (BorderStyleValue)BorderStyle.converter
+ .toCSS(parts[i]);
+ if (style != null) {
+ continue;
+ }
+ }
+
+ if (color == null) {
+ color = (ColorProperty)ColorProperty.factory
+ .toCSS(parts[i]);
+ if (color != null) {
+ continue;
+ }
+ }
+
+ if (width == null) {
+ width = (BorderWidthValue)BorderWidthValue.factory
+ .toCSS(parts[i]);
+ if (width != null) {
+ continue;
+ }
+ }
+
+ // Contains an unknown value - ignore the entire declaration
+ return;
+ }
+
+ if (style != null) {
+ final BorderStyle styleValue =
+ (BorderStyle)attrs.getAttribute(Attribute.BORDER_STYLE);
+ if (styleValue != null) {
+ styleValue.setSideStyle(sideIndex, style);
+ } else {
+ BorderStyleValue[] styles = new BorderStyleValue[4];
+ styles[sideIndex] = style;
+ attrs.addAttribute(Attribute.BORDER_STYLE,
+ new BorderStyle(styles));
+ }
+ }
+
+ if (color != null) {
+ final BorderColor colorValue =
+ (BorderColor)attrs.getAttribute(Attribute.BORDER_COLOR);
+ if (colorValue != null) {
+ colorValue.setSideColor(sideIndex, color);
+ } else {
+ ColorProperty[] colors = new ColorProperty[4];
+ colors[sideIndex] = color;
+ attrs.addAttribute(Attribute.BORDER_COLOR,
+ new BorderColor(colors));
+ }
+ }
+
+ if (width != null) {
+ attrs.addAttribute(widthKey, width);
+ }
+ }
+ }
+
+ /**
+ * The implementation is based on CSS1 spec for
+ * <a href="http://www.w3.org/TR/CSS1#border">'border'</a>.
+ */
+ static final class BorderExpander implements ShorthandPropertyExpander {
+ public void parseAndExpandProperty(final MutableAttributeSet attrs,
+ final String value) {
+ BorderStyleValue style = null;
+ ColorProperty color = null;
+ BorderWidthValue width = null;
+
+ final String[] parts = BorderColor.SPLIT_PATTERN.split(value);
+ for (int i = 0; i < parts.length; i++) {
+ if (style == null) {
+ style = (BorderStyleValue)BorderStyle.converter
+ .toCSS(parts[i]);
+ if (style != null) {
+ continue;
+ }
+ }
+
+ if (color == null) {
+ color = (ColorProperty)ColorProperty.factory
+ .toCSS(parts[i]);
+ if (color != null) {
+ continue;
+ }
+ }
+
+ if (width == null) {
+ width = (BorderWidthValue)BorderWidthValue.factory
+ .toCSS(parts[i]);
+ if (width != null) {
+ continue;
+ }
+ }
+
+ // Contains an unknown value - ignore the entire declaration
+ return;
+ }
+
+ if (style != null) {
+ attrs.addAttribute(Attribute.BORDER_STYLE,
+ new BorderStyle(new BorderStyleValue[] {
+ style, style,
+ style, style
+ },
+ style.toString()));
+ }
+
+ if (color != null) {
+ attrs.addAttribute(Attribute.BORDER_COLOR,
+ new BorderColor(new ColorProperty[] {
+ color, color,
+ color, color
+ },
+ color.toString()));
+ }
+
+ if (width != null) {
+ attrs.addAttribute(Attribute.BORDER_TOP_WIDTH, width);
+ attrs.addAttribute(Attribute.BORDER_RIGHT_WIDTH, width);
+ attrs.addAttribute(Attribute.BORDER_BOTTOM_WIDTH, width);
+ attrs.addAttribute(Attribute.BORDER_LEFT_WIDTH, width);
+ }
+ }
+ }
+
+ /**
+ * The implementation is based on CSS1 spec for
+ * <a href="http://www.w3.org/TR/CSS1#color">'color'</a>.
+ * @see <a href="http://www.w3.org/TR/CSS1#color-units">Color Units</a>.
+ */
+ static class ColorProperty implements PropertyValueConverter {
+ static final ColorProperty factory = new ColorProperty();
+
+ private static final char[] zeros = {'0', '0', '0', '0', '0'};
+ private static final Map colorMap = new HashMap();
+ private static final Pattern RGB_SEPARATOR =
+ Pattern.compile(",");
+
+ private final Color color;
+ private final String cssValue;
+
+ static {
+ Color color;
+
+ color = Color.CYAN;
+ colorMap.put("aqua", color);
+ colorMap.put("00ffff", color);
+
+ color = Color.BLACK;
+ colorMap.put("black", color);
+ colorMap.put("000000", color);
+
+ color = Color.BLUE;
+ colorMap.put("blue", color);
+ colorMap.put("0000ff", color);
+
+ color = Color.MAGENTA;
+ colorMap.put("fuchsia", color);
+ colorMap.put("ff00ff", color);
+
+ color = Color.GRAY;
+ colorMap.put("gray", color);
+ colorMap.put("808080", color);
+
+ color = new Color(0, 128, 0);
+ colorMap.put("green", color);
+ colorMap.put("008000", color);
+
+ color = Color.GREEN;
+ colorMap.put("lime", color);
+ colorMap.put("00ff00", color);
+
+ color = new Color(128, 0, 0);
+ colorMap.put("maroon", color);
+ colorMap.put("800000", color);
+
+ color = new Color(0, 0, 128);
+ colorMap.put("navy", color);
+ colorMap.put("000080", color);
+
+ color = new Color(128, 128, 0);
+ colorMap.put("olive", color);
+ colorMap.put("808000", color);
+
+ color = new Color(128, 0, 128);
+ colorMap.put("purple", color);
+ colorMap.put("800080", color);
+
+ color = Color.RED;
+ colorMap.put("red", color);
+ colorMap.put("ff0000", color);
+
+ color = Color.LIGHT_GRAY;
+ colorMap.put("silver", color);
+ colorMap.put("c0c0c0", color);
+
+ color = new Color(0, 128, 128);
+ colorMap.put("teal", color);
+ colorMap.put("008080", color);
+
+ color = Color.WHITE;
+ colorMap.put("white", color);
+ colorMap.put("ffffff", color);
+
+ color = Color.YELLOW;
+ colorMap.put("yellow", color);
+ colorMap.put("ffff00", color);
+ }
+
+ ColorProperty(final String cssValue, final Color color) {
+ this.cssValue = cssValue;
+ this.color = color;
+ }
+
+ private ColorProperty() {
+ this(null, null);
+ }
+
+ private ColorProperty(final Color color) {
+ this(colorToString(color), color);
+ }
+
+ public PropertyValueConverter toCSS(final Object value) {
+ if (value instanceof String) {
+ final String cssValue = (String)value;
+ Color c = parseRGB(cssValue);
+ if (c == null) {
+ c = stringToColor(cssValue);
+ }
+ return c != null ? new ColorProperty(cssValue, c) : null;
+ }
+ return new ColorProperty((Color)value);
+ }
+
+ public Object fromCSS() {
+ return color;
+ }
+
+ public String toString() {
+ return cssValue;
+ }
+
+ static Color stringToColor(final String colorName) {
+ if (Utilities.isEmptyString(colorName)) {
+ return null;
+ }
+
+ final String lower = colorName.toLowerCase();
+ if (lower.charAt(0) == '#') {
+ final StringBuffer name = new StringBuffer(6);
+ if (lower.length() == 4) {
+ for (int i = 1; i < 4; i++) {
+ name.append(lower.charAt(i))
+ .append(lower.charAt(i));
+ }
+ } else if (lower.length() != 7) {
+ return null;
+ } else {
+ name.append(lower.substring(1));
+ }
+
+ final Color result = (Color)colorMap.get(name.toString());
+ return result != null
+ ? result
+ : new Color(Integer.parseInt(name.toString(), 16));
+ } else {
+ return (Color)colorMap.get(lower);
+ }
+ }
+
+ static Color parseRGB(final String rgbColor) {
+ if (Utilities.isEmptyString(rgbColor)
+ || !rgbColor.startsWith("rgb(") && !rgbColor.endsWith(")")) {
+
+ return null;
+ }
+ final String[] colorComponents =
+ RGB_SEPARATOR.split(rgbColor.substring(4,
+ rgbColor.length() - 1));
+ if (colorComponents.length < 3) {
+ return null;
+ }
+ final int[] rgb = new int[3];
+ boolean percentage = false;
+ for (int i = 0; i < colorComponents.length; i++) {
+ final String cc = colorComponents[i].trim();
+ if (Utilities.isEmptyString(cc)) {
+ return null;
+ }
+ percentage |= cc.charAt(cc.length() - 1) == '%';
+ if (percentage && cc.charAt(cc.length() - 1) != '%') {
+ return null;
+ }
+ rgb[i] = percentage
+ ? (int)(Double.parseDouble(cc.substring(0,
+ cc.length()
+ - 1))
+ * 255 / 100)
+ : Integer.parseInt(cc);
+ rgb[i] = Utilities.range(rgb[i], 0, 255);
+ }
+ return new Color(rgb[0], rgb[1], rgb[2]);
+ }
+
+ final Color getColor() {
+ return color;
+ }
+
+ private static String colorToString(final Color color) {
+ final StringBuffer result = new StringBuffer(7);
+ final String hex = Integer.toHexString(color.getRGB() & 0x00FFFFFF);
+ result.append('#').append(zeros, 0, 6 - hex.length()).append(hex);
+ return result.toString();
+ }
+ }
+
+ /**
+ * The implementation is based on CSS1 spec for
+ * <a href="http://www.w3.org/TR/CSS1#clear">'clear'</a>.
+ */
+ static final class Clear extends FixedSetValues {
+ private static final String[] VALID_VALUES = {
+ "none", "left", "right", "both"
+ };
+ private static final Clear[] VALUE_HOLDERS =
+ new Clear[VALID_VALUES.length];
+
+ Clear() {
+ this(-1);
+ }
+
+ private Clear(final int index) {
+ super(index);
+ }
+
+ String[] getValidValues() {
+ return VALID_VALUES;
+ }
+
+ PropertyValueConverter[] getValueHolders() {
+ return VALUE_HOLDERS;
+ }
+
+ PropertyValueConverter createValueHolder(final int valueIndex) {
+ return new Clear(valueIndex);
+ }
+ }
+
+ /**
+ * The implementation is based on CSS1 spec for
+ * <a href="http://www.w3.org/TR/CSS1#display">'display'</a>.
+ */
+ static final class Display extends FixedSetValues {
+ private static final String[] VALID_VALUES = {
+ "block", "inline", "list-item", "none"
+ };
+ private static final Display[] VALUE_HOLDERS =
+ new Display[VALID_VALUES.length];
+
+ Display() {
+ this(-1);
+ }
+
+ private Display(final int index) {
+ super(index);
+ }
+
+ String[] getValidValues() {
+ return VALID_VALUES;
+ }
+
+ PropertyValueConverter[] getValueHolders() {
+ return VALUE_HOLDERS;
+ }
+
+ PropertyValueConverter createValueHolder(final int valueIndex) {
+ return new Display(valueIndex);
+ }
+ }
+
+ /**
+ * The implementation is based on CSS1 spec for
+ * <a href="http://www.w3.org/TR/CSS1#float">'float'</a>.
+ */
+ static final class FloatProperty extends FixedSetValues {
+ private static final String[] VALID_VALUES = {
+ "none", "left", "right"
+ };
+ private static final FloatProperty[] VALUE_HOLDERS =
+ new FloatProperty[VALID_VALUES.length];
+
+ FloatProperty() {
+ this(-1);
+ }
+
+ private FloatProperty(final int index) {
+ super(index);
+ }
+
+ String[] getValidValues() {
+ return VALID_VALUES;
+ }
+
+ PropertyValueConverter[] getValueHolders() {
+ return VALUE_HOLDERS;
+ }
+
+ PropertyValueConverter createValueHolder(final int valueIndex) {
+ return new FloatProperty(valueIndex);
+ }
+ }
+
+ /**
+ * The implementation is based on CSS1 spec for
+ * <a href="http://www.w3.org/TR/CSS1#font-family">'font-family'</a>.
+ */
+ static final class FontFamily implements PropertyValueConverter {
+ private static final String SANS_SERIF_FAMILY = "sans-serif";
+ private static final String SERIF_FAMILY = "serif";
+ private static final String MONOSPACE_FAMILY = "monospace";
+
+ private static final String SANS_SERIF = "SansSerif";
+ private static final String SERIF = "Serif";
+ private static final String MONOSPACED = "Monospaced";
+
+ static final String DEFAULT = SANS_SERIF;
+
+ private static final Pattern SPLIT_PATTERN =
+ Pattern.compile("\\s*,\\s*");
+
+ private static final String[] fontFamilies;
+
+ static {
+ GraphicsEnvironment ge =
+ GraphicsEnvironment.getLocalGraphicsEnvironment();
+ fontFamilies = ge.getAvailableFontFamilyNames();
+ Arrays.sort(fontFamilies);
+ }
+
+ private final String value;
+ private final String family;
+
+ FontFamily() {
+ value = null;
+ family = null;
+ }
+
+ private FontFamily(final Object value) {
+ this.value = (String)value;
+ this.family = init();
+ }
+
+ public PropertyValueConverter toCSS(final Object value) {
+ return new FontFamily(value);
+ }
+
+ public Object fromCSS() {
+ return family;
+ }
+
+ public String toString() {
+ return value;
+ }
+
+ private String init() {
+ final String[] familyList = SPLIT_PATTERN.split(value);
+ for (int i = 0; i < familyList.length; i++) {
+ if (SANS_SERIF_FAMILY.equals(familyList[i])) {
+ return SANS_SERIF;
+ }
+ if (SERIF_FAMILY.equals(familyList[i])) {
+ return SERIF;
+ }
+ if (MONOSPACE_FAMILY.equals(familyList[i])) {
+ return MONOSPACED;
+ }
+ if (Arrays.binarySearch(fontFamilies, familyList[i]) >= 0) {
+ return familyList[i];
+ }
+ }
+ return SANS_SERIF;
+ }
+ }
+
+ /**
+ * The implementation is based on CSS1 spec for
+ * <a href="http://www.w3.org/TR/CSS1#font-size">'font-size'</a>.
+ */
+ static final class FontSize extends Length
+ implements RelativeValueResolver {
+
+ static final Integer[] SIZE_TABLE = {
+ new Integer(8), new Integer(10), new Integer(12), new Integer(14),
+ new Integer(18), new Integer(24), new Integer(36)
+ };
+
+ static final int RELATIVE_UNITS_SMALLER = 10;
+ static final int RELATIVE_UNITS_LARGER = 11;
+
+ private static final String[] VALUE_TABLE = {
+ "xx-small", "x-small", "small",
+ "medium",
+ "large", "x-large", "xx-large"
+ };
+
+ private final Integer size;
+
+ FontSize() {
+ super();
+ size = null;
+ }
+
+ private FontSize(final String strValue, final int rUnits) {
+ super(strValue, rUnits);
+ size = getDefaultValue();
+ }
+
+ private FontSize(final String strValue, final Integer theSize) {
+ super(strValue, RELATIVE_UNITS_UNDEFINED);
+ size = theSize;
+ }
+
+ private FontSize(final String strValue,
+ final int theSize, final int rUnits) {
+ super(strValue, rUnits);
+ size = new Integer(theSize);
+ }
+
+ public PropertyValueConverter toCSS(final Object value) {
+ if (value instanceof String) {
+ if ("smaller".equals(value)) {
+ return new FontSize((String)value, RELATIVE_UNITS_SMALLER);
+ }
+ if ("larger".equals(value)) {
+ return new FontSize((String)value, RELATIVE_UNITS_LARGER);
+ }
+ Integer theSize = valueToSize((String)value);
+ return theSize != null ? new FontSize((String)value, theSize)
+ : super.toCSS(value);
+ }
+ if (value instanceof Integer) {
+ final int index = sizeValueIndex(((Integer)value).intValue());
+ return new FontSize(VALUE_TABLE[index], SIZE_TABLE[index]);
+ }
+ return null;
+ }
+
+ public Object getComputedValue(final View view) {
+ if (relativeUnits != RELATIVE_UNITS_UNDEFINED) {
+ return new FontSize(sValue,
+ (Integer)resolveRelativeValue(view));
+ }
+ return this;
+ }
+
+ static Integer getDefaultValue() {
+ return SIZE_TABLE[2];
+ }
+
+ static int sizeValueIndex(final int size) {
+ for (int i = 0; i < SIZE_TABLE.length; i++) {
+ if (size <= SIZE_TABLE[i].intValue()) {
+ return i;
+ }
+ }
+ return VALUE_TABLE.length - 1;
+ }
+
+ PropertyValueConverter create(final String strValue,
+ final float theValue,
+ final int rUnits) {
+ if (theValue < 0) {
+ return null;
+ }
+ return new FontSize(strValue, (int)theValue, rUnits);
+ }
+
+ Object getValue(final View view) {
+ return size;
+ }
+
+ Object resolveRelativeValue(final View view) {
+ if (view == null) {
+ return getDefaultValue();
+ }
+ final View parent = view.getParent();
+ if (parent == null) {
+ return getDefaultValue();
+ }
+
+ final AttributeSet attr = parent.getAttributes();
+ final Object fs = attr.getAttribute(Attribute.FONT_SIZE);
+ final int fontSize = fs != null ? ((Length)fs).intValue(parent)
+ : getDefaultValue().intValue();
+ int sizeValueIndex;
+
+ // calculation is defined by CSS1, http://www.w3.org/TR/CSS1#length-units
+ switch (relativeUnits) {
+ case RELATIVE_UNITS_EM:
+ return new Integer(fontSize * size.intValue());
+
+ case RELATIVE_UNITS_EX:
+ return new Integer(fontSize * size.intValue() / 2);
+
+ case RELATIVE_UNITS_PERCENTAGE:
+ return new Integer(fontSize * size.intValue() / 100);
+
+ case RELATIVE_UNITS_SMALLER:
+ sizeValueIndex = sizeValueIndex(fontSize);
+ return SIZE_TABLE[sizeValueIndex > 0 ? sizeValueIndex - 1 : 0];
+
+ case RELATIVE_UNITS_LARGER:
+ sizeValueIndex = sizeValueIndex(fontSize) + 1;
+ return new Integer(fontSize * 120 / 100);
+
+ default:
+ System.err.println("font-size: can't resolve relative value. "
+ + "Unknown relative unit");
+ }
+ return getDefaultValue();
+ }
+
+ private static Integer valueToSize(final String value) {
+ for (int i = 0; i < VALUE_TABLE.length; i++) {
+ if (VALUE_TABLE[i].equals(value)) {
+ return SIZE_TABLE[i];
+ }
+ }
+ return null;
+ }
+ }
+
+ /**
+ * The implementation is based on CSS1 spec for
+ * <a href="http://www.w3.org/TR/CSS1#font-style">'font-style'</a>.
+ */
+ static final class FontStyle implements PropertyValueConverter {
+ private static final FontStyle OBLIQUE =
+ new FontStyle("oblique", Boolean.FALSE);
+ private static final FontStyle NORMAL =
+ new FontStyle("normal", Boolean.FALSE);
+ private static final FontStyle ITALIC =
+ new FontStyle("italic", Boolean.TRUE);
+
+ private final String cssValue;
+ private final Boolean value;
+
+ FontStyle() {
+ cssValue = null;
+ value = null;
+ }
+
+ private FontStyle(final String cssValue, final Boolean value) {
+ this.cssValue = cssValue;
+ this.value = value;
+ }
+
+ public PropertyValueConverter toCSS(final Object value) {
+ if (value instanceof Boolean) {
+ return ((Boolean)value).booleanValue() ? ITALIC : NORMAL;
+ } else if (ITALIC.cssValue.equals(value)) {
+ return ITALIC;
+ } else if (OBLIQUE.cssValue.equals(value)) {
+ return OBLIQUE;
+ } else if (NORMAL.cssValue.equals(value)) {
+ return NORMAL;
+ }
+
+ return null;
+ }
+
+ public Object fromCSS() {
+ return value;
+ }
+
+ public String toString() {
+ return cssValue;
+ }
+
+ }
+
+ /**
+ * The implementation is based on CSS1 spec for
+ * <a href="http://www.w3.org/TR/CSS1#font-variant">'font-variant'</a>.
+ */
+ static final class FontVariant extends FixedSetValues {
+ private static final String[] VALID_VALUES = {
+ "normal", "small-caps"
+ };
+ private static final FontVariant[] VALUE_HOLDERS =
+ new FontVariant[VALID_VALUES.length];
+
+ FontVariant() {
+ this(-1);
+ }
+
+ private FontVariant(final int index) {
+ super(index);
+ }
+
+ String[] getValidValues() {
+ return VALID_VALUES;
+ }
+
+ PropertyValueConverter[] getValueHolders() {
+ return VALUE_HOLDERS;
+ }
+
+ PropertyValueConverter createValueHolder(final int valueIndex) {
+ return new FontVariant(valueIndex);
+ }
+ }
+
+ /**
+ * The implementation is based on CSS1 spec for
+ * <a href="http://www.w3.org/TR/CSS1#font-weight">'font-weight'</a>.
+ */
+ static final class FontWeight implements PropertyValueConverter {
+ private static final FontWeight BOLD =
+ new FontWeight("bold", Boolean.TRUE);
+ private static final FontWeight NORMAL =
+ new FontWeight("normal", Boolean.FALSE);
+
+ private static final String[] RELATIVE = {"bolder", "lighter"};
+ private static final String[] ABSOLUTE = {
+ "100", "200", "300", "400", "500", "600", "700", "800", "900"
+ };
+ private static final int BOLD_THRESHOLD = 5;
+
+ private final String cssValue;
+ private final Boolean value;
+
+ FontWeight() {
+ cssValue = null;
+ value = null;
+ }
+
+ private FontWeight(final String cssValue, final Boolean value) {
+ this.cssValue = cssValue;
+ this.value = value;
+ }
+
+ public PropertyValueConverter toCSS(final Object value) {
+ if (value instanceof Boolean) {
+ return ((Boolean)value).booleanValue() ? BOLD : NORMAL;
+ }
+
+ if (BOLD.cssValue.equals(value)) {
+ return BOLD;
+ } else if (NORMAL.cssValue.equals(value)) {
+ return NORMAL;
+ }
+
+ for (int i = 0; i < ABSOLUTE.length; i++) {
+ if (ABSOLUTE[i].equals(value)) {
+ return new FontWeight((String)value,
+ Boolean.valueOf(i >= BOLD_THRESHOLD));
+ }
+ }
+
+ for (int i = 0; i < RELATIVE.length; i++) {
+ if (RELATIVE[i].equals(value)) {
+ return new FontWeight((String)value,
+ Boolean.valueOf(i == 0));
+ }
+ }
+
+ return null;
+ }
+
+ public Object fromCSS() {
+ return value;
+ }
+
+ public String toString() {
+ return cssValue;
+ }
+
+ }
+
+ /**
+ * The implementation is based on CSS1 spec for
+ * <a href="http://www.w3.org/TR/CSS1#font">'font'</a>.
+ */
+ static final class FontExpander implements ShorthandPropertyExpander {
+ private static final Pattern WS_SPLIT = Pattern.compile("\\s+");
+
+ public void parseAndExpandProperty(final MutableAttributeSet attrs,
+ final String value) {
+ parse(value.trim(), attrs);
+ }
+
+ private void parse(final String value,
+ final MutableAttributeSet attrs) {
+ final String[] parts = WS_SPLIT.split(value);
+ FontStyle style = null;
+ FontVariant variant = null;
+ FontWeight weight = null;
+ FontSize size = null;
+ FloatValue lineHeight = null;
+ int slashIndex = -1;
+ int i;
+ for (i = 0; i < parts.length; i++) {
+ if (i > 3) {
+ return;
+ }
+
+ if ("normal".equals(parts[i])) {
+ continue;
+ }
+
+ if (style == null) {
+ style = (FontStyle)Attribute.FONT_STYLE.getConverter()
+ .toCSS(parts[i]);
+ if (style != null) {
+ continue;
+ }
+ }
+
+ if (variant == null) {
+ variant =
+ (FontVariant)Attribute.FONT_VARIANT.getConverter()
+ .toCSS(parts[i]);
+ if (variant != null) {
+ continue;
+ }
+ }
+
+ if (weight == null) {
+ weight = (FontWeight)Attribute.FONT_WEIGHT.getConverter()
+ .toCSS(parts[i]);
+ if (weight != null) {
+ continue;
+ }
+ }
+
+ if (size == null) {
+ slashIndex = parts[i].indexOf('/');
+ final String sizeValue = slashIndex == -1
+ ? parts[i]
+ : parts[i].substring(0, slashIndex);
+ size = (FontSize)Attribute.FONT_SIZE.getConverter()
+ .toCSS(sizeValue);
+ if (size != null) {
+ break;
+ }
+ }
+
+ return;
+ }
+
+ if (size == null) {
+ return;
+ }
+
+ String lhValue;
+ if (slashIndex > 0) {
+ if (parts[i].length() > slashIndex + 1) {
+ lhValue = parts[i].substring(slashIndex + 1);
+ i += 1;
+ } else {
+ if (parts.length <= i + 1) {
+ return;
+ }
+ lhValue = parts[i + 1];
+ i += 2;
+ }
+ } else {
+ if (parts.length <= i + 1) {
+ return;
+ }
+
+ if (parts[i + 1].length() == 1
+ && parts[i + 1].charAt(0) == '/') {
+
+ if (parts.length <= i + 2) {
+ return;
+ }
+ lhValue = parts[i + 2];
+ i += 3;
+ } else if (parts[i + 1].startsWith("/")) {
+ lhValue = parts[i + 1].substring(1);
+ i += 2;
+ } else {
+ lhValue = Attribute.LINE_HEIGHT.getDefaultValue();
+ i += 1;
+ }
+ }
+ lineHeight = (FloatValue)Attribute.LINE_HEIGHT.getConverter()
+ .toCSS(lhValue);
+ if (lineHeight == null) {
+ return;
+ }
+
+ StringBuffer family = new StringBuffer();
+ family.append(parts[i++]);
+ for (; i < parts.length; i++) {
+ family.append(' ')
+ .append(parts[i]);
+ }
+ if (family.length() == 0) {
+ return;
+ }
+
+
+ if (style == null) {
+ style = (FontStyle)Attribute.FONT_STYLE.getConverter()
+ .toCSS(Attribute.FONT_STYLE.getDefaultValue());
+ }
+
+ if (variant == null) {
+ variant = (FontVariant)Attribute.FONT_VARIANT.getConverter()
+ .toCSS(Attribute.FONT_VARIANT.getDefaultValue());
+ }
+
+ if (weight == null) {
+ weight = (FontWeight)Attribute.FONT_WEIGHT.getConverter()
+ .toCSS(Attribute.FONT_WEIGHT.getDefaultValue());
+ }
+
+// if (lineHeight == null ) {
+// lineHeight = (FloatValue)Attribute.LINE_HEIGHT.getConverter()
+// .toCSS(Attribute.LINE_HEIGHT.getDefaultValue());
+// }
+
+ attrs.addAttribute(Attribute.FONT_STYLE, style);
+ attrs.addAttribute(Attribute.FONT_VARIANT, variant);
+ attrs.addAttribute(Attribute.FONT_WEIGHT, weight);
+ attrs.addAttribute(Attribute.FONT_SIZE, size);
+ attrs.addAttribute(Attribute.LINE_HEIGHT, lineHeight);
+ attrs.addAttribute(Attribute.FONT_FAMILY, family.toString());
+ }
+ }
+
+ /**
+ * The implementation is based on CSS1 spec for
+ * <a href="http://www.w3.org/TR/CSS1#text-align">'text-align'</a>.
+ */
+ static final class TextAlign implements PropertyValueConverter {
+ private static final TextAlign LEFT =
+ new TextAlign("left", new Integer(StyleConstants.ALIGN_LEFT));
+ private static final TextAlign CENTER =
+ new TextAlign("center", new Integer(StyleConstants.ALIGN_CENTER));
+ private static final TextAlign RIGHT =
+ new TextAlign("right", new Integer(StyleConstants.ALIGN_RIGHT));
+ private static final TextAlign JUSTIFY =
+ new TextAlign("justify",
+ new Integer(StyleConstants.ALIGN_JUSTIFIED));
+
+ private final String align;
+ private final Integer justification;
+
+ TextAlign() {
+ this.align = null;
+ this.justification = null;
+ }
+
+ private TextAlign(final String align, final Integer justification) {
+ this.align = align;
+ this.justification = justification;
+ }
+
+ public String toString() {
+ return align;
+ }
+
+ public PropertyValueConverter toCSS(final Object value) {
+ return value instanceof Integer
+ ? convertIntegerValue((Integer)value)
+ : convertStringValue((String)value);
+ }
+
+ public Object fromCSS() {
+ return justification;
+ }
+
+ private PropertyValueConverter convertIntegerValue(final Integer value) {
+ switch (value.intValue()) {
+ case StyleConstants.ALIGN_LEFT:
+ return LEFT;
+
+ case StyleConstants.ALIGN_CENTER:
+ return CENTER;
+
+ case StyleConstants.ALIGN_RIGHT:
+ return RIGHT;
+
+ case StyleConstants.ALIGN_JUSTIFIED:
+ return JUSTIFY;
+
+ default:
+ return LEFT;
+ }
+ }
+
+ private PropertyValueConverter convertStringValue(final String value) {
+ if (LEFT.align.equals(value)) {
+ return LEFT;
+ }
+ if (CENTER.align.equals(value)) {
+ return CENTER;
+ }
+ if (RIGHT.align.equals(value)) {
+ return RIGHT;
+ }
+ if (JUSTIFY.align.equals(value)) {
+ return JUSTIFY;
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Storage for
+ * <a href="http://www.w3.org/TR/CSS1#length-units">Length Units</a>
+ * of float type.
+ */
+ static class FloatValue extends Length {
+ static final FloatValue factory = new FloatValue();
+
+ private static final Float ZERO = new Float(0);
+
+ private final Float theValue;
+
+ FloatValue() {
+ super();
+ theValue = null;
+ }
+
+ protected FloatValue(final String strValue,
+ final float theValue,
+ final int units) {
+ this(strValue, new Float(theValue), units);
+ }
+
+ protected FloatValue(final String strValue,
+ final Float theValue,
+ final int units) {
+ super(strValue, units);
+ this.theValue = theValue;
+ }
+
+ public PropertyValueConverter toCSS(final Object value) {
+ if (value instanceof Float) {
+ return new FloatValue(value.toString() + "pt", (Float)value,
+ RELATIVE_UNITS_UNDEFINED);
+ }
+ return super.toCSS(value);
+ }
+
+ PropertyValueConverter create(final String strValue,
+ final float theValue,
+ final int rUnits) {
+ return new FloatValue(strValue, theValue, rUnits);
+ }
+
+ Object resolveRelativeValue(final View view) {
+ if (view == null) {
+ return ZERO;
+ }
+
+ final AttributeSet attr = view.getAttributes();
+
+ switch (relativeUnits) {
+ case RELATIVE_UNITS_EM:
+ case RELATIVE_UNITS_EX:
+ final Object fs = attr.getAttribute(Attribute.FONT_SIZE);
+ final float fontSize = fs != null
+ ? ((Length)fs).floatValue(view)
+ : FontSize.getDefaultValue().floatValue();
+
+ float result = fontSize * theValue.floatValue();
+ if (relativeUnits == RELATIVE_UNITS_EX) {
+ result /= 2;
+ }
+ return new Float(result);
+
+ case RELATIVE_UNITS_PERCENTAGE:
+ View parent = view.getParent();
+ if (!(parent instanceof BoxView)) {
+ return ZERO;
+ }
+ float width = ((BoxView)parent).getWidth();
+ if (width >= Integer.MAX_VALUE) {
+ return ZERO;
+ }
+
+ return new Float(width * theValue.floatValue() / 100);
+
+ default:
+ System.err.println("FloatValue: can't resolve relative value. "
+ + "Unknown relative unit");
+ }
+ return ZERO;
+ }
+
+ Object getValue(final View view) {
+ return theValue;
+ }
+ }
+
+ /**
+ * The implementation is based on CSS1 spec for
+ * <a href="http://www.w3.org/TR/CSS1#width">'width'</a>.
+ */
+ static class Width extends FloatValue {
+ static final Width auto =
+ new Width("auto", 0, RELATIVE_UNITS_UNDEFINED);
+
+ Width() {
+ super();
+ }
+
+ Width(final String strValue, final float theValue, final int units) {
+ super(strValue, theValue, units);
+ }
+
+ public PropertyValueConverter toCSS(final Object value) {
+ if ("auto".equals(value)) {
+ return auto;
+ }
+ return super.toCSS(value);
+ }
+
+ PropertyValueConverter create(final String strValue,
+ final float theValue, final int rUnits) {
+ if (theValue < 0) {
+ return null;
+ }
+ return super.create(strValue, theValue, rUnits);
+ }
+ }
+
+ static final class Height extends Width {
+ PropertyValueConverter create(final String strValue,
+ final float theValue, final int rUnits) {
+ if (rUnits == RELATIVE_UNITS_PERCENTAGE) {
+ return null;
+ }
+ return super.create(strValue, theValue, rUnits);
+ }
+ }
+
+ /**
+ * The implementation is based on CSS1 spec for
+ * <a href="http://www.w3.org/TR/CSS1#line-height">'line-height'</a>.
+ */
+ static final class LineHeight extends FloatValue
+ implements RelativeValueResolver {
+
+ static final int RELATIVE_UNITS_NUMBER = 20;
+
+ static final Length normal = new LineHeight("normal", 1,
+ RELATIVE_UNITS_NUMBER);
+
+ private LineHeight(final String strValue,
+ final float theValue,
+ final int units) {
+ super(strValue, theValue, units);
+ }
+
+ public PropertyValueConverter toCSS(final Object value) {
+ if ("normal".equals(value)) {
+ return normal;
+ }
+ if (value instanceof String) {
+ final String sValue = (String)value;
+
+ return NUMBER_PATTERN.matcher(sValue).matches()
+ ? create(sValue, Float.parseFloat(sValue),
+ RELATIVE_UNITS_NUMBER)
+ : super.toCSS(value);
+ }
+ return super.toCSS(value);
+ }
+
+ public Object getComputedValue(final View view) {
+ if (relativeUnits == RELATIVE_UNITS_UNDEFINED
+ || relativeUnits == RELATIVE_UNITS_NUMBER) {
+
+ return this;
+ }
+ return new FloatValue(sValue,
+ (Float)resolveRelativeValue(view),
+ RELATIVE_UNITS_UNDEFINED);
+ }
+
+ PropertyValueConverter create(final String strValue,
+ final float theValue,
+ final int rUnits) {
+ if (theValue < 0) {
+ return null;
+ }
+ return super.create(strValue, theValue, rUnits);
+ }
+ }
+
+ /**
+ * The implementation is based on CSS1 spec for
+ * <a href="http://www.w3.org/TR/CSS1#list-style-type">'list-style-type'</a>.
+ */
+ static final class ListStyleType extends FixedSetValues {
+ static final ListStyleType factory = new ListStyleType(0);
+
+ static final int LIST_STYLE_DISC = 0;
+ static final int LIST_STYLE_CIRCLE = 1;
+ static final int LIST_STYLE_SQUARE = 2;
+ static final int LIST_STYLE_DECIMAL = 3;
+
+ static final int LIST_STYLE_LOWER_ROMAN = 4;
+ static final int LIST_STYLE_UPPER_ROMAN = 5;
+ static final int LIST_STYLE_LOWER_ALPHA = 6;
+ static final int LIST_STYLE_UPPER_ALPHA = 7;
+
+ static final int LIST_STYLE_NONE = 8;
+
+ private static final String[] VALID_VALUES = {
+ "disc", "circle", "square", "decimal",
+ "lower-roman", "upper-roman", "lower-alpha", "upper-alpha",
+ "none"
+ };
+ private static final ListStyleType[] VALUE_HOLDERS =
+ new ListStyleType[VALID_VALUES.length];
+
+ static {
+ VALUE_HOLDERS[0] = factory;
+ }
+
+ private ListStyleType(final int index) {
+ super(index);
+ }
+
+ String[] getValidValues() {
+ return VALID_VALUES;
+ }
+
+ PropertyValueConverter[] getValueHolders() {
+ return VALUE_HOLDERS;
+ }
+
+ PropertyValueConverter createValueHolder(final int valueIndex) {
+ return new ListStyleType(valueIndex);
+ }
+ }
+
+ /**
+ * The implementation is based on CSS1 spec for
+ * <a href="http://www.w3.org/TR/CSS1#list-style-position">'list-style-position'</a>.
+ */
+ static final class ListStylePosition extends FixedSetValues {
+ static final ListStylePosition factory = new ListStylePosition(0);
+
+ private static final String[] VALID_VALUES = {
+ "outside", "inside"
+ };
+ private static final ListStylePosition[] VALUE_HOLDERS =
+ new ListStylePosition[VALID_VALUES.length];
+
+ static {
+ VALUE_HOLDERS[0] = factory;
+ }
+
+ private ListStylePosition(final int index) {
+ super(index);
+ }
+
+ String[] getValidValues() {
+ return VALID_VALUES;
+ }
+
+ PropertyValueConverter[] getValueHolders() {
+ return VALUE_HOLDERS;
+ }
+
+ PropertyValueConverter createValueHolder(final int valueIndex) {
+ return new ListStylePosition(valueIndex);
+ }
+ }
+
+ /**
+ * The implementation is based on CSS1 spec for
+ * <a href="http://www.w3.org/TR/CSS1#list-style">'list-style'</a>.
+ */
+ static final class ListStyleExpander implements ShorthandPropertyExpander {
+ private static final Pattern WS_SPLIT = Pattern.compile("\\s+");
+ // pattern is based on CSS1, http://www.w3.org/TR/CSS1#url format
+ static final Pattern URL_PATTERN =
+ Pattern.compile("url\\(\\s*((?:\"|')?+).+?\\1\\s*\\)");
+
+ public void parseAndExpandProperty(final MutableAttributeSet attrs,
+ final String value) {
+ String[] parts = split(value.trim());
+ if (parts == null || parts.length > 3) {
+ return;
+ }
+
+ ListStyleType type = null;
+ ListStylePosition position = null;
+ ImageValue image = null;
+
+ for (int i = 0; i < parts.length; i++) {
+ if (type == null) {
+ type = (ListStyleType)ListStyleType.factory.toCSS(parts[i]);
+ if (type != null) {
+ continue;
+ }
+ } else {
+ if ("none".equals(type.toString()) && image == null) {
+ ListStyleType typeTry =
+ (ListStyleType)ListStyleType.factory.toCSS(parts[i]);
+ if (typeTry != null) {
+ type = typeTry;
+ image = ImageValue.NONE;
+ continue;
+ }
+ }
+ }
+
+ if (position == null) {
+ position =
+ (ListStylePosition)ListStylePosition.factory
+ .toCSS(parts[i]);
+ if (position != null) {
+ continue;
+ }
+ }
+
+ if (image == null) {
+ image = (ImageValue)ImageValue.NONE.toCSS(parts[i]);
+ if (image != null) {
+ continue;
+ }
+ }
+
+ // An invalid value encountered
+ return;
+ }
+
+ attrs.addAttribute(Attribute.LIST_STYLE_TYPE,
+ type != null ? type : ListStyleType.factory);
+ attrs.addAttribute(Attribute.LIST_STYLE_POSITION,
+ position != null ? position
+ : ListStylePosition.factory);
+ attrs.addAttribute(Attribute.LIST_STYLE_IMAGE,
+ image != null ? image : ImageValue.NONE);
+ }
+
+ private static String[] split(final String value) {
+ if (value.indexOf("url(") != -1) {
+ Matcher matcher = URL_PATTERN.matcher(value);
+ if (!matcher.find()) {
+ return null;
+ }
+ final int urlStart = matcher.start();
+ final int urlEnd = matcher.end();
+ final String url = value.substring(urlStart, urlEnd);
+ final String rest = (value.substring(0, urlStart)
+ + value.substring(urlEnd)).trim();
+ if (rest.length() != 0) {
+ if (rest.indexOf("url(") != -1) {
+ return null;
+ }
+
+ String[] parts = WS_SPLIT.split(rest);
[... 742 lines stripped ...]
|