introduce CSS function image(); parse content CSS property in own class

Signed-off-by: Florian Thienel <florian@thienel.org>
diff --git a/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/css/BatikBehaviorTest.java b/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/css/BatikBehaviorTest.java
index 8760d40..4737763 100644
--- a/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/css/BatikBehaviorTest.java
+++ b/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/css/BatikBehaviorTest.java
@@ -50,7 +50,7 @@
 		final Element element = new Element("plan");
 		final IElement before = styleSheet.getPseudoElementBefore(element);
 		final Styles beforeStyles = styleSheet.getStyles(before);
-		assertEquals("test", beforeStyles.getTextualContent(element).get(0));
+		assertEquals("test", beforeStyles.getTextualContent());
 		assertEquals(123.0f, beforeStyles.getFontSize(), 0.0f);
 	}
 
@@ -63,7 +63,7 @@
 		assertEquals(123.0f, styles.getFontSize(), 0.0f);
 		final IElement before = styleSheet.getPseudoElementBefore(element);
 		final Styles beforeStyles = styleSheet.getStyles(before);
-		assertEquals("test", beforeStyles.getTextualContent(element).get(0));
+		assertEquals("test", beforeStyles.getTextualContent());
 		assertEquals(123.0f, beforeStyles.getFontSize(), 0.0f);
 	}
 
@@ -76,9 +76,8 @@
 		final Element nochild = new Element("child");
 		child.setParent(element);
 		final Styles styles = styleSheet.getStyles(child);
-		assertEquals(1, styles.getTextualContent(element).size());
-		assertEquals("child", styles.getTextualContent(element).get(0));
+		assertEquals("child", styles.getTextualContent());
 		final Styles nochildStyles = styleSheet.getStyles(nochild);
-		assertEquals("nochild", nochildStyles.getTextualContent(element).get(0));
+		assertEquals("nochild", nochildStyles.getTextualContent());
 	}
 }
\ No newline at end of file
diff --git a/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/css/CssTest.java b/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/css/CssTest.java
index a770e76..00f97b0 100644
--- a/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/css/CssTest.java
+++ b/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/css/CssTest.java
@@ -579,9 +579,9 @@
 
 		final Styles styles = ss.getStyles(element);
 
-		assertEquals("Before", styles.getTextualContent(element).get(0));
+		assertEquals("Before", styles.getTextualContent());
 		element.setAttribute("attribute", "After");
-		assertEquals("After", styles.getTextualContent(element).get(0));
+		assertEquals("After", styles.getTextualContent());
 	}
 
 	@Test
diff --git a/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/css/MockLU.java b/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/css/MockLU.java
index 3fe3306..477ed0e 100644
--- a/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/css/MockLU.java
+++ b/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/css/MockLU.java
@@ -21,32 +21,57 @@
 		lexicalUnitType = type;
 	}
 
-	public static LexicalUnit INHERIT = new MockLU(LexicalUnit.SAC_INHERIT);
+	public static final MockLU INHERIT = new MockLU(LexicalUnit.SAC_INHERIT);
 
