add support for pseudo elements

Signed-off-by: Florian Thienel <florian@thienel.org>
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 7fe65f9..0eca580 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
@@ -38,6 +38,7 @@
 import org.eclipse.vex.core.internal.css.CSS;
 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;
 import org.eclipse.vex.core.internal.dom.CollectingNodeTraversal;
 import org.eclipse.vex.core.provisional.dom.BaseNodeVisitorWithResult;
 import org.eclipse.vex.core.provisional.dom.ContentRange;
@@ -129,7 +130,7 @@
 			public VisualizeResult visit(final IElement element) {
 				final Collection<VisualizeResult> childrenResults = traverseChildren(element);
 				final Styles styles = styleSheet.getStyles(element);
-				if (isDisplayedAsBlock(element, styles)) {
+				if (isDisplayedAsBlock(styles)) {
 					return new VisualizeResult(element, childrenResults, visualizeAsBlock(element, childrenResults));
 				} else {
 					return new VisualizeResult(element, childrenResults, visualizeInline(element, childrenResults));
@@ -144,13 +145,17 @@
 		});
 	}
 
-	private static boolean isDisplayedAsBlock(final IElement element, final Styles styles) {
+	private static boolean isDisplayedAsBlock(final Styles styles) {
 		// currently we can only render blocks or inline, hence everything that is not inline must be a block
-		return !CSS.INLINE.equals(styles.getDisplay());
+		return !isDisplayedInline(styles);
 	}
 
-	private static boolean isDisplayedPreservingWhitespace(final IText text, final Styles styles) {
-		return CSS.PRE.equals(styles.getDisplay());
+	private static boolean isDisplayedInline(final Styles styles) {
+		return CSS.INLINE.equals(styles.getDisplay());
+	}
+
+	private static boolean isPreservingWhitespace(final Styles styles) {
+		return CSS.PRE.equals(styles.getWhiteSpace());
 	}
 
 	/*
@@ -171,14 +176,42 @@
 				}
 
 				if (mayContainText) {
-					return nodeReferenceWithText(element, frame(content, styles));
+					return nodeReferenceWithText(element, surroundWithPseudoElements(frame(content, styles), element, styles));
 				} else {
-					return nodeReference(element, frame(content, styles));
+					return nodeReference(element, surroundWithPseudoElements(frame(content, styles), element, styles));
 				}
 			}
 		});
 	}
 
+	private static IStructuralBox surroundWithPseudoElements(final IStructuralBox content, final INode node, final Styles styles) {
+		final IStructuralBox pseudoElementBefore = visualizePseudoElementAsBlock(styles, node, PseudoElement.BEFORE);
+		final IStructuralBox pseudoElementAfter = visualizePseudoElementAsBlock(styles, node, PseudoElement.AFTER);
+
+		if (pseudoElementBefore == null && pseudoElementAfter == null) {
+			return content;
+		}
+
+		return verticalBlock(pseudoElementBefore, content, pseudoElementAfter);
+	}
+
+	private static IStructuralBox visualizePseudoElementAsBlock(final Styles styles, final INode node, final PseudoElement pseudoElement) {
+		if (!styles.hasPseudoElement(pseudoElement)) {
+			return null;
+		}
+
+		final Styles pseudoElementStyles = styles.getPseudoElementStyles(pseudoElement);
+		if (!isDisplayedAsBlock(pseudoElementStyles)) {
+			return null;
+		}
+
+		final StringBuilder content = new StringBuilder();
+		for (final String part : pseudoElementStyles.getContent(node)) {
+			content.append(part);
+		}
+		return frame(paragraph(pseudoElementStyles, staticText(content.toString(), pseudoElementStyles)), pseudoElementStyles);
+	}
+
 	private static boolean containsInlineContent(final Collection<VisualizeResult> visualizeResults) {
 		for (final VisualizeResult visualizeResult : visualizeResults) {
 			if (visualizeResult.inline) {
@@ -203,7 +236,7 @@
 		return node.accept(new BaseNodeVisitorWithResult<IInlineBox>() {
 			@Override
 			public IInlineBox visit(final IElement element) {
-				final InlineContainer inlineElementContent = visualizeInlineElementContent(element, styles, childrenResults, inlineContainer());
+				final IInlineBox inlineElementContent = visualizeInlineElementContent(element, styles, childrenResults, inlineContainer());
 				if (mayContainText(element)) {
 					return nodeReferenceWithText(element, frame(inlineElementContent, styles));
 				} else {
@@ -214,7 +247,7 @@
 			@Override
 			public IInlineBox visit(final IText text) {
 				final ContentRange textRange = text.getRange();
-				if (isDisplayedPreservingWhitespace(text, styles)) {
+				if (isPreservingWhitespace(styles)) {
 					return visualizeAsMultilineText(text, styles);
 				} else {
 					return textContent(content, textRange, styles);
@@ -226,7 +259,7 @@
 
 	private <P extends IParentBox<IInlineBox>> P visualizeInlineElementContent(final IElement element, final Styles styles, final Collection<VisualizeResult> childrenResults, final P parent) {
 		if (!childrenResults.isEmpty()) {
-			return visualizeChildrenInline(childrenResults, parent);
+			return surroundWithPseudoElements(visualizeChildrenInline(childrenResults, parent), element, styles);
 		} else {
 			return placeholderForEmptyElement(element, styles, parent);
 		}
@@ -254,6 +287,37 @@
 		return lineContainer;
 	}
 
+	private static <P extends IParentBox<IInlineBox>> P surroundWithPseudoElements(final P parent, final INode node, final Styles styles) {
+		final IInlineBox pseudoElementBefore = visualizePseudoElementInline(styles, node, PseudoElement.BEFORE);
+		final IInlineBox pseudoElementAfter = visualizePseudoElementInline(styles, node, PseudoElement.AFTER);
+
+		if (pseudoElementBefore != null) {
+			parent.prependChild(pseudoElementBefore);
+		}
+		if (pseudoElementAfter != null) {
+			parent.appendChild(pseudoElementAfter);
+		}
+
+		return parent;
+	}
+
+	private static IInlineBox visualizePseudoElementInline(final Styles styles, final INode node, final PseudoElement pseudoElement) {
+		if (!styles.hasPseudoElement(pseudoElement)) {
+			return null;
+		}
+
+		final Styles pseudoElementStyles = styles.getPseudoElementStyles(pseudoElement);
+		if (!isDisplayedInline(pseudoElementStyles)) {
+			return null;
+		}
+
+		final StringBuilder content = new StringBuilder();
+		for (final String part : pseudoElementStyles.getContent(node)) {
+			content.append(part);
+		}
+		return frame(inlineContainer(staticText(content.toString(), pseudoElementStyles)), pseudoElementStyles);
+	}
+
 	private static <P extends IParentBox<IInlineBox>> P placeholderForEmptyElement(final IElement element, final Styles styles, final P parent) {
 		parent.appendChild(endOffsetPlaceholder(element, styles));
 		if (false) { // TODO allow to provide a placeholder text in the CSS