/* * {{{ header & license * Copyright (c) 2007 Wisconsin Court System * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * }}} */ package org.xhtmlrenderer.css.parser.property; import java.util.ArrayList; import java.util.BitSet; import java.util.Collections; import java.util.Iterator; import java.util.List; import org.w3c.dom.css.CSSPrimitiveValue; import org.xhtmlrenderer.css.constants.CSSName; import org.xhtmlrenderer.css.constants.IdentValue; import org.xhtmlrenderer.css.parser.CSSParseException; import org.xhtmlrenderer.css.parser.FSFunction; import org.xhtmlrenderer.css.parser.FSRGBColor; import org.xhtmlrenderer.css.parser.PropertyValue; import org.xhtmlrenderer.css.parser.Token; import org.xhtmlrenderer.css.sheet.PropertyDeclaration; public class PrimitivePropertyBuilders { // none | hidden | dotted | dashed | solid | double | groove | ridge | inset | outset public static final BitSet BORDER_STYLES = setFor( new IdentValue[] { IdentValue.NONE, IdentValue.HIDDEN, IdentValue.DOTTED, IdentValue.DASHED, IdentValue.SOLID, IdentValue.DOUBLE, IdentValue.GROOVE, IdentValue.RIDGE, IdentValue.INSET, IdentValue.OUTSET }); // thin | medium | thick public static final BitSet BORDER_WIDTHS = setFor( new IdentValue[] { IdentValue.THIN, IdentValue.MEDIUM, IdentValue.THICK }); // normal | small-caps | inherit public static final BitSet FONT_VARIANTS = setFor( new IdentValue[] { IdentValue.NORMAL, IdentValue.SMALL_CAPS }); // normal | italic | oblique | inherit public static final BitSet FONT_STYLES = setFor( new IdentValue[] { IdentValue.NORMAL, IdentValue.ITALIC, IdentValue.OBLIQUE }); public static final BitSet FONT_WEIGHTS = setFor( new IdentValue[] { IdentValue.NORMAL, IdentValue.BOLD, IdentValue.BOLDER, IdentValue.LIGHTER }); public static final BitSet PAGE_ORIENTATIONS = setFor( new IdentValue[] { IdentValue.AUTO, IdentValue.PORTRAIT, IdentValue.LANDSCAPE }); // inside | outside | inherit public static final BitSet LIST_STYLE_POSITIONS = setFor(new IdentValue[] { IdentValue.INSIDE, IdentValue.OUTSIDE }); // disc | circle | square | decimal // | decimal-leading-zero | lower-roman | upper-roman // | lower-greek | lower-latin | upper-latin | armenian // | georgian | lower-alpha | upper-alpha | none | inherit public static final BitSet LIST_STYLE_TYPES = setFor(new IdentValue[] { IdentValue.DISC, IdentValue.CIRCLE, IdentValue.SQUARE, IdentValue.DECIMAL, IdentValue.DECIMAL_LEADING_ZERO, IdentValue.LOWER_ROMAN, IdentValue.UPPER_ROMAN, IdentValue.LOWER_GREEK, IdentValue.LOWER_LATIN, IdentValue.UPPER_LATIN, IdentValue.ARMENIAN, IdentValue.GEORGIAN, IdentValue.LOWER_ALPHA, IdentValue.UPPER_ALPHA, IdentValue.NONE }); // repeat | repeat-x | repeat-y | no-repeat | inherit public static final BitSet BACKGROUND_REPEATS = setFor( new IdentValue[] { IdentValue.REPEAT, IdentValue.REPEAT_X, IdentValue.REPEAT_Y, IdentValue.NO_REPEAT }); // scroll | fixed | inherit public static final BitSet BACKGROUND_ATTACHMENTS = setFor( new IdentValue[] { IdentValue.SCROLL, IdentValue.FIXED }); // left | right | top | bottom | center public static final BitSet BACKGROUND_POSITIONS = setFor( new IdentValue[] { IdentValue.LEFT, IdentValue.RIGHT, IdentValue.TOP, IdentValue.BOTTOM, IdentValue.CENTER }); public static final BitSet ABSOLUTE_FONT_SIZES = setFor( new IdentValue[] { IdentValue.XX_SMALL, IdentValue.X_SMALL, IdentValue.SMALL, IdentValue.MEDIUM, IdentValue.LARGE, IdentValue.X_LARGE, IdentValue.XX_LARGE }); public static final BitSet RELATIVE_FONT_SIZES = setFor( new IdentValue[] { IdentValue.SMALLER, IdentValue.LARGER }); public static final PropertyBuilder COLOR = new GenericColor(); public static final PropertyBuilder BORDER_STYLE = new GenericBorderStyle(); public static final PropertyBuilder BORDER_WIDTH = new GenericBorderWidth(); public static final PropertyBuilder MARGIN = new LengthLikeWithAuto(); public static final PropertyBuilder PADDING = new NonNegativeLengthLike(); private static BitSet setFor(IdentValue[] values) { BitSet result = new BitSet(IdentValue.getIdentCount()); for (int i = 0; i < values.length; i++) { IdentValue ident = (IdentValue)values[i]; result.set(ident.FS_ID); } return result; } private static abstract class SingleIdent extends AbstractPropertyBuilder { protected abstract BitSet getAllowed(); public List buildDeclarations( CSSName cssName, List values, int origin, boolean important, boolean inheritAllowed) { checkValueCount(cssName, 1, values.size()); CSSPrimitiveValue value = (CSSPrimitiveValue)values.get(0); checkInheritAllowed(value, inheritAllowed); if (value.getCssValueType() != CSSPrimitiveValue.CSS_INHERIT) { checkIdentType(cssName, value); IdentValue ident = checkIdent(cssName, value); checkValidity(cssName, getAllowed(), ident); } return Collections.singletonList( new PropertyDeclaration(cssName, value, important, origin)); } } private static class GenericColor extends AbstractPropertyBuilder { private static final BitSet ALLOWED = setFor( new IdentValue[] { IdentValue.TRANSPARENT }); public List buildDeclarations( CSSName cssName, List values, int origin, boolean important, boolean inheritAllowed) { checkValueCount(cssName, 1, values.size()); CSSPrimitiveValue value = (CSSPrimitiveValue)values.get(0); checkInheritAllowed(value, inheritAllowed); if (value.getCssValueType() != CSSPrimitiveValue.CSS_INHERIT) { checkIdentOrColorType(cssName, value); if (value.getPrimitiveType() == CSSPrimitiveValue.CSS_IDENT) { FSRGBColor color = Conversions.getColor(value.getStringValue()); if (color != null) { return Collections.singletonList( new PropertyDeclaration( cssName, new PropertyValue(color), important, origin)); } IdentValue ident = checkIdent(cssName, value); checkValidity(cssName, ALLOWED, ident); } } return Collections.singletonList( new PropertyDeclaration(cssName, value, important, origin)); } } private static class GenericBorderStyle extends SingleIdent { protected BitSet getAllowed() { return BORDER_STYLES; } } private static class GenericBorderWidth extends AbstractPropertyBuilder { public List buildDeclarations( CSSName cssName, List values, int origin, boolean important, boolean inheritAllowed) { checkValueCount(cssName, 1, values.size()); PropertyValue value = (PropertyValue)values.get(0); checkInheritAllowed(value, inheritAllowed); if (value.getCssValueType() != CSSPrimitiveValue.CSS_INHERIT) { checkIdentOrLengthType(cssName, value); if (value.getPrimitiveType() == CSSPrimitiveValue.CSS_IDENT) { IdentValue ident = checkIdent(cssName, value); checkValidity(cssName, BORDER_WIDTHS, ident); return Collections.singletonList( new PropertyDeclaration( cssName, Conversions.getBorderWidth(ident.toString()), important, origin)); } else { if (value.getFloatValue() < 0.0f) { throw new CSSParseException(cssName + " may not be negative", -1); } } } return Collections.singletonList( new PropertyDeclaration(cssName, value, important, origin)); } } private static abstract class LengthWithIdent extends AbstractPropertyBuilder { protected abstract BitSet getAllowed(); public List buildDeclarations( CSSName cssName, List values, int origin, boolean important, boolean inheritAllowed) { checkValueCount(cssName, 1, values.size()); PropertyValue value = (PropertyValue)values.get(0); checkInheritAllowed(value, inheritAllowed); if (value.getCssValueType() != CSSPrimitiveValue.CSS_INHERIT) { checkIdentOrLengthType(cssName, value); if (value.getPrimitiveType() == CSSPrimitiveValue.CSS_IDENT) { IdentValue ident = checkIdent(cssName, value); checkValidity(cssName, getAllowed(), ident); } else if (! isNegativeValuesAllowed() && value.getFloatValue() < 0.0f) { throw new CSSParseException(cssName + " may not be negative", -1); } } return Collections.singletonList( new PropertyDeclaration(cssName, value, important, origin)); } protected boolean isNegativeValuesAllowed() { return true; } } private static abstract class LengthLikeWithIdent extends AbstractPropertyBuilder { protected abstract BitSet getAllowed(); public List buildDeclarations( CSSName cssName, List values, int origin, boolean important, boolean inheritAllowed) { checkValueCount(cssName, 1, values.size()); PropertyValue value = (PropertyValue)values.get(0); checkInheritAllowed(value, inheritAllowed); if (value.getCssValueType() != CSSPrimitiveValue.CSS_INHERIT) { checkIdentLengthOrPercentType(cssName, value); if (value.getPrimitiveType() == CSSPrimitiveValue.CSS_IDENT) { IdentValue ident = checkIdent(cssName, value); checkValidity(cssName, getAllowed(), ident); } else if (! isNegativeValuesAllowed() && value.getFloatValue() < 0.0f) { throw new CSSParseException(cssName + " may not be negative", -1); } } return Collections.singletonList( new PropertyDeclaration(cssName, value, important, origin)); } protected boolean isNegativeValuesAllowed() { return true; } } private static class LengthLike extends AbstractPropertyBuilder { public List buildDeclarations( CSSName cssName, List values, int origin, boolean important, boolean inheritAllowed) { checkValueCount(cssName, 1, values.size()); PropertyValue value = (PropertyValue)values.get(0); checkInheritAllowed(value, inheritAllowed); if (value.getCssValueType() != CSSPrimitiveValue.CSS_INHERIT) { checkLengthOrPercentType(cssName, value); if (! isNegativeValuesAllowed() && value.getFloatValue() < 0.0f) { throw new CSSParseException(cssName + " may not be negative", -1); } } return Collections.singletonList( new PropertyDeclaration(cssName, value, important, origin)); } protected boolean isNegativeValuesAllowed() { return true; } } private static class NonNegativeLengthLike extends LengthLike { protected boolean isNegativeValuesAllowed() { return false; } } private static class ColOrRowSpan extends AbstractPropertyBuilder { public List buildDeclarations(CSSName cssName, List values, int origin, boolean important, boolean inheritAllowed) { checkValueCount(cssName, 1, values.size()); PropertyValue value = (PropertyValue)values.get(0); checkInheritAllowed(value, inheritAllowed); if (value.getCssValueType() != CSSPrimitiveValue.CSS_INHERIT) { checkNumberType(cssName, value); if (value.getFloatValue() < 1) { throw new CSSParseException("colspan/rowspan must be greater than zero", -1); } } return Collections.singletonList( new PropertyDeclaration(cssName, value, important, origin)); } } private static class PlainInteger extends AbstractPropertyBuilder { public List buildDeclarations( CSSName cssName, List values, int origin, boolean important, boolean inheritAllowed) { checkValueCount(cssName, 1, values.size()); PropertyValue value = (PropertyValue)values.get(0); checkInheritAllowed(value, inheritAllowed); if (value.getCssValueType() != CSSPrimitiveValue.CSS_INHERIT) { checkInteger(cssName, value); if (! isNegativeValuesAllowed() && value.getFloatValue() < 0.0f) { throw new CSSParseException(cssName + " may not be negative", -1); } } return Collections.singletonList( new PropertyDeclaration(cssName, value, important, origin)); } protected boolean isNegativeValuesAllowed() { return true; } } private static class Length extends AbstractPropertyBuilder { public List buildDeclarations( CSSName cssName, List values, int origin, boolean important, boolean inheritAllowed) { checkValueCount(cssName, 1, values.size()); PropertyValue value = (PropertyValue)values.get(0); checkInheritAllowed(value, inheritAllowed); if (value.getCssValueType() != CSSPrimitiveValue.CSS_INHERIT) { checkLengthType(cssName, value); if (! isNegativeValuesAllowed() && value.getFloatValue() < 0.0f) { throw new CSSParseException(cssName + " may not be negative", -1); } } return Collections.singletonList( new PropertyDeclaration(cssName, value, important, origin)); } protected boolean isNegativeValuesAllowed() { return true; } } /* private static class SingleString extends AbstractPropertyBuilder { public List buildDeclarations( CSSName cssName, List values, int origin, boolean important, boolean inheritAllowed) { checkValueCount(cssName, 1, values.size()); CSSPrimitiveValue value = (CSSPrimitiveValue)values.get(0); checkInheritAllowed(value, inheritAllowed); if (value.getCssValueType() != CSSPrimitiveValue.CSS_INHERIT) { checkStringType(cssName, value); } return Collections.singletonList( new PropertyDeclaration(cssName, value, important, origin)); } } */ /* private static abstract class SingleStringWithIdent extends AbstractPropertyBuilder { protected abstract BitSet getAllowed(); public List buildDeclarations( CSSName cssName, List values, int origin, boolean important, boolean inheritAllowed) { checkValueCount(cssName, 1, values.size()); CSSPrimitiveValue value = (CSSPrimitiveValue)values.get(0); checkInheritAllowed(value, inheritAllowed); if (value.getCssValueType() != CSSPrimitiveValue.CSS_INHERIT) { checkIdentOrString(cssName, value); if (value.getPrimitiveType() == CSSPrimitiveValue.CSS_IDENT) { IdentValue ident = checkIdent(cssName, value); checkValidity(cssName, getAllowed(), ident); } } return Collections.singletonList( new PropertyDeclaration(cssName, value, important, origin)); } } */ /* private static class SingleStringWithNone extends SingleStringWithIdent { private static final BitSet ALLOWED = setFor(new IdentValue[] { IdentValue.NONE }); protected BitSet getAllowed() { return ALLOWED; } } */ private static class LengthLikeWithAuto extends LengthLikeWithIdent { // <length> | <percentage> | auto | inherit private static final BitSet ALLOWED = setFor( new IdentValue[] { IdentValue.AUTO }); protected BitSet getAllowed() { return ALLOWED; } } private static class LengthWithNormal extends LengthWithIdent { // <length> | normal | inherit private static final BitSet ALLOWED = setFor( new IdentValue[] { IdentValue.NORMAL }); protected BitSet getAllowed() { return ALLOWED; } } private static class LengthLikeWithNone extends LengthLikeWithIdent { // <length> | <percentage> | none | inherit private static final BitSet ALLOWED = setFor( new IdentValue[] { IdentValue.NONE }); protected BitSet getAllowed() { return ALLOWED; } } private static class GenericURIWithNone extends AbstractPropertyBuilder { // <uri> | none | inherit private static final BitSet ALLOWED = setFor(new IdentValue[] { IdentValue.NONE }); public List buildDeclarations( CSSName cssName, List values, int origin, boolean important, boolean inheritAllowed) { checkValueCount(cssName, 1, values.size()); CSSPrimitiveValue value = (CSSPrimitiveValue)values.get(0); checkInheritAllowed(value, inheritAllowed); if (value.getCssValueType() != CSSPrimitiveValue.CSS_INHERIT) { checkIdentOrURIType(cssName, value); if (value.getPrimitiveType() == CSSPrimitiveValue.CSS_IDENT) { IdentValue ident = checkIdent(cssName, value); checkValidity(cssName, ALLOWED, ident); } } return Collections.singletonList( new PropertyDeclaration(cssName, value, important, origin)); } } public static class BackgroundAttachment extends SingleIdent { protected BitSet getAllowed() { return BACKGROUND_ATTACHMENTS; } } public static class BackgroundColor extends GenericColor { } public static class BackgroundImage extends GenericURIWithNone { } public static class BackgroundPosition extends AbstractPropertyBuilder { public List buildDeclarations( CSSName cssName, List values, int origin, boolean important, boolean inheritAllowed) { checkValueCount(cssName, 1, 2, values.size()); CSSPrimitiveValue first = (CSSPrimitiveValue)values.get(0); CSSPrimitiveValue second = null; if (values.size() == 2) { second = (CSSPrimitiveValue)values.get(1); } checkInheritAllowed(first, inheritAllowed); if (values.size() == 1 && first.getCssValueType() == CSSPrimitiveValue.CSS_INHERIT) { return Collections.singletonList( new PropertyDeclaration(cssName, first, important, origin)); } if (second != null) { checkInheritAllowed(second, false); } checkIdentLengthOrPercentType(cssName, first); if (second == null) { if (isLength(first) || first.getPrimitiveType() == CSSPrimitiveValue.CSS_PERCENTAGE) { List responseValues = new ArrayList(2); responseValues.add(first); responseValues.add(new PropertyValue( CSSPrimitiveValue.CSS_PERCENTAGE, 50.0f, "50%")); return Collections.singletonList(new PropertyDeclaration( CSSName.BACKGROUND_POSITION, new PropertyValue(responseValues), important, origin)); } } else { checkIdentLengthOrPercentType(cssName, second); } IdentValue firstIdent = null; if (first.getPrimitiveType() == CSSPrimitiveValue.CSS_IDENT) { firstIdent = checkIdent(cssName, first); checkValidity(cssName, getAllowed(), firstIdent); } IdentValue secondIdent = null; if (second == null) { secondIdent = IdentValue.CENTER; } else if (second.getPrimitiveType() == CSSPrimitiveValue.CSS_IDENT) { secondIdent = checkIdent(cssName, second); checkValidity(cssName, getAllowed(), secondIdent); } if (firstIdent == null && secondIdent == null) { return Collections.singletonList(new PropertyDeclaration( CSSName.BACKGROUND_POSITION, new PropertyValue(values), important, origin)); } else if (firstIdent != null && secondIdent != null) { if (firstIdent == IdentValue.TOP || firstIdent == IdentValue.BOTTOM || secondIdent == IdentValue.LEFT || secondIdent == IdentValue.RIGHT) { IdentValue temp = firstIdent; firstIdent = secondIdent; secondIdent = temp; } checkIdentPosition(cssName, firstIdent, secondIdent); return createTwoPercentValueResponse( getPercentForIdent(firstIdent), getPercentForIdent(secondIdent), important, origin); } else { checkIdentPosition(cssName, firstIdent, secondIdent); List responseValues = new ArrayList(2); if (firstIdent == null) { responseValues.add(first); responseValues.add(createValueForIdent(secondIdent)); } else { responseValues.add(createValueForIdent(firstIdent)); responseValues.add(second); } return Collections.singletonList(new PropertyDeclaration( CSSName.BACKGROUND_POSITION, new PropertyValue(responseValues), important, origin)); } } private void checkIdentPosition(CSSName cssName, IdentValue firstIdent, IdentValue secondIdent) { if (firstIdent == IdentValue.TOP || firstIdent == IdentValue.BOTTOM || secondIdent == IdentValue.LEFT || secondIdent == IdentValue.RIGHT) { throw new CSSParseException("Invalid combination of keywords in " + cssName, -1); } } private float getPercentForIdent(IdentValue ident) { float percent = 0.0f; if (ident == IdentValue.CENTER) { percent = 50.f; } else if (ident == IdentValue.BOTTOM || ident == IdentValue.RIGHT) { percent = 100.0f; } return percent; } private PropertyValue createValueForIdent(IdentValue ident) { float percent = getPercentForIdent(ident); return new PropertyValue( CSSPrimitiveValue.CSS_PERCENTAGE, percent, percent + "%"); } private List createTwoPercentValueResponse( float percent1, float percent2, boolean important, int origin) { PropertyValue value1 = new PropertyValue( CSSPrimitiveValue.CSS_PERCENTAGE, percent1, percent1 + "%"); PropertyValue value2 = new PropertyValue( CSSPrimitiveValue.CSS_PERCENTAGE, percent2, percent2 + "%"); List values = new ArrayList(2); values.add(value1); values.add(value2); PropertyDeclaration result = new PropertyDeclaration( CSSName.BACKGROUND_POSITION, new PropertyValue(values), important, origin); return Collections.singletonList(result); } private BitSet getAllowed() { return BACKGROUND_POSITIONS; } } public static class BackgroundRepeat extends SingleIdent { protected BitSet getAllowed() { return BACKGROUND_REPEATS; } } public static class BorderCollapse extends SingleIdent { // collapse | separate | inherit private static final BitSet ALLOWED = setFor( new IdentValue[] { IdentValue.COLLAPSE, IdentValue.SEPARATE }); protected BitSet getAllowed() { return ALLOWED; } } public static class BorderTopColor extends GenericColor { } public static class BorderRightColor extends GenericColor { } public static class BorderBottomColor extends GenericColor { } public static class BorderLeftColor extends GenericColor { } public static class BorderTopStyle extends GenericBorderStyle { } public static class BorderRightStyle extends GenericBorderStyle { } public static class BorderBottomStyle extends GenericBorderStyle { } public static class BorderLeftStyle extends GenericBorderStyle { } public static class BorderTopWidth extends GenericBorderWidth { } public static class BorderRightWidth extends GenericBorderWidth { } public static class BorderBottomWidth extends GenericBorderWidth { } public static class BorderLeftWidth extends GenericBorderWidth { } public static class Bottom extends LengthLikeWithAuto { } public static class CaptionSide extends SingleIdent { // top | bottom | inherit private static final BitSet ALLOWED = setFor( new IdentValue[] { IdentValue.TOP, IdentValue.BOTTOM }); protected BitSet getAllowed() { return ALLOWED; } } public static class Clear extends SingleIdent { // none | left | right | both | inherit private static final BitSet ALLOWED = setFor( new IdentValue[] { IdentValue.NONE, IdentValue.LEFT, IdentValue.RIGHT, IdentValue.BOTH }); protected BitSet getAllowed() { return ALLOWED; } } public static class Color extends GenericColor { } public static class Cursor extends SingleIdent { // [ [<uri> ,]* [ auto | crosshair | default | pointer | move | e-resize // | ne-resize | nw-resize | n-resize | se-resize | sw-resize | s-resize // | w-resize | text | wait | help | progress ] ] | inherit private static final BitSet ALLOWED = setFor( new IdentValue[] { IdentValue.AUTO, IdentValue.CROSSHAIR, IdentValue.DEFAULT, IdentValue.POINTER, IdentValue.MOVE, IdentValue.E_RESIZE, IdentValue.NE_RESIZE, IdentValue.NW_RESIZE, IdentValue.N_RESIZE, IdentValue.SE_RESIZE, IdentValue.SW_RESIZE, IdentValue.S_RESIZE, IdentValue.W_RESIZE, IdentValue.TEXT, IdentValue.WAIT, IdentValue.HELP, IdentValue.PROGRESS}); protected BitSet getAllowed() { return ALLOWED; } } public static class Display extends SingleIdent { // inline | block | list-item | run-in | inline-block | table | inline-table // | table-row-group | table-header-group // | table-footer-group | table-row | table-column-group | table-column // | table-cell | table-caption | none | inherit private static final BitSet ALLOWED = setFor( new IdentValue[] { IdentValue.INLINE, IdentValue.BLOCK, IdentValue.LIST_ITEM, /* IdentValue.RUN_IN, */ IdentValue.INLINE_BLOCK, IdentValue.TABLE, IdentValue.INLINE_TABLE, IdentValue.TABLE_ROW_GROUP, IdentValue.TABLE_HEADER_GROUP, IdentValue.TABLE_FOOTER_GROUP, IdentValue.TABLE_ROW, IdentValue.TABLE_COLUMN_GROUP, IdentValue.TABLE_COLUMN, IdentValue.TABLE_CELL, IdentValue.TABLE_CAPTION, IdentValue.NONE }); protected BitSet getAllowed() { return ALLOWED; } } public static class EmptyCells extends SingleIdent { // show | hide | inherit private static final BitSet ALLOWED = setFor( new IdentValue[] { IdentValue.SHOW, IdentValue.HIDE }); protected BitSet getAllowed() { return ALLOWED; } } public static class Float extends SingleIdent { // left | right | none | inherit private static final BitSet ALLOWED = setFor( new IdentValue[] { IdentValue.LEFT, IdentValue.RIGHT, IdentValue.NONE }); protected BitSet getAllowed() { return ALLOWED; } } public static class FontFamily extends AbstractPropertyBuilder { // [[ <family-name> | <generic-family> ] [, <family-name>| <generic-family>]* ] | inherit public List buildDeclarations( CSSName cssName, List values, int origin, boolean important, boolean inheritAllowed) { if (values.size() == 1) { CSSPrimitiveValue value = (CSSPrimitiveValue)values.get(0); checkInheritAllowed(value, inheritAllowed); if (value.getCssValueType() == CSSPrimitiveValue.CSS_INHERIT) { return Collections.singletonList( new PropertyDeclaration(cssName, value, important, origin)); } } // Both Opera and Firefox parse "Century Gothic" Arial sans-serif as // [Century Gothic], [Arial sans-serif] (i.e. the comma is assumed // after a string). Seems wrong per the spec, but FF (at least) // does it in standards mode so we do too. List consecutiveIdents = new ArrayList(); List normalized = new ArrayList(values.size()); for (Iterator i = values.iterator(); i.hasNext(); ) { PropertyValue value = (PropertyValue)i.next(); Token operator = value.getOperator(); if (operator != null && operator != Token.TK_COMMA) { throw new CSSParseException("Invalid font-family definition", -1); } if (operator != null) { if (consecutiveIdents.size() > 0) { normalized.add(concat(consecutiveIdents, ' ')); consecutiveIdents.clear(); } } checkInheritAllowed(value, false); short type = value.getPrimitiveType(); if (type == CSSPrimitiveValue.CSS_STRING) { if (consecutiveIdents.size() > 0) { normalized.add(concat(consecutiveIdents, ' ')); consecutiveIdents.clear(); } normalized.add(value.getStringValue()); } else if (type == CSSPrimitiveValue.CSS_IDENT) { consecutiveIdents.add(value.getStringValue()); } else { throw new CSSParseException("Invalid font-family definition", -1); } } if (consecutiveIdents.size() > 0) { normalized.add(concat(consecutiveIdents, ' ')); } String text = concat(normalized, ','); PropertyValue result = new PropertyValue( CSSPrimitiveValue.CSS_STRING, text, text); // HACK cssText can be wrong result.setStringArrayValue((String[]) normalized.toArray(new String[normalized.size()])); return Collections.singletonList( new PropertyDeclaration(cssName, result, important, origin)); } private String concat(List strings, char separator) { StringBuffer buf = new StringBuffer(64); for (Iterator i = strings.iterator(); i.hasNext(); ) { String s = (String)i.next(); buf.append(s); if (i.hasNext()) { buf.append(separator); } } return buf.toString(); } } public static class FontSize extends AbstractPropertyBuilder { // <absolute-size> | <relative-size> | <length> | <percentage> | inherit private static final BitSet ALLOWED; static { ALLOWED = new BitSet(IdentValue.getIdentCount()); ALLOWED.or(ABSOLUTE_FONT_SIZES); ALLOWED.or(RELATIVE_FONT_SIZES); } public List buildDeclarations( CSSName cssName, List values, int origin, boolean important, boolean inheritAllowed) { checkValueCount(cssName, 1, values.size()); PropertyValue value = (PropertyValue)values.get(0); checkInheritAllowed(value, inheritAllowed); if (value.getCssValueType() != CSSPrimitiveValue.CSS_INHERIT) { checkIdentLengthOrPercentType(cssName, value); if (value.getPrimitiveType() == CSSPrimitiveValue.CSS_IDENT) { IdentValue ident = checkIdent(cssName, value); checkValidity(cssName, ALLOWED, ident); } else if (value.getFloatValue() < 0.0f) { throw new CSSParseException("font-size may not be negative", -1); } } return Collections.singletonList( new PropertyDeclaration(cssName, value, important, origin)); } } public static class FontStyle extends SingleIdent { protected BitSet getAllowed() { return FONT_STYLES; } } public static class FontVariant extends SingleIdent { protected BitSet getAllowed() { return FONT_VARIANTS; } } public static class FontWeight extends AbstractPropertyBuilder { // normal | bold | bolder | lighter | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | inherit public List buildDeclarations( CSSName cssName, List values, int origin, boolean important, boolean inheritAllowed) { checkValueCount(cssName, 1, values.size()); PropertyValue value = (PropertyValue)values.get(0); checkInheritAllowed(value, inheritAllowed); if (value.getCssValueType() != CSSPrimitiveValue.CSS_INHERIT) { checkIdentOrNumberType(cssName, value); short type = value.getPrimitiveType(); if (type == CSSPrimitiveValue.CSS_IDENT) { checkIdentType(cssName, value); IdentValue ident = checkIdent(cssName, value); checkValidity(cssName, getAllowed(), ident); } else if (type == CSSPrimitiveValue.CSS_NUMBER) { IdentValue weight = Conversions.getNumericFontWeight(value.getFloatValue()); if (weight == null) { throw new CSSParseException(value + " is not a valid font weight", -1); } PropertyValue replacement = new PropertyValue( CSSPrimitiveValue.CSS_IDENT, weight.toString(), weight.toString()); replacement.setIdentValue(weight); return Collections.singletonList( new PropertyDeclaration(cssName, replacement, important, origin)); } } return Collections.singletonList( new PropertyDeclaration(cssName, value, important, origin)); } private BitSet getAllowed() { return FONT_WEIGHTS; } } public static class FSBorderSpacingHorizontal extends Length { } public static class FSBorderSpacingVertical extends Length { } public static class FSFontMetricSrc extends GenericURIWithNone { } public static class FSPageHeight extends LengthLikeWithAuto { protected boolean isNegativeValuesAllowed() { return false; } } public static class FSPageWidth extends LengthLikeWithAuto { protected boolean isNegativeValuesAllowed() { return false; } } public static class FSPageSequence extends SingleIdent { // start | auto private static final BitSet ALLOWED = setFor( new IdentValue[] { IdentValue.START, IdentValue.AUTO }); protected BitSet getAllowed() { return ALLOWED; } } public static class FSPageOrientation extends SingleIdent { protected BitSet getAllowed() { return PAGE_ORIENTATIONS; } } public static class FSPDFFontEmbed extends SingleIdent { // auto | embed private static final BitSet ALLOWED = setFor( new IdentValue[] { IdentValue.AUTO, IdentValue.EMBED }); protected BitSet getAllowed() { return ALLOWED; } } public static class FSPDFFontEncoding extends AbstractPropertyBuilder { public List buildDeclarations( CSSName cssName, List values, int origin, boolean important, boolean inheritAllowed) { checkValueCount(cssName, 1, values.size()); CSSPrimitiveValue value = (CSSPrimitiveValue)values.get(0); checkInheritAllowed(value, inheritAllowed); if (value.getCssValueType() != CSSPrimitiveValue.CSS_INHERIT) { checkIdentOrString(cssName, value); if (value.getPrimitiveType() == CSSPrimitiveValue.CSS_IDENT) { // Convert to string return Collections.singletonList( new PropertyDeclaration( cssName, new PropertyValue( CSSPrimitiveValue.CSS_STRING, value.getStringValue(), value.getCssText()), important, origin)); } } return Collections.singletonList( new PropertyDeclaration(cssName, value, important, origin)); } } public static class FSTableCellColspan extends ColOrRowSpan { } public static class FSTableCellRowspan extends ColOrRowSpan { } public static class FSTablePaginate extends SingleIdent { private static final BitSet ALLOWED = setFor( new IdentValue[] { IdentValue.PAGINATE, IdentValue.AUTO }); protected BitSet getAllowed() { return ALLOWED; } } public static class FSTextDecorationExtent extends SingleIdent { private static final BitSet ALLOWED = setFor( new IdentValue[] { IdentValue.LINE, IdentValue.BLOCK }); protected BitSet getAllowed() { return ALLOWED; } } public static class Height extends LengthLikeWithAuto { protected boolean isNegativeValuesAllowed() { return false; } } public static class FSKeepWithInline extends SingleIdent { // auto | keep private static final BitSet ALLOWED = setFor( new IdentValue[] { IdentValue.AUTO, IdentValue.KEEP }); protected BitSet getAllowed() { return ALLOWED; } } public static class Left extends LengthLikeWithAuto { } public static class LetterSpacing extends LengthWithNormal { } public static class LineHeight extends AbstractPropertyBuilder { // normal | <number> | <length> | <percentage> | inherit private static final BitSet ALLOWED = setFor( new IdentValue[] { IdentValue.NORMAL }); public List buildDeclarations( CSSName cssName, List values, int origin, boolean important, boolean inheritAllowed) { checkValueCount(cssName, 1, values.size()); PropertyValue value = (PropertyValue)values.get(0); checkInheritAllowed(value, inheritAllowed); if (value.getCssValueType() != CSSPrimitiveValue.CSS_INHERIT) { checkIdentLengthNumberOrPercentType(cssName, value); if (value.getPrimitiveType() == CSSPrimitiveValue.CSS_IDENT) { IdentValue ident = checkIdent(cssName, value); checkValidity(cssName, ALLOWED, ident); } else if (value.getFloatValue() < 0.0) { throw new CSSParseException("line-height may not be negative", -1); } } return Collections.singletonList( new PropertyDeclaration(cssName, value, important, origin)); } } public static class ListStyleImage extends GenericURIWithNone { } public static class ListStylePosition extends SingleIdent { protected BitSet getAllowed() { return LIST_STYLE_POSITIONS; } } public static class ListStyleType extends SingleIdent { protected BitSet getAllowed() { return LIST_STYLE_TYPES; } } public static class MarginTop extends LengthLikeWithAuto { } public static class MarginRight extends LengthLikeWithAuto { } public static class MarginBottom extends LengthLikeWithAuto { } public static class MarginLeft extends LengthLikeWithAuto { } public static class MaxHeight extends LengthLikeWithNone { protected boolean isNegativeValuesAllowed() { return false; } } public static class MaxWidth extends LengthLikeWithNone { protected boolean isNegativeValuesAllowed() { return false; } } public static class MinHeight extends NonNegativeLengthLike { } public static class MinWidth extends NonNegativeLengthLike { } public static class Orphans extends PlainInteger { protected boolean isNegativeValuesAllowed() { return false; } } public static class Overflow extends SingleIdent { // visible | hidden | scroll | auto | inherit private static final BitSet ALLOWED = setFor( new IdentValue[] { IdentValue.VISIBLE, IdentValue.HIDDEN, /* IdentValue.SCROLL, IdentValue.AUTO, */ }); // We only support visible or hidden for now protected BitSet getAllowed() { return ALLOWED; } } public static class PaddingTop extends NonNegativeLengthLike { } public static class PaddingRight extends NonNegativeLengthLike { } public static class PaddingBottom extends NonNegativeLengthLike { } public static class PaddingLeft extends NonNegativeLengthLike { } public static class PageBreakBefore extends SingleIdent { // auto | always | avoid | left | right | inherit private static final BitSet ALLOWED = setFor( new IdentValue[] { IdentValue.AUTO, IdentValue.ALWAYS, IdentValue.AVOID, IdentValue.LEFT, IdentValue.RIGHT }); protected BitSet getAllowed() { return ALLOWED; } } public static class Page extends AbstractPropertyBuilder { public List buildDeclarations( CSSName cssName, List values, int origin, boolean important, boolean inheritAllowed) { checkValueCount(cssName, 1, values.size()); CSSPrimitiveValue value = (CSSPrimitiveValue)values.get(0); checkInheritAllowed(value, inheritAllowed); if (value.getCssValueType() != CSSPrimitiveValue.CSS_INHERIT) { checkIdentType(cssName, value); if (! value.getStringValue().equals("auto")) { // Treat as string since it won't be a proper IdentValue value = new PropertyValue( CSSPrimitiveValue.CSS_STRING, value.getStringValue(), value.getCssText()); } } return Collections.singletonList( new PropertyDeclaration(cssName, value, important, origin)); } } public static class PageBreakAfter extends SingleIdent { // auto | always | avoid | left | right | inherit private static final BitSet ALLOWED = setFor( new IdentValue[] { IdentValue.AUTO, IdentValue.ALWAYS, IdentValue.AVOID, IdentValue.LEFT, IdentValue.RIGHT }); protected BitSet getAllowed() { return ALLOWED; } } public static class PageBreakInside extends SingleIdent { // avoid | auto | inherit private static final BitSet ALLOWED = setFor( new IdentValue[] { IdentValue.AVOID, IdentValue.AUTO }); protected BitSet getAllowed() { return ALLOWED; } } public static class Position extends AbstractPropertyBuilder { // static | relative | absolute | fixed | inherit private static final BitSet ALLOWED = setFor( new IdentValue[] { IdentValue.STATIC, IdentValue.RELATIVE, IdentValue.ABSOLUTE, IdentValue.FIXED }); public List buildDeclarations( CSSName cssName, List values, int origin, boolean important, boolean inheritAllowed) { checkValueCount(cssName, 1, values.size()); PropertyValue value = (PropertyValue)values.get(0); checkInheritAllowed(value, inheritAllowed); if (value.getCssValueType() != CSSPrimitiveValue.CSS_INHERIT) { if (value.getPrimitiveType() == CSSPrimitiveValue.CSS_IDENT) { checkIdentType(cssName, value); IdentValue ident = checkIdent(cssName, value); checkValidity(cssName, getAllowed(), ident); } else if (value.getPropertyValueType() == PropertyValue.VALUE_TYPE_FUNCTION) { FSFunction function = value.getFunction(); if (function.getName().equals("running")) { List params = function.getParameters(); if (params.size() == 1) { PropertyValue param = (PropertyValue)params.get(0); if (param.getPrimitiveType() != CSSPrimitiveValue.CSS_IDENT) { throw new CSSParseException("The running function takes an identifier as a parameter", -1); } } else { throw new CSSParseException("The running function takes one parameter", -1); } } else { throw new CSSParseException("Only the running function is supported here", -1); } } else { throw new CSSParseException("Value for " + cssName + " must be an identifier or function", -1); } } return Collections.singletonList( new PropertyDeclaration(cssName, value, important, origin)); } private BitSet getAllowed() { return ALLOWED; } } public static class Right extends LengthLikeWithAuto { } public static class Src extends GenericURIWithNone { } public static class Top extends LengthLikeWithAuto { } public static class TableLayout extends SingleIdent { // auto | fixed | inherit private static final BitSet ALLOWED = setFor( new IdentValue[] { IdentValue.AUTO, IdentValue.FIXED }); protected BitSet getAllowed() { return ALLOWED; } } public static class TextAlign extends SingleIdent { // left | right | center | justify | inherit private static final BitSet ALLOWED = setFor( new IdentValue[] { IdentValue.LEFT, IdentValue.RIGHT, IdentValue.CENTER, IdentValue.JUSTIFY }); protected BitSet getAllowed() { return ALLOWED; } } public static class TextDecoration extends AbstractPropertyBuilder { // none | [ underline || overline || line-through || blink ] | inherit private static final BitSet ALLOWED = setFor( new IdentValue[] { /* IdentValue.NONE, */ IdentValue.UNDERLINE, IdentValue.OVERLINE, IdentValue.LINE_THROUGH, /* IdentValue.BLINK */ }); private BitSet getAllowed() { return ALLOWED; } public List buildDeclarations( CSSName cssName, List values, int origin, boolean important, boolean inheritAllowed) { if (values.size() == 1) { CSSPrimitiveValue value = (CSSPrimitiveValue)values.get(0); boolean goWithSingle = false; if (value.getCssValueType() == CSSPrimitiveValue.CSS_INHERIT) { goWithSingle = true; } else { checkIdentType(CSSName.TEXT_DECORATION, value); IdentValue ident = checkIdent(cssName, value); if (ident == IdentValue.NONE) { goWithSingle = true; } } if (goWithSingle) { return Collections.singletonList( new PropertyDeclaration(cssName, value, important, origin)); } } for (Iterator i = values.iterator(); i.hasNext(); ) { PropertyValue value = (PropertyValue)i.next(); checkInheritAllowed(value, false); checkIdentType(cssName, value); IdentValue ident = checkIdent(cssName, value); if (ident == IdentValue.NONE) { throw new CSSParseException("Value none may not be used in this position", -1); } checkValidity(cssName, getAllowed(), ident); } return Collections.singletonList( new PropertyDeclaration(cssName, new PropertyValue(values), important, origin)); } } public static class TextIndent extends LengthLike { } public static class TextTransform extends SingleIdent { // capitalize | uppercase | lowercase | none | inherit private static final BitSet ALLOWED = setFor( new IdentValue[] { IdentValue.CAPITALIZE, IdentValue.UPPERCASE, IdentValue.LOWERCASE, IdentValue.NONE }); protected BitSet getAllowed() { return ALLOWED; } } public static class VerticalAlign extends LengthLikeWithIdent { // baseline | sub | super | top | text-top | middle // | bottom | text-bottom | <percentage> | <length> | inherit private static final BitSet ALLOWED = setFor( new IdentValue[] { IdentValue.BASELINE, IdentValue.SUB, IdentValue.SUPER, IdentValue.TOP, IdentValue.TEXT_TOP, IdentValue.MIDDLE, IdentValue.BOTTOM, IdentValue.TEXT_BOTTOM }); protected BitSet getAllowed() { return ALLOWED; } } public static class Visibility extends SingleIdent { // visible | hidden | collapse | inherit private static final BitSet ALLOWED = setFor( new IdentValue[] { IdentValue.VISIBLE, IdentValue.HIDDEN, IdentValue.COLLAPSE }); protected BitSet getAllowed() { return ALLOWED; } } public static class WhiteSpace extends SingleIdent { // normal | pre | nowrap | pre-wrap | pre-line | inherit private static final BitSet ALLOWED = setFor( new IdentValue[] { IdentValue.NORMAL, IdentValue.PRE, IdentValue.NOWRAP, IdentValue.PRE_WRAP, IdentValue.PRE_LINE}); protected BitSet getAllowed() { return ALLOWED; } } public static class Widows extends PlainInteger { protected boolean isNegativeValuesAllowed() { return false; } } public static class Width extends LengthLikeWithAuto { protected boolean isNegativeValuesAllowed() { return false; } } public static class WordSpacing extends LengthWithNormal { } public static class ZIndex extends AbstractPropertyBuilder { // auto | <integer> | inherit private static final BitSet ALLOWED = setFor( new IdentValue[] { IdentValue.AUTO }); public List buildDeclarations( CSSName cssName, List values, int origin, boolean important, boolean inheritAllowed) { checkValueCount(cssName, 1, values.size()); CSSPrimitiveValue value = (CSSPrimitiveValue)values.get(0); checkInheritAllowed(value, inheritAllowed); if (value.getCssValueType() != CSSPrimitiveValue.CSS_INHERIT) { checkIdentOrIntegerType(cssName, value); if (value.getPrimitiveType() == CSSPrimitiveValue.CSS_IDENT) { IdentValue ident = checkIdent(cssName, value); checkValidity(cssName, ALLOWED, ident); } } return Collections.singletonList( new PropertyDeclaration(cssName, value, important, origin)); } } }