-	public static LexicalUnit createFloat(final short units, final float value) {
+	public static MockLU createFloat(final short units, final float value) {
 		final MockLU lu = new MockLU(units);
 		lu.setFloatValue(value);
 		return lu;
 	}
 
-	public static LexicalUnit createIdent(final String s) {
+	public static MockLU createIdent(final String s) {
 		final MockLU lu = new MockLU(LexicalUnit.SAC_IDENT);
 		lu.setStringValue(s);
 		return lu;
 	}
 
-	public static LexicalUnit createString(final String s) {
+	public static MockLU createString(final String s) {
 		final MockLU lu = new MockLU(LexicalUnit.SAC_STRING_VALUE);
 		lu.setStringValue(s);
 		return lu;
 	}
 
-	public static LexicalUnit createAttr(final String attributeName) {
+	public static MockLU createAttr(final String attributeName) {
 		final MockLU result = new MockLU(LexicalUnit.SAC_ATTR);
 		result.setStringValue(attributeName);
 		return result;
 	}
 
+	public static MockLU createUri(final String uri) {
+		final MockLU result = new MockLU(LexicalUnit.SAC_URI);
+		result.setStringValue(uri);
+		return result;
+	}
+
+	public static MockLU createImage(final MockLU... parameters) {
+		final MockLU result = new MockLU(LexicalUnit.SAC_FUNCTION);
+		result.setFunctionName(CSS.IMAGE_FUNCTION);
+		MockLU firstParameter = null;
+		MockLU lastParameter = null;
+		for (final MockLU parameter : parameters) {
+			if (firstParameter == null) {
+				firstParameter = parameter;
+			}
+			if (lastParameter != null) {
+				lastParameter.setNextLexicalUnit(parameter);
+				parameter.setPreviousLexicalUnit(lastParameter);
+			}
+			lastParameter = parameter;
+		}
+		result.setParameters(firstParameter);
+		return result;
+	}
+
 	@Override
 	public String getDimensionUnitText() {
 		return dimensionUnitText;
diff --git a/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/css/PropertyTest.java b/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/css/PropertyTest.java
index a959da0..48dd2fb 100644
--- a/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/css/PropertyTest.java
+++ b/org.eclipse.vex.core.tests/src/org/eclipse/vex/core/internal/css/PropertyTest.java
@@ -16,11 +16,13 @@
 
 import java.io.StringReader;
 import java.util.Iterator;
+import java.util.List;
 
 import org.eclipse.vex.core.internal.core.DisplayDevice;
 import org.eclipse.vex.core.internal.io.DocumentReader;
 import org.eclipse.vex.core.provisional.dom.IDocument;
 import org.eclipse.vex.core.provisional.dom.IElement;
+import org.eclipse.vex.core.provisional.dom.IProcessingInstruction;
 import org.junit.Test;
 import org.w3c.css.sac.InputSource;
 import org.w3c.css.sac.LexicalUnit;
@@ -143,13 +145,65 @@
 		final IElement parent = document.getRootElement().childElements().first();
 		final IElement child = parent.childElements().first();
 		final OutlineContentProperty property = new OutlineContentProperty();
-		final MockLU lu = (MockLU) MockLU.createIdent("child");
+		final MockLU lu = MockLU.createIdent("child");
 		lu.setNextLexicalUnit(MockLU.createIdent("none"));
 
 		assertEquals(child, property.calculate(lu, styles, null, parent));
 		assertEquals(null, property.calculate(lu, styles, null, child));
 	}
 
+	@Test
+	public void contentProperty_textualContent() throws Exception {
+		final ContentProperty property = new ContentProperty();
+		final LexicalUnit lu = MockLU.createString("textual content");
+		final List<IPropertyContent> propertyContent = property.calculate(lu, null, null, null);
+		assertEquals(1, propertyContent.size());
+		assertEquals("textual content", propertyContent.get(0).toString());
+	}
+
+	@Test
+	public void contentProperty_attributeDependendContent() throws Exception {
+		final IDocument document = new DocumentReader().read("<root><elem textAttr=\"textual content from attribute\"/></root>");
+		final IElement element = document.getRootElement().childElements().first();
+		final LexicalUnit lu = MockLU.createAttr("textAttr");
+		final List<IPropertyContent> propertyContent = new ContentProperty().calculate(lu, null, null, element);
+		assertEquals(1, propertyContent.size());
+		final IPropertyContent firstContent = propertyContent.get(0);
+		assertEquals("textual content from attribute", firstContent.toString());
+
+		element.setAttribute("textAttr", "changed textual content");
+		assertEquals("changed textual content", firstContent.toString());
+	}
+
+	@Test
+	public void contentProperty_processingInstructionTarget() throws Exception {
+		final IDocument document = new DocumentReader().read("<root><?piTarget?></root>");
+		final IProcessingInstruction pi = (IProcessingInstruction) document.getRootElement().children().first();
+		final LexicalUnit lu = MockLU.createAttr("target");
+		final List<IPropertyContent> propertyContent = new ContentProperty().calculate(lu, null, null, pi);
+		assertEquals(1, propertyContent.size());
+		final IPropertyContent firstContent = propertyContent.get(0);
+		assertEquals("piTarget", firstContent.toString());
+
+		pi.setTarget("anotherTarget");
+		assertEquals("anotherTarget", firstContent.toString());
+	}
+
+	@Test
+	public void contentProperty_image_attributeValue() throws Exception {
+		final IDocument document = new DocumentReader().read("<root><image src=\"image.jpg\"/></root>");
+		final IElement imageElement = document.getRootElement().childElements().first();
+		imageElement.setBaseURI("https://www.eclipse.org");
+		final LexicalUnit lu = MockLU.createImage(MockLU.createAttr("src"));
+		final List<IPropertyContent> propertyContent = new ContentProperty().calculate(lu, null, null, imageElement);
+		assertEquals(1, propertyContent.size());
+		final IPropertyContent firstContent = propertyContent.get(0);
+		assertEquals("https://www.eclipse.org/image.jpg", ((ImageContent) firstContent).getResolvedImageURL().toString());
+
+		imageElement.setAttribute("src", "anotherImage.jpg");
+		assertEquals("https://www.eclipse.org/anotherImage.jpg", ((ImageContent) firstContent).getResolvedImageURL().toString());
+	}
+
 	private static class DummyDisplayDevice extends DisplayDevice {
 		public DummyDisplayDevice(final int horizontalPPI, final int verticalPPI) {
 			this.horizontalPPI = horizontalPPI;
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/css/AttributeDependendContent.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/css/AttributeDependendContent.java
new file mode 100644
index 0000000..289424d
--- /dev/null
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/css/AttributeDependendContent.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Florian Thienel and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * 		Florian Thienel - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.vex.core.internal.css;
+
+import org.eclipse.core.runtime.QualifiedName;
+import org.eclipse.vex.core.provisional.dom.IElement;
+
+public class AttributeDependendContent implements IPropertyContent {
+
+	public final IElement element;
+	public final QualifiedName attributeName;
+
+	public AttributeDependendContent(final IElement element, final QualifiedName attributeName) {
+		this.element = element;
+		this.attributeName = attributeName;
+	}
+
+	@Override
+	public <T> T accept(final IPropertyContentVisitor<T> visitor) {
+		return visitor.visit(this);
+	}
+
+	@Override
+	public String toString() {
+		final String attributeValue = element.getAttributeValue(attributeName);
+		if (attributeValue != null) {
+			return attributeValue;
+		}
+		return "";
+	}
+
+}
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/css/CSS.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/css/CSS.java
index a146a86..627a5c9 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/css/CSS.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/css/CSS.java
@@ -135,6 +135,9 @@
 	public static final String OUTLINE_CONTENT = "_vex-outline-content";
 	public static final String INLINE_MARKER = "_vex-inline-marker";
 
+	// VEX specific functions
+	public static final String IMAGE_FUNCTION = "image";
+
 	// suffixes to BORDER_XXX
 	public static final String COLOR_SUFFIX = "-color";
 	public static final String STYLE_SUFFIX = "-style";
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/css/ContentProperty.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/css/ContentProperty.java
new file mode 100644
index 0000000..ea8b4e3
--- /dev/null
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/css/ContentProperty.java
@@ -0,0 +1,145 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Florian Thienel and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * 		Florian Thienel - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.vex.core.internal.css;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.core.runtime.QualifiedName;
+import org.eclipse.vex.core.provisional.dom.BaseNodeVisitorWithResult;
+import org.eclipse.vex.core.provisional.dom.IElement;
+import org.eclipse.vex.core.provisional.dom.INode;
+import org.eclipse.vex.core.provisional.dom.IProcessingInstruction;
+import org.w3c.css.sac.LexicalUnit;
+
+public class ContentProperty extends AbstractProperty {
+
+	public ContentProperty() {
+		super(CSS.CONTENT);
+	}
+
+	@Override
+	public List<IPropertyContent> calculate(final LexicalUnit lu, final Styles parentStyles, final Styles styles, final INode node) {
+		final List<IPropertyContent> result = new ArrayList<IPropertyContent>();
+
+		LexicalUnit currentLexicalUnit = lu;
+		while (currentLexicalUnit != null) {
+			IPropertyContent propertyContent;
+			switch (currentLexicalUnit.getLexicalUnitType()) {
+			case LexicalUnit.SAC_STRING_VALUE:
+				propertyContent = stringValue(currentLexicalUnit, result);
+				break;
+			case LexicalUnit.SAC_ATTR:
+				propertyContent = attr(currentLexicalUnit, node, result);
+				break;
+			case LexicalUnit.SAC_FUNCTION:
+				if (CSS.IMAGE_FUNCTION.equalsIgnoreCase(currentLexicalUnit.getFunctionName())) {
+					String stylesBaseURI;
+					if (styles != null && styles.getBaseUrl() != null) {
+						stylesBaseURI = styles.getBaseUrl().toString();
+					} else {
+						stylesBaseURI = null;
+					}
+					propertyContent = image(currentLexicalUnit, node, stylesBaseURI);
+				} else {
+					propertyContent = null;
+				}
+				break;
+			default:
+				// ignore other LexicalUnit types
+				propertyContent = null;
+				break;
+			}
+
+			if (propertyContent != null) {
+				result.add(propertyContent);
+			}
+
+			currentLexicalUnit = currentLexicalUnit.getNextLexicalUnit();
+		}
+
+		return result;
+	}
+
+	private static IPropertyContent stringValue(final LexicalUnit lexicalUnit, final List<IPropertyContent> result) {
+		return new TextualContent(lexicalUnit.getStringValue());
+	}
+
+	private static IPropertyContent attr(final LexicalUnit currentLexicalUnit, final INode node, final List<IPropertyContent> result) {
+		final String stringValue = currentLexicalUnit.getStringValue();
+		return node.accept(new BaseNodeVisitorWithResult<IPropertyContent>() {
+			@Override
+			public IPropertyContent visit(final IElement element) {
+				return new AttributeDependendContent(element, new QualifiedName(null, stringValue));
+			}
+
+			@Override
+			public IPropertyContent visit(final IProcessingInstruction pi) {
+				if (CSS.PSEUDO_TARGET.equalsIgnoreCase(stringValue)) {
+					return new ProcessingInstructionTargetContent(pi);
+				}
+				return null;
+			}
+		});
+	}
+
+	private static IPropertyContent uri(final LexicalUnit lexicalUnit) {
+		return new URIContent(lexicalUnit.getStringValue());
+	}
+
+	private static IPropertyContent image(final LexicalUnit lexicalUnit, final INode node, final String stylesBaseURI) {
+		final List<IPropertyContent> parameters = parseImageParameters(lexicalUnit.getParameters(), node);
+		final String baseURI = determineImageBaseURI(parameters, node, stylesBaseURI);
+
+		return new ImageContent(baseURI, parameters);
+	}
+
+	private static List<IPropertyContent> parseImageParameters(final LexicalUnit parameters, final INode node) {
+		final List<IPropertyContent> result = new ArrayList<IPropertyContent>();
+
+		LexicalUnit currentLexicalUnit = parameters;
+		while (currentLexicalUnit != null) {
+			IPropertyContent propertyContent;
+			switch (currentLexicalUnit.getLexicalUnitType()) {
+			case LexicalUnit.SAC_STRING_VALUE:
+				propertyContent = stringValue(currentLexicalUnit, result);
+				break;
+			case LexicalUnit.SAC_ATTR:
+				propertyContent = attr(currentLexicalUnit, node, result);
+				break;
+			case LexicalUnit.SAC_URI:
+				propertyContent = uri(currentLexicalUnit);
+				break;
+			default:
+				// ignore other LexicalUnit types
+				propertyContent = null;
+				break;
+			}
+
+			if (propertyContent != null) {
+				result.add(propertyContent);
+			}
+
+			currentLexicalUnit = currentLexicalUnit.getNextLexicalUnit();
+		}
+
+		return result;
+	}
+
+	private static String determineImageBaseURI(final List<IPropertyContent> parameters, final INode node, final String stylesBaseURI) {
+		for (final IPropertyContent parameter : parameters) {
+			if (parameter instanceof AttributeDependendContent) {
+				return node.getBaseURI();
+			}
+		}
+		return stylesBaseURI;
+	}
+}
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/css/IPropertyContentVisitor.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/css/IPropertyContentVisitor.java
index 0beaee1..c9664c5 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/css/IPropertyContentVisitor.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/css/IPropertyContentVisitor.java
@@ -14,6 +14,12 @@
 
 	T visit(TextualContent content);
 
+	T visit(AttributeDependendContent content);
+
+	T visit(ProcessingInstructionTargetContent content);
+
 	T visit(URIContent content);
 
+	T visit(ImageContent content);
+
 }
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/css/ImageContent.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/css/ImageContent.java
new file mode 100644
index 0000000..2244400
--- /dev/null
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/css/ImageContent.java
@@ -0,0 +1,54 @@
+package org.eclipse.vex.core.internal.css;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.List;
+
+public class ImageContent implements IPropertyContent {
+
+	public final String baseURI;
+	public final List<IPropertyContent> parameters;
+
+	public ImageContent(final String baseURI, final List<IPropertyContent> parameters) {
+		this.baseURI = baseURI;
+		this.parameters = parameters;
+	}
+
+	@Override
+	public <T> T accept(final IPropertyContentVisitor<T> visitor) {
+		return visitor.visit(this);
+	}
+
+	public URL getResolvedImageURL() throws MalformedURLException {
+		final String urlSpecification = getParametersAsString();
+		final URL baseURL = getBaseURL();
+		if (baseURL == null) {
+			return new URL(urlSpecification);
+		} else {
+			return new URL(baseURL, urlSpecification);
+		}
+	}
+
+	private String getParametersAsString() {
+		final StringBuilder string = new StringBuilder();
+		for (final IPropertyContent parameter : parameters) {
+			string.append(parameter.toString());
+		}
+		return string.toString();
+	}
+
+	private URL getBaseURL() throws MalformedURLException {
+		final URL baseURL;
+		if (baseURI == null) {
+			baseURL = null;
+		} else {
+			baseURL = new URL(baseURI);
+		}
+		return baseURL;
+	}
+
+	@Override
+	public String toString() {
+		return "ImageContent [baseURI=" + baseURI + ", urlSpecification=" + getParametersAsString() + "]";
+	}
+}
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/css/ProcessingInstructionTargetContent.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/css/ProcessingInstructionTargetContent.java
new file mode 100644
index 0000000..5d760f1
--- /dev/null
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/css/ProcessingInstructionTargetContent.java
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Florian Thienel and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * 		Florian Thienel - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.vex.core.internal.css;
+
+import org.eclipse.vex.core.provisional.dom.IProcessingInstruction;
+
+public class ProcessingInstructionTargetContent implements IPropertyContent {
+
+	public final IProcessingInstruction processingInstruction;
+
+	public ProcessingInstructionTargetContent(final IProcessingInstruction processingInstruction) {
+		this.processingInstruction = processingInstruction;
+	}
+
+	@Override
+	public <T> T accept(final IPropertyContentVisitor<T> visitor) {
+		return visitor.visit(this);
+	}
+
+	@Override
+	public String toString() {
+		return processingInstruction.getTarget();
+	}
+
+}
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/css/StyleSheet.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/css/StyleSheet.java
index d6dfbdb..568db74 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/css/StyleSheet.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/css/StyleSheet.java
@@ -86,7 +86,9 @@
 			new BorderWidthProperty(CSS.BORDER_LEFT_WIDTH, CSS.BORDER_LEFT_STYLE, IProperty.Axis.HORIZONTAL),
 			new BorderWidthProperty(CSS.BORDER_RIGHT_WIDTH, CSS.BORDER_RIGHT_STYLE, IProperty.Axis.HORIZONTAL),
 			new BorderWidthProperty(CSS.BORDER_TOP_WIDTH, CSS.BORDER_TOP_STYLE, IProperty.Axis.VERTICAL), new BorderSpacingProperty(), new LengthProperty(CSS.HEIGHT, IProperty.Axis.VERTICAL),
-			new LengthProperty(CSS.WIDTH, IProperty.Axis.HORIZONTAL), new BackgroundImageProperty(), new OutlineContentProperty(), new InlineMarkerProperty() };
+			new LengthProperty(CSS.WIDTH, IProperty.Axis.HORIZONTAL), new BackgroundImageProperty(), new OutlineContentProperty(), new InlineMarkerProperty(),
+			new ContentProperty()
+	};
 
 	private final List<Rule> rules;
 	private final URL baseUrl;
@@ -268,32 +270,8 @@
 
 		styles.setBaseUrl(baseUrl);
 
-		LexicalUnit lexicalUnit;
-		lexicalUnit = decls.get(CSS.CONTENT);
-		// Content needs special handling, since the value of attr(xxx) may change while editing
-		// We pass all valid LexicalUnits to Styles and evaluate there on every access
-		final List<LexicalUnit> content = new ArrayList<LexicalUnit>();
-		while (lexicalUnit != null) {
-			switch (lexicalUnit.getLexicalUnitType()) {
-			case LexicalUnit.SAC_STRING_VALUE:
-				// content: "A String"
-				content.add(lexicalUnit);
-				break;
-			case LexicalUnit.SAC_ATTR:
-				// content: attr(attributeName)
-				content.add(lexicalUnit);
-				break;
-			case LexicalUnit.SAC_URI:
-				// content: url("<some URI of an image>")
-				content.add(lexicalUnit);
-				break;
-			}
-			lexicalUnit = lexicalUnit.getNextLexicalUnit();
-		}
-		styles.setContent(content);
-
 		for (final IProperty property : CSS_PROPERTIES) {
-			lexicalUnit = decls.get(property.getName());
+			final LexicalUnit lexicalUnit = decls.get(property.getName());
 			final Object value = property.calculate(lexicalUnit, parentStyles, styles, node);
 			styles.put(property.getName(), value);
 		}
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/css/Styles.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/css/Styles.java
index 2840bda..d609345 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/css/Styles.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/css/Styles.java
@@ -14,10 +14,7 @@
 package org.eclipse.vex.core.internal.css;
 
 import java.net.MalformedURLException;
-import java.net.URI;
-import java.net.URISyntaxException;
 import java.net.URL;
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -25,12 +22,6 @@
 import org.eclipse.vex.core.internal.core.Color;
 import org.eclipse.vex.core.internal.core.FontSpec;
 import org.eclipse.vex.core.internal.core.Length;
-import org.eclipse.vex.core.provisional.dom.BaseNodeVisitor;
-import org.eclipse.vex.core.provisional.dom.BaseNodeVisitorWithResult;
-import org.eclipse.vex.core.provisional.dom.IElement;
-import org.eclipse.vex.core.provisional.dom.INode;
-import org.eclipse.vex.core.provisional.dom.IProcessingInstruction;
-import org.w3c.css.sac.LexicalUnit;
 
 /**
  * Represents the computed style properties for a particular element.
@@ -67,7 +58,6 @@
 	 */
 	private final Map<String, Styles> pseudoElementStyles = new HashMap<String, Styles>();
 
-	private List<LexicalUnit> contentLexicalUnits;
 	private FontSpec font;
 	private URL baseUrl;
 
@@ -183,7 +173,10 @@
 	 * @return <code>true</code> if the stylesheet defined content for this element.
 	 */
 	public boolean isContentDefined() {
-		return contentLexicalUnits.size() > 0;
+		if (!values.containsKey(CSS.CONTENT)) {
+			return false;
+		}
+		return !getContent().isEmpty();
 	}
 
 	/**
@@ -195,87 +188,17 @@
 	 * @param node
 	 *            The INode to get attr(...) values from
 	 */
-	public List<String> getTextualContent(final INode node) {
-		final List<String> content = new ArrayList<String>();
-		for (final LexicalUnit lexicalUnit : contentLexicalUnits) {
-			switch (lexicalUnit.getLexicalUnitType()) {
-			case LexicalUnit.SAC_STRING_VALUE:
-				// content: "A String"
-				content.add(lexicalUnit.getStringValue());
-				break;
-			case LexicalUnit.SAC_ATTR:
-				// content: attr(attributeName)
-				final LexicalUnit currentLexicalUnit = lexicalUnit;
-				node.accept(new BaseNodeVisitor() {
-					@Override
-					public void visit(final IElement element) {
-						final String attributeValue = element.getAttributeValue(currentLexicalUnit.getStringValue());
-						if (attributeValue != null) {
-							content.add(attributeValue);
-						}
-					}
-
-					@Override
-					public void visit(final IProcessingInstruction pi) {
-						if (currentLexicalUnit.getStringValue().equalsIgnoreCase(CSS.PSEUDO_TARGET)) {
-							content.add(pi.getTarget());
-						}
-					}
-				});
-				break;
-			}
+	public String getTextualContent() {
+		final StringBuilder string = new StringBuilder();
+		for (final IPropertyContent content : getContent()) {
+			string.append(content.toString());
 		}
-		return content;
+		return string.toString();
 	}
 
-	public List<IPropertyContent> getAllContent(final INode node) {
-		final List<IPropertyContent> allContent = new ArrayList<IPropertyContent>();
-		for (final LexicalUnit lexicalUnit : contentLexicalUnits) {
-			final IPropertyContent content = getContent(lexicalUnit, node);
-			if (content != null) {
-				allContent.add(content);
-			}
-		}
-		return allContent;
-	}
-
-	private IPropertyContent getContent(final LexicalUnit lexicalUnit, final INode node) {
-		switch (lexicalUnit.getLexicalUnitType()) {
-		case LexicalUnit.SAC_STRING_VALUE:
-			// content: "A String"
-			return new TextualContent(lexicalUnit.getStringValue());
-		case LexicalUnit.SAC_ATTR:
-			// content: attr(attributeName)
-			final LexicalUnit currentLexicalUnit = lexicalUnit;
-			return node.accept(new BaseNodeVisitorWithResult<IPropertyContent>() {
-				@Override
-				public IPropertyContent visit(final IElement element) {
-					final String attributeValue = element.getAttributeValue(currentLexicalUnit.getStringValue());
-					if (attributeValue != null) {
-						return new TextualContent(attributeValue);
-					}
-					return null;
-				}
-
-				@Override
-				public IPropertyContent visit(final IProcessingInstruction pi) {
-					if (currentLexicalUnit.getStringValue().equalsIgnoreCase(CSS.PSEUDO_TARGET)) {
-						return new TextualContent(pi.getTarget());
-					}
-					return null;
-				}
-			});
-		case LexicalUnit.SAC_URI:
-			// content: url("<some URI of an image>")
-			try {
-				return new URIContent(new URI(lexicalUnit.getStringValue()));
-			} catch (final URISyntaxException e) {
-				e.printStackTrace();
-				return null;
-			}
-		default:
-			return null;
-		}
+	@SuppressWarnings("unchecked")
+	public List<IPropertyContent> getContent() {
+		return (List<IPropertyContent>) values.get(CSS.CONTENT);
 	}
 
 	/**
@@ -405,16 +328,6 @@
 	}
 
 	/**
-	 * Sets the LexicalUnits of the <code>content</code> property.
-	 *
-	 * @param content
-	 *            <code>List</code> of <code>LexicalUnits</code> objects defining the content.
-	 */
-	public void setContent(final List<LexicalUnit> content) {
-		contentLexicalUnits = content;
-	}
-
-	/**
 	 * Sets the value of the <code>font</code> property.
 	 *
 	 * @param font
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/css/URIContent.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/css/URIContent.java
index 4c06f2b..11ed460 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/css/URIContent.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/css/URIContent.java
@@ -11,12 +11,13 @@
 package org.eclipse.vex.core.internal.css;
 
 import java.net.URI;
+import java.net.URISyntaxException;
 
 public class URIContent implements IPropertyContent {
 
-	public final URI uri;
+	public final String uri;
 
-	public URIContent(final URI uri) {
+	public URIContent(final String uri) {
 		this.uri = uri;
 	}
 
@@ -29,4 +30,8 @@
 	public String toString() {
 		return uri.toString();
 	}
+
+	public URI uriValue() throws URISyntaxException {
+		return new URI(uri);
+	}
 }
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/layout/LayoutUtils.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/layout/LayoutUtils.java
index dbe570d..c578a65 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/layout/LayoutUtils.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/layout/LayoutUtils.java
@@ -77,12 +77,7 @@
 	 *            The node passed to Styles#getContent (to get attr values from)
 	 */
 	public static String getGeneratedContent(final LayoutContext context, final Styles styles, final INode node) {
-		final List<String> content = styles.getTextualContent(node);
-		final StringBuffer sb = new StringBuffer();
-		for (final String string : content) {
-			sb.append(string); // TODO: change to ContentPart
-		}
-		return sb.toString();
+		return styles.getTextualContent();
 	}
 
 	/**
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/visualization/CssBasedBoxModelBuilder.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/visualization/CssBasedBoxModelBuilder.java
index cfc17ef..72e85e9 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/visualization/CssBasedBoxModelBuilder.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/visualization/CssBasedBoxModelBuilder.java
@@ -25,6 +25,7 @@
 import static org.eclipse.vex.core.internal.visualization.CssBoxFactory.staticText;
 import static org.eclipse.vex.core.internal.visualization.CssBoxFactory.textContent;
 
+import java.net.MalformedURLException;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedList;
@@ -41,9 +42,12 @@
 import org.eclipse.vex.core.internal.boxes.Paragraph;
 import org.eclipse.vex.core.internal.boxes.RootBox;
 import org.eclipse.vex.core.internal.boxes.TextContent;
+import org.eclipse.vex.core.internal.css.AttributeDependendContent;
 import org.eclipse.vex.core.internal.css.CSS;
 import org.eclipse.vex.core.internal.css.IPropertyContent;
 import org.eclipse.vex.core.internal.css.IPropertyContentVisitor;
+import org.eclipse.vex.core.internal.css.ImageContent;
+import org.eclipse.vex.core.internal.css.ProcessingInstructionTargetContent;
 import org.eclipse.vex.core.internal.css.StyleSheet;
 import org.eclipse.vex.core.internal.css.Styles;
 import org.eclipse.vex.core.internal.css.Styles.PseudoElement;
@@ -308,7 +312,7 @@
 	}
 
 	private static <P extends IParentBox<IInlineBox>> P visualizeContentProperty(final INode node, final Styles styles, final P parent) {
-		for (final IPropertyContent part : styles.getAllContent(node)) {
+		for (final IPropertyContent part : styles.getContent()) {
 			final IInlineBox box = part.accept(new IPropertyContentVisitor<IInlineBox>() {
 				@Override
 				public IInlineBox visit(final TextualContent content) {
@@ -316,12 +320,34 @@
 				}
 
 				@Override
+				public IInlineBox visit(final AttributeDependendContent content) {
+					return staticText(content.toString(), styles);
+				}
+
+				@Override
+				public IInlineBox visit(final ProcessingInstructionTargetContent content) {
+					return staticText(content.toString(), styles);
+				}
+
+				@Override
 				public IInlineBox visit(final URIContent content) {
 					final String imageUri = content.uri.toString();
 					final Image image = new Image();
 					image.setImageUrl(styles.resolveUrl(imageUri));
 					return image;
 				}
+
+				@Override
+				public IInlineBox visit(final ImageContent content) {
+					final Image image = new Image();
+					try {
+						image.setImageUrl(content.getResolvedImageURL());
+					} catch (final MalformedURLException e) {
+						// TODO log error, render error information
+						e.printStackTrace();
+					}
+					return image;
+				}
 			});
 			if (box != null) {
 				parent.appendChild(box);
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/swt/SwtGraphics.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/swt/SwtGraphics.java
index ed3c7c6..5d504bc 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/swt/SwtGraphics.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/swt/SwtGraphics.java
@@ -240,6 +240,10 @@
 	}
 
 	private static ImageData[] loadImageData(final URL url) {
+		if (url == null) {
+			return null;
+		}
+
 		final ImageLoader imageLoader = new ImageLoader();
 		try {
 			final InputStream in = url.openStream();