intial code check in and renaming
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/.classpath b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/.classpath
new file mode 100644
index 0000000..d00d96b
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/.classpath
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry exported="true" kind="lib" path="lib/flute.jar"/>
+	<classpathentry exported="true" kind="lib" path="lib/sac.jar"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/.cvsignore b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/.cvsignore
new file mode 100644
index 0000000..fc08203
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/.cvsignore
@@ -0,0 +1,3 @@
+bin
+net.sf.vex.toolkit_1.0.0.jar
+vex-toolkit.jar
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/.project b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/.project
new file mode 100644
index 0000000..8670aa3
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/.project
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>org.eclipse.wst.xml.vex.core</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.ManifestBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.SchemaBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>net.sourceforge.metrics.builder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.pde.PluginNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>net.sourceforge.metrics.nature</nature>
+	</natures>
+</projectDescription>
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/.settings/org.eclipse.jdt.core.prefs b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..310db2d
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,7 @@
+#Tue Jul 17 18:36:56 CEST 2007
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=disabled
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/.template b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/.template
new file mode 100644
index 0000000..d65e0f4
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/.template
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<form>
+   <p/><p><b>Tips on working with this plug-in project</b></p><li>For the view of the new plug-in at a glance, go to the <img href="pageImage"/><a href="OverviewPage">Overview</a>.</li><li>You can test the contributions of this plug-in by launching another instance of the workbench. On the <b>Run</b> menu, click <b>Run As</b> and choose <img href="runTimeWorkbenchImage"/><a href="action.run">Run-time Workbench</a> from the available choices.</li><li>You can add more functionality to this plug-in by adding extensions using the <a href="action.newExtension">New Extension Wizard</a>.</li><li>The plug-in project contains Java code that you can debug. Place breakpoints in Java classes. On the <b>Run</b> menu, select <b>Debug As</b> and choose <img href="runTimeWorkbenchImage"/><a href="action.debug">Run-time Workbench</a> from the available choices.</li>
+</form>
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/META-INF/MANIFEST.MF b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..adc1898
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/META-INF/MANIFEST.MF
@@ -0,0 +1,32 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: %plugin.name
+Bundle-Version: 0.5.0.qualifier
+Bundle-Vendor: Eclipse
+Bundle-SymbolicName: org.eclipse.wst.xml.vex.core;singleton:=true
+Require-Bundle: org.eclipse.core.runtime,
+ org.eclipse.ui,
+ org.junit
+Export-Package: com.wutka.dtd,
+ org.eclipse.wst.xml.vex.core.internal;x-internal:=true,
+ org.eclipse.wst.xml.vex.core.internal.action;x-internal:=true,
+ org.eclipse.wst.xml.vex.core.internal.core;x-internal:=true,
+ org.eclipse.wst.xml.vex.core.internal.css;x-internal:=true,
+ org.eclipse.wst.xml.vex.core.internal.dom;x-internal:=true,
+ org.eclipse.wst.xml.vex.core.internal.layout;x-internal:=true,
+ org.eclipse.wst.xml.vex.core.internal.swing;x-internal:=true,
+ org.eclipse.wst.xml.vex.core.internal.swt;x-internal:=true,
+ org.eclipse.wst.xml.vex.core.internal.undo;x-internal:=true,
+ org.eclipse.wst.xml.vex.core.internal.widget;x-internal:=true,
+ org.w3c.css.sac,
+ org.w3c.css.sac.helpers,
+ org.w3c.flute.parser,
+ org.w3c.flute.parser.selectors,
+ org.w3c.flute.util
+Bundle-Activator: org.eclipse.wst.xml.vex.core.internal.VEXCorePlugin
+Eclipse-LazyStart: true
+Bundle-Localization: plugin
+Bundle-ClassPath: lib/dtdparser120.jar,
+ lib/sac.jar,
+ lib/flute.jar,
+ .
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/build.properties b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/build.properties
new file mode 100644
index 0000000..e390377
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/build.properties
@@ -0,0 +1,19 @@
+bin.includes = plugin.xml,\
+               vex-toolkit.jar,\
+               lgpl.txt,\
+               plugin.properties,\
+               META-INF/,\
+               .,\
+               lib/,\
+               lib/dtdparser120.jar,\
+               lib/flute.jar,\
+               lib/sac.jar,\
+               bin/
+output.vex-toolkit.jar = bin/
+source.vex-toolkit.jar = src/
+src.includes = lgpl.txt,\
+               lib/,\
+               src/
+jars.compile.order = vex-toolkit.jar,\
+                     .
+source.. = src/
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/lib/.cvsignore b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/lib/.cvsignore
new file mode 100644
index 0000000..8f7e8a6
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/lib/.cvsignore
@@ -0,0 +1 @@
+dtdparser120.jar
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/lib/flute.jar b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/lib/flute.jar
new file mode 100644
index 0000000..39a55d4
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/lib/flute.jar
Binary files differ
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/lib/sac.jar b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/lib/sac.jar
new file mode 100644
index 0000000..6ff0b70
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/lib/sac.jar
Binary files differ
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/plugin.properties b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/plugin.properties
new file mode 100644
index 0000000..d7d3e64
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/plugin.properties
@@ -0,0 +1 @@
+plugin.name=Vex Toolkit
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/VEXCorePlugin.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/VEXCorePlugin.java
new file mode 100644
index 0000000..4680695
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/VEXCorePlugin.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *     David Carver (STAR) - initial renaming
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal;
+
+import org.eclipse.ui.plugin.AbstractUIPlugin;
+import org.osgi.framework.BundleContext;
+
+public class VEXCorePlugin extends AbstractUIPlugin {
+
+    private static VEXCorePlugin instance;
+    
+    public VEXCorePlugin() {
+        instance = this;  
+    }
+    
+    /**
+     * Returns the shared instance.
+     */
+    public static VEXCorePlugin getInstance() {
+        return instance;
+    }
+    
+    public void start(BundleContext bundleContext) throws Exception {
+        super.start(bundleContext);
+    }
+
+    public void stop(BundleContext context) throws Exception {
+        super.stop(context);
+    }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/AbstractVexAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/AbstractVexAction.java
new file mode 100644
index 0000000..da0e86e
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/AbstractVexAction.java
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+
+/**
+ * Abstract Vex action. This class provides default implementations for
+ * all methods in IVexAction except for <code>run</code>.
+ */
+public abstract class AbstractVexAction implements IVexAction {
+
+    /**
+     * Class constructor.
+     */
+    public AbstractVexAction() {
+    }
+
+    /**
+     * Returns <code>true</code>.
+     */
+    public boolean isEnabled(IVexWidget vexWidget) {
+        return true;
+    }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/ActionUtils.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/ActionUtils.java
new file mode 100644
index 0000000..d757ef2
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/ActionUtils.java
@@ -0,0 +1,494 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.wst.xml.vex.core.internal.core.IntRange;
+import org.eclipse.wst.xml.vex.core.internal.css.CSS;
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheet;
+import org.eclipse.wst.xml.vex.core.internal.dom.Document;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.dom.Node;
+import org.eclipse.wst.xml.vex.core.internal.layout.BlockBox;
+import org.eclipse.wst.xml.vex.core.internal.layout.Box;
+import org.eclipse.wst.xml.vex.core.internal.layout.ElementOrRangeCallback;
+import org.eclipse.wst.xml.vex.core.internal.layout.LayoutUtils;
+import org.eclipse.wst.xml.vex.core.internal.layout.TableRowBox;
+import org.eclipse.wst.xml.vex.core.internal.widget.IBoxFilter;
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+
+
+/**
+ * Static helper methods used across actions.
+ */
+public class ActionUtils {
+
+    public static class RowColumnInfo {
+        public Object row;
+        public Object cell;
+        public int rowIndex;
+        public int cellIndex;
+        public int rowCount;
+        public int columnCount;
+        public int maxColumnCount;
+    }
+
+    /**
+     * Clone the table cells from the given TableRowBox to the current offset in vexWidget.
+     * @param vexWidget IVexWidget to modify.
+     * @param tr TableRowBox whose cells are to be cloned.
+     * @param moveToFirstCell TODO
+     */
+    public static void cloneTableCells(final IVexWidget vexWidget, final TableRowBox tr, final boolean moveToFirstCell) {
+        vexWidget.doWork(new Runnable() {
+            public void run() {
+    
+                int offset = vexWidget.getCaretOffset();
+    
+                boolean firstCellIsAnonymous = false;
+                Box[] cells = tr.getChildren();
+                for (int i = 0; i < cells.length; i++) {
+                    if (cells[i].isAnonymous()) {
+                        vexWidget.insertText(" ");
+                        if (i == 0) {
+                            firstCellIsAnonymous = true;
+                        }
+                    } else {
+                        vexWidget.insertElement((Element) cells[i].getElement().clone());
+                        vexWidget.moveBy(+1);
+                    }
+                }
+                
+                if (moveToFirstCell) {
+                    vexWidget.moveTo(offset + 1);
+                    if (firstCellIsAnonymous) {
+                        vexWidget.moveBy(-1, true);
+                    }
+                }
+            }
+        });
+    }
+    
+    /**
+     * Duplicate the given table row, inserting a new empty one below it. The new
+     * row contains empty children corresponding to the given row's children.
+     * @param vexWidget IVexWidget with which we're working
+     * @param tr TableRowBox to be duplicated.
+     */
+    public static void duplicateTableRow(final IVexWidget vexWidget, final TableRowBox tr) {
+        vexWidget.doWork(new Runnable() {
+            public void run() {
+    
+                vexWidget.moveTo(tr.getEndOffset());
+                
+                if (!tr.isAnonymous()) {
+                    vexWidget.moveBy(+1); // Move past sentinel in current row
+                    vexWidget.insertElement((Element) tr.getElement().clone());
+                }
+
+                cloneTableCells(vexWidget, tr, true);
+            }
+        });
+    }
+
+    /**
+     * Returns true if the given element or range is at least partially selected.
+     * 
+     * @param vexWidget IVexWidget being tested.
+     * @param elementOrRange Element or IntRange being tested.
+     */
+    public static boolean elementOrRangeIsPartiallySelected(IVexWidget vexWidget, Object elementOrRange) {
+        IntRange range = getInnerRange(elementOrRange);
+        return range.getEnd() >= vexWidget.getSelectionStart()
+        && range.getStart() <= vexWidget.getSelectionEnd();
+    }
+    
+    /**
+     * Returns the zero-based index of the table column containing the 
+     * current offset. Returns -1 if we are not inside a table.
+     */
+    public static int getCurrentColumnIndex(IVexWidget vexWidget) {
+        
+        Element row = getCurrentTableRow(vexWidget);
+        
+        if (row == null) {
+            return -1;
+        }
+        
+        final int offset = vexWidget.getCaretOffset();
+        final int[] column = new int[] { -1 };
+        LayoutUtils.iterateTableCells(vexWidget.getStyleSheet(), row, new ElementOrRangeCallback() {
+            private int i = 0;
+            public void onElement(Element child, String displayStyle) {
+                if (offset > child.getStartOffset() && offset <= child.getEndOffset()) {
+                    column[0] = i;
+                }
+                i++;
+            }
+            public void onRange(Element parent, int startOffset, int endOffset) {
+                i++;
+            }
+        });
+        
+        return column[0];
+    }
+
+    /**
+     * Returns the innermost Element with style table-row containing the caret, 
+     * or null if no such element exists.
+     * @param vexWidget IVexWidget to use.
+     */
+    public static Element getCurrentTableRow(IVexWidget vexWidget) {
+        
+        StyleSheet ss = vexWidget.getStyleSheet();
+        Element element = vexWidget.getCurrentElement();
+        
+        while (element != null) {
+            if (ss.getStyles(element).getDisplay().equals(CSS.TABLE_ROW)) {
+                return element;
+            }
+            element = element.getParent();
+        }
+
+        return null;
+    }
+    
+    /**
+     * Returns the start offset of the next sibling of the parent element.
+     * Returns -1 if there is no previous sibling in the parent.
+     * @param vexWidget VexWidget to use.
+     */
+    public static int getPreviousSiblingStart(IVexWidget vexWidget) {
+        int startOffset;
+        
+        if (vexWidget.hasSelection()) {
+            startOffset = vexWidget.getSelectionStart();
+        } else {
+            Box box = vexWidget.findInnermostBox(new IBoxFilter() {
+                public boolean matches(Box box) {
+                    return box instanceof BlockBox
+                        && box.getElement() != null;
+                }
+            });
+            
+            if (box.getElement() == vexWidget.getDocument().getRootElement()) {
+                return -1;
+            }
+            
+            startOffset = box.getElement().getStartOffset();
+        }
+        
+        int previousSiblingStart = -1;
+        Element parent = vexWidget.getDocument().getElementAt(startOffset);
+        Node[] children = parent.getChildNodes();
+        for (int i = 0; i < children.length; i++) {
+            Node child = children[i];
+            if (startOffset == child.getStartOffset()) {
+                break;
+            }
+            previousSiblingStart = child.getStartOffset();
+        }
+        return previousSiblingStart;
+    }
+
+    /**
+     * Returns an array of the selected block boxes. Text nodes between boxes
+     * are not returned. If the selection does not enclose any block boxes,
+     * returns an empty array.
+     * @param vexWidget VexWidget to use.
+     */
+    public static BlockBox[] getSelectedBlockBoxes(final IVexWidget vexWidget) {
+        
+        if (!vexWidget.hasSelection()) {
+            return new BlockBox[0];
+        }
+        
+        Box parent = vexWidget.findInnermostBox(new IBoxFilter() {
+            public boolean matches(Box box) {
+                System.out.println("Matching " + box);
+                return box instanceof BlockBox
+                && box.getStartOffset() <= vexWidget.getSelectionStart()
+                && box.getEndOffset() >= vexWidget.getSelectionEnd();
+            }
+        });
+        
+        System.out.println("Matched " + parent);
+        
+        List blockList = new ArrayList();
+        
+        Box[] children = parent.getChildren();
+        System.out.println("Parent has " + children.length + " children");
+        for (int i = 0; i < children.length; i++) {
+            Box child = children[i];
+            if (child instanceof BlockBox
+                    && child.getStartOffset() >= vexWidget.getSelectionStart()
+                    && child.getEndOffset() <= vexWidget.getSelectionEnd()) {
+                System.out.println("  adding " + child);
+                blockList.add(child);
+            } else {
+                System.out.println("  skipping " + child);
+            }
+                    
+        }
+        
+        return (BlockBox[]) blockList.toArray(new BlockBox[blockList.size()]);
+    }
+
+    /**
+     * Returns the currently selected table rows, or the current row if
+     * ther is no selection. If no row can be found, returns an empty array.
+     * @param vexWidget IVexWidget to use.
+     */
+    public static SelectedRows getSelectedTableRows(final IVexWidget vexWidget) {
+        
+        final SelectedRows selected = new SelectedRows();
+        
+        ActionUtils.iterateTableCells(vexWidget, new TableCellCallback() {
+            public void startRow(Object row, int rowIndex) {
+                if (ActionUtils.elementOrRangeIsPartiallySelected(vexWidget, row)) {
+                    if (selected.rows == null) {
+                        selected.rows = new ArrayList();
+                    }
+                    selected.rows.add(row);
+                } else {
+                    if (selected.rows == null) {
+                        selected.rowBefore = row;
+                    } else {
+                        if (selected.rowAfter == null) {
+                            selected.rowAfter = row;
+                        }
+                    }
+                }
+            }
+            public void onCell(Object row, Object cell, int rowIndex, int cellIndex) {
+            }
+            public void endRow(Object row, int rowIndex) {
+            }
+        });
+        
+        return selected;
+    }
+
+    public static void iterateTableCells(IVexWidget vexWidget, final TableCellCallback callback) {
+        
+        final StyleSheet ss = vexWidget.getStyleSheet();
+
+        iterateTableRows(vexWidget, new ElementOrRangeCallback() {
+            
+            final private int[] rowIndex = { 0 };
+            
+            public void onElement(final Element row, String displayStyle) {
+                
+                callback.startRow(row, rowIndex[0]);
+                
+                LayoutUtils.iterateTableCells(ss, row, new ElementOrRangeCallback() {
+                    private int cellIndex = 0;
+                    public void onElement(Element cell, String displayStyle) {
+                        callback.onCell(row, cell, rowIndex[0], cellIndex);
+                        cellIndex++;
+                    }
+                    public void onRange(Element parent, int startOffset, int endOffset) {
+                        callback.onCell(row, new IntRange(startOffset, endOffset), rowIndex[0], cellIndex);
+                        cellIndex++;
+                    }
+                });
+                
+                callback.endRow(row, rowIndex[0]);
+                
+                rowIndex[0]++;
+            }
+            
+            public void onRange(Element parent, final int startOffset, final int endOffset) {
+                
+                final IntRange row = new IntRange(startOffset, endOffset);
+                callback.startRow(row, rowIndex[0]);
+                
+                LayoutUtils.iterateTableCells(ss, parent, startOffset, endOffset, new ElementOrRangeCallback() {
+                    private int cellIndex = 0;
+                    public void onElement(Element cell, String displayStyle) {
+                        callback.onCell(row, cell, rowIndex[0], cellIndex);
+                        cellIndex++;
+                    }
+                    public void onRange(Element parent, int startOffset, int endOffset) {
+                        callback.onCell(row, new IntRange(startOffset, endOffset), rowIndex[0], cellIndex);
+                        cellIndex++;
+                    }
+                });
+                
+                callback.endRow(row, rowIndex[0]);
+                
+                rowIndex[0]++;
+            }
+        });
+    }
+
+    /**
+     * Returns a RowColumnInfo structure containing information about the table
+     * containing the caret. Returns null if the caret is not currently inside
+     * a table.
+     * 
+     * @param vexWidget IVexWidget to inspect.
+     */
+    public static RowColumnInfo getRowColumnInfo(IVexWidget vexWidget) {
+        
+        final boolean[] found = new boolean[1];
+        final RowColumnInfo[] rcInfo = new RowColumnInfo[] { new RowColumnInfo() };
+        final int offset = vexWidget.getCaretOffset();
+        
+        rcInfo[0].cellIndex = -1;
+        rcInfo[0].rowIndex = -1;
+        
+        iterateTableCells(vexWidget, new TableCellCallback() {
+            
+            int rowColumnCount;
+            
+            public void startRow(Object row, int rowIndex) {
+                rowColumnCount = 0;
+            }
+
+            public void onCell(Object row, Object cell, int rowIndex, int cellIndex) {
+                found[0] = true;
+                if (LayoutUtils.elementOrRangeContains(row, offset)) {
+                    rcInfo[0].row = row;
+                    rcInfo[0].rowIndex = rowIndex;
+                    rcInfo[0].columnCount++;
+                    
+                    if (LayoutUtils.elementOrRangeContains(cell, offset)) {
+                        rcInfo[0].cell = cell;
+                        rcInfo[0].cellIndex = cellIndex;
+                    }
+                }
+                
+                rowColumnCount++;
+            }
+
+            public void endRow(Object row, int rowIndex) {
+                rcInfo[0].rowCount++;
+                rcInfo[0].maxColumnCount = Math.max(rcInfo[0].maxColumnCount, rowColumnCount);
+            }
+        });
+        
+        if (found[0]) {
+            return rcInfo[0];
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Iterate over all rows in the table containing the caret.
+     * 
+     * @param vexWidget IVexWidget to iterate over.
+     * @param callback Caller-provided callback that this method calls for
+     * each row in the current table.
+     */
+    public static void iterateTableRows(IVexWidget vexWidget, ElementOrRangeCallback callback) {
+        
+        final StyleSheet ss = vexWidget.getStyleSheet();
+        final Document doc = vexWidget.getDocument();
+        final int offset = vexWidget.getCaretOffset();
+
+        // This may or may not be a table
+        // In any case, it's the element that contains the top-level table children
+        Element table = doc.getElementAt(offset);
+        
+        while (table != null && !LayoutUtils.isTableChild(ss, table)) {
+            table = table.getParent();
+        }
+        
+        while (table != null && LayoutUtils.isTableChild(ss, table)) {
+            table = table.getParent();
+        }
+        
+        if (table == null || table.getParent() == null) {
+            return;
+        }
+        
+        final List tableChildren = new ArrayList();
+        final boolean[] found = new boolean[] { false };
+        LayoutUtils.iterateChildrenByDisplayStyle(ss, LayoutUtils.TABLE_CHILD_STYLES, table, new ElementOrRangeCallback() {
+            public void onElement(Element child, String displayStyle) {
+                if (offset >= child.getStartOffset() && offset <= child.getEndOffset()) {
+                    found[0] = true;
+                }
+                tableChildren.add(child);
+            }
+            public void onRange(Element parent, int startOffset, int endOffset) {
+                if (!found[0]) {
+                    tableChildren.clear();
+                }
+            }
+        });
+    
+        if (!found[0]) {
+            return;
+        }
+    
+        int startOffset = ((Element) tableChildren.get(0)).getStartOffset();
+        int endOffset = ((Element) tableChildren.get(tableChildren.size() - 1)).getEndOffset() + 1;
+        LayoutUtils.iterateTableRows(ss, table, startOffset, endOffset, callback);
+    }
+
+    /**
+     * Returns an IntRange representing the offsets inside the given Element or
+     * IntRange. If an Element is passed, returns the offsets inside the 
+     * sentinels. If an IntRange is passed it is returned directly.
+     * 
+     * @param elementOrRange Element or IntRange to be inspected.
+     */
+    public static IntRange getInnerRange(Object elementOrRange) {
+        if (elementOrRange instanceof Element) {
+            Element element = (Element) elementOrRange;
+            return new IntRange(element.getStartOffset() + 1, element.getEndOffset());
+        } else {
+            return (IntRange) elementOrRange;
+        }
+    }
+
+    /**
+     * Returns an IntRange representing the offsets outside the given Element or
+     * IntRange. If an Element is passed, returns the offsets outside the 
+     * sentinels. If an IntRange is passed it is returned directly.
+     * 
+     * @param elementOrRange Element or IntRange to be inspected.
+     */
+    public static IntRange getOuterRange(Object elementOrRange) {
+        if (elementOrRange instanceof Element) {
+            Element element = (Element) elementOrRange;
+            return new IntRange(element.getStartOffset(), element.getEndOffset() + 1);
+        } else {
+            return (IntRange) elementOrRange;
+        }
+    }
+
+    
+    public static class SelectedRows {
+        
+        private SelectedRows() {
+        }
+
+        public List getRows() {
+            return this.rows;
+        }
+        public Object getRowBefore() {
+            return this.rowBefore;
+        }
+        public Object getRowAfter() {
+            return this.rowAfter;
+        }
+        
+        private List rows;
+        private Object rowBefore;
+        private Object rowAfter;
+    }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/DeleteColumnAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/DeleteColumnAction.java
new file mode 100644
index 0000000..802eb34
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/DeleteColumnAction.java
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.wst.xml.vex.core.internal.core.IntRange;
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+
+
+/**
+ * Delete the table column containing the caret.
+ */
+public class DeleteColumnAction extends AbstractVexAction {
+
+    public void run(final IVexWidget vexWidget) {
+        
+        vexWidget.doWork(new Runnable() {
+            public void run() {
+                
+                final ActionUtils.RowColumnInfo rcInfo = ActionUtils.getRowColumnInfo(vexWidget);
+                
+                if (rcInfo == null) {
+                    return;
+                }
+
+                final List cellsToDelete = new ArrayList();
+                ActionUtils.iterateTableCells(vexWidget, new TableCellCallback() {
+                    public void startRow(Object row, int rowIndex) {
+                    }
+                    public void onCell(Object row, Object cell, int rowIndex, int cellIndex) {
+                        if (cellIndex == rcInfo.cellIndex) {
+                            cellsToDelete.add(cell);
+                        }
+                    }
+                    public void endRow(Object row, int rowIndex) {
+                    }
+                });
+
+                // Iterate the deletions in reverse, so that we don't mess up
+                // offsets that are in anonymous cells, which are not stored
+                // as Positions.
+                for (int i = cellsToDelete.size() - 1; i >= 0; i--) {
+                    Object cell = cellsToDelete.get(i);
+                    IntRange range = ActionUtils.getOuterRange(cell);
+                    vexWidget.moveTo(range.getStart());
+                    vexWidget.moveTo(range.getEnd(), true);
+                    vexWidget.deleteSelection();
+                }
+            }
+        });
+        
+    }
+
+    public boolean isEnabled(IVexWidget vexWidget) {
+        return ActionUtils.getCurrentColumnIndex(vexWidget) != -1;
+    }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/DeleteRowAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/DeleteRowAction.java
new file mode 100644
index 0000000..48d50c4
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/DeleteRowAction.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+import java.util.List;
+
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+
+
+/**
+ * Delete selected table rows.
+ */
+public class DeleteRowAction extends AbstractVexAction {
+
+    public void run(final IVexWidget vexWidget) {
+
+        final List rows = ActionUtils.getSelectedTableRows(vexWidget).getRows();
+        
+        if (rows == null) {
+            return;
+        }
+
+        vexWidget.doWork(new Runnable() {
+            public void run() {
+                int startOffset = ActionUtils.getOuterRange(rows.get(0)).getStart();
+                int endOffset = ActionUtils.getOuterRange(rows.get(rows.size() - 1)).getEnd();
+                
+                vexWidget.moveTo(startOffset);
+                vexWidget.moveTo(endOffset, true);
+                vexWidget.deleteSelection();
+            }
+        });
+
+    }
+
+    public boolean isEnabled(IVexWidget vexWidget) {
+        return ActionUtils.getSelectedTableRows(vexWidget).getRows() != null;
+    }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/DuplicateSelectionAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/DuplicateSelectionAction.java
new file mode 100644
index 0000000..7f41b93
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/DuplicateSelectionAction.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+
+
+/**
+ * Duplicates current selection or element.
+ */
+public class DuplicateSelectionAction extends AbstractVexAction {
+
+    public void run(final IVexWidget vexWidget) {
+        
+        vexWidget.doWork(new Runnable() {
+            public void run() {
+                if (!vexWidget.hasSelection()) {
+                    Element element = vexWidget.getCurrentElement();
+                    if (element.getParent() == null) {
+                        // Can't dup the root element
+                        return;
+                    }
+                    vexWidget.moveTo(element.getStartOffset());
+                    vexWidget.moveTo(element.getEndOffset() + 1, true);
+                }
+
+                vexWidget.copySelection();
+                int startOffset = vexWidget.getSelectionEnd(); 
+                vexWidget.moveTo(startOffset);
+                vexWidget.paste();
+                int endOffset = vexWidget.getCaretOffset();
+                vexWidget.moveTo(startOffset);
+                vexWidget.moveTo(endOffset, true);
+            }
+        });
+    }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/IVexAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/IVexAction.java
new file mode 100644
index 0000000..aa9ecfd
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/IVexAction.java
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+
+/**
+ * Interface implemented by command objects that can act on a VexWidget.
+ */
+public interface IVexAction {
+   
+    /**
+     * Performs the action on the VexWidget.
+     * @param vexWidget IVexWidget on which the action is to be performed.
+     */
+    public void run(IVexWidget vexWidget);
+    
+    /**
+     * Returns true if the action is valid for the given VexWidget.
+     * @param vexWidget IVexWidget against which to test validity.
+     */
+    public boolean isEnabled(IVexWidget vexWidget);
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/InsertColumnAfterAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/InsertColumnAfterAction.java
new file mode 100644
index 0000000..5fd253d
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/InsertColumnAfterAction.java
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+
+
+/**
+ * Inserts a single table column after the current one.
+ */
+public class InsertColumnAfterAction extends AbstractVexAction {
+
+    public void run(final IVexWidget vexWidget) {
+
+        final int indexToDup = ActionUtils.getCurrentColumnIndex(vexWidget);
+        if (indexToDup == -1) {
+            return;
+        }
+        
+        vexWidget.doWork(new Runnable() {
+            public void run() {
+
+                final List cellsToDup = new ArrayList();
+                ActionUtils.iterateTableCells(vexWidget, new TableCellCallback() {
+                    public void startRow(Object row, int rowIndex) {
+                    }
+                    public void onCell(Object row, Object cell, int rowIndex, int cellIndex) {
+                        if (cellIndex == indexToDup && cell instanceof Element) {
+                            cellsToDup.add(cell);
+                        }
+                    }
+                    public void endRow(Object row, int rowIndex) {
+                    }
+                });
+                
+                int finalOffset = -1;
+                for (Iterator it = cellsToDup.iterator(); it.hasNext();) {
+                    Element element = (Element) it.next();
+                    if (finalOffset == -1) {
+                        finalOffset = element.getStartOffset() + 1;
+                    }
+                    vexWidget.moveTo(element.getEndOffset() + 1);
+                    vexWidget.insertElement((Element) element.clone());
+                }
+                
+                if (finalOffset != -1) {
+                    vexWidget.moveTo(finalOffset);
+                }
+            }
+        });
+    }
+
+    public boolean isEnabled(IVexWidget vexWidget) {
+        return ActionUtils.getCurrentColumnIndex(vexWidget) != -1;
+    }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/InsertColumnBeforeAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/InsertColumnBeforeAction.java
new file mode 100644
index 0000000..f8f1e89
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/InsertColumnBeforeAction.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+
+
+/**
+ * Inserts a single table column before the current one.
+ */
+public class InsertColumnBeforeAction extends AbstractVexAction {
+
+    public void run(final IVexWidget vexWidget) {
+        
+        vexWidget.doWork(new Runnable() {
+            public void run() {
+                
+                final int indexToDup = ActionUtils.getCurrentColumnIndex(vexWidget);
+                if (indexToDup == -1) {
+                    return;
+                }
+                
+                final List cellsToDup = new ArrayList();
+                ActionUtils.iterateTableCells(vexWidget, new TableCellCallback() {
+                    public void startRow(Object row, int rowIndex) {
+                    }
+                    public void onCell(Object row, Object cell, int rowIndex, int cellIndex) {
+                        if (cellIndex == indexToDup && cell instanceof Element) {
+                            cellsToDup.add(cell);
+                        }
+                    }
+                    public void endRow(Object row, int rowIndex) {
+                    }
+                });
+                
+                int finalOffset = -1;
+                for (Iterator it = cellsToDup.iterator(); it.hasNext();) {
+                    Element element = (Element) it.next();
+                    if (finalOffset == -1) {
+                        finalOffset = element.getStartOffset() + 1;
+                    }
+                    vexWidget.moveTo(element.getStartOffset());
+                    vexWidget.insertElement((Element) element.clone());
+                }
+                
+                if (finalOffset != -1) {
+                    vexWidget.moveTo(finalOffset);
+                }
+
+            }
+        });
+        
+    }
+
+    public boolean isEnabled(IVexWidget vexWidget) {
+        return ActionUtils.getCurrentColumnIndex(vexWidget) != -1;
+    }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/InsertRowAboveAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/InsertRowAboveAction.java
new file mode 100644
index 0000000..00b227a
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/InsertRowAboveAction.java
@@ -0,0 +1,22 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+
+/**
+ * Inserts one or more table rows above the currently selected one(s).
+ */
+public class InsertRowAboveAction extends InsertRowAction {
+
+    public InsertRowAboveAction() {
+        super(true);
+    }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/InsertRowAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/InsertRowAction.java
new file mode 100644
index 0000000..7e44ef9
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/InsertRowAction.java
@@ -0,0 +1,144 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+
+
+/**
+ * Inserts one or more table rows either above or below the currently selected one(s).
+ * This class is meant as a base class for InsertRowAboveAction and 
+ * InsertRowBelowAction.
+ */
+public class InsertRowAction extends AbstractVexAction {
+
+    /**
+     * Class constructor.
+     * @param above If true, the new rows are inserted above the currently
+     * selected ones, else they are inserted below.
+     */
+    public InsertRowAction(boolean above) {
+        this.above = above;
+    }
+    
+    public void run(final IVexWidget vexWidget) {
+        
+        vexWidget.doWork(new Runnable() {
+            public void run() {
+                
+                final List rowsToInsert = new ArrayList();
+                final List rowCellsToInsert = new ArrayList();
+                
+                ActionUtils.iterateTableCells(vexWidget, new TableCellCallback() {
+
+                    boolean rowSelected;
+                    List cellsToInsert;
+                    
+                    public void startRow(Object row, int rowIndex) {
+                        rowSelected = ActionUtils.elementOrRangeIsPartiallySelected(vexWidget, row);
+                        
+                        if (rowSelected) {
+                            cellsToInsert = new ArrayList();
+                        }
+                    }
+
+                    public void onCell(Object row, Object cell, int rowIndex, int cellIndex) {
+                        if (rowSelected) {
+                            cellsToInsert.add(cell);
+                        }
+                    }
+
+                    public void endRow(Object row, int rowIndex) {
+                        if (rowSelected) {
+                            rowsToInsert.add(row);
+                            rowCellsToInsert.add(cellsToInsert);
+                        }
+                    }
+                    
+                });
+                
+                if (rowsToInsert.size() == 0) {
+                    return;
+                }
+                
+
+                //
+                // save the caret offset so that we return just inside the first
+                // table cell
+                //
+                // (innerOffset - outerOffset) represents the final offset of
+                // the caret, relative to the insertion point of the new rows
+                //
+                int outerOffset = ActionUtils.getOuterRange(rowsToInsert.get(0)).getStart();
+                int innerOffset;
+                List firstCells = (List) rowCellsToInsert.get(0);
+                if (firstCells.size() == 0) {
+                    innerOffset = ActionUtils.getInnerRange(rowsToInsert.get(0)).getStart();
+                } else {
+                    innerOffset = ActionUtils.getInnerRange(firstCells.get(0)).getStart();
+                }
+
+                int insertOffset;
+                if (above) {
+                    insertOffset = ActionUtils.getOuterRange(rowsToInsert.get(0)).getStart();
+                } else {
+                    Object lastRow = rowsToInsert.get(rowsToInsert.size() - 1);
+                    insertOffset = ActionUtils.getOuterRange(lastRow).getEnd();
+                }
+
+                int finalOffset = insertOffset + (innerOffset - outerOffset);
+                
+                vexWidget.moveTo(insertOffset);
+                
+                for (int i = 0; i < rowsToInsert.size(); i++) {
+                    
+                    Object row = rowsToInsert.get(i);
+                    
+                    if (row instanceof Element) {
+                        vexWidget.insertElement((Element) ((Element) row).clone());
+                    }
+                    
+                    List cellsToInsert = (List) rowCellsToInsert.get(i);
+                    
+                    for (int j = 0; j < cellsToInsert.size(); j++) {
+                        Object cell = cellsToInsert.get(j);
+                        if (cell instanceof Element) {
+                            vexWidget.insertElement((Element) ((Element) cell).clone());
+                            vexWidget.moveBy(+1);
+                        } else {
+                            vexWidget.insertText(" ");
+                        }
+                    }
+                    
+                    if (row instanceof Element) {
+                        vexWidget.moveBy(+1);
+                    }
+                    
+                }
+                
+                vexWidget.moveTo(finalOffset);
+            }
+        });
+
+    }
+
+    public boolean isEnabled(IVexWidget vexWidget) {
+        // TODO only enable (a) if rows are selected, and (b) if not inserting
+        // adjacent anonymous rows
+        return true;
+    }
+
+    private boolean above;
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/InsertRowBelowAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/InsertRowBelowAction.java
new file mode 100644
index 0000000..9ce6ac3
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/InsertRowBelowAction.java
@@ -0,0 +1,22 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+
+/**
+ * Inserts one or more table rows below the currently selected one(s).
+ */
+public class InsertRowBelowAction extends InsertRowAction {
+    
+    public InsertRowBelowAction() {
+        super(false);
+    }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/MoveColumnLeftAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/MoveColumnLeftAction.java
new file mode 100644
index 0000000..946ea6c
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/MoveColumnLeftAction.java
@@ -0,0 +1,91 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.wst.xml.vex.core.internal.core.IntRange;
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+
+
+/**
+ * Moves the current table column to the left.
+ */
+public class MoveColumnLeftAction extends AbstractVexAction {
+
+    public void run(final IVexWidget vexWidget) {
+
+        final ActionUtils.RowColumnInfo rcInfo = ActionUtils.getRowColumnInfo(vexWidget);
+
+        if (rcInfo == null || rcInfo.cellIndex < 1) {
+            return;
+        }
+        
+        vexWidget.doWork(true, new Runnable() {
+            public void run() {
+
+                // Cells to the left of the current column
+                final List sourceCells = new ArrayList();
+                
+                // Cells in the current column
+                final List destCells = new ArrayList();
+
+                ActionUtils.iterateTableCells(vexWidget, new TableCellCallback() {
+                    Object prevCell = null;
+                    public void startRow(Object row, int rowIndex) {
+                    }
+                    public void onCell(Object row, Object cell, int rowIndex, int cellIndex) {
+                        if (cellIndex == rcInfo.cellIndex) {
+                            sourceCells.add(this.prevCell);
+                            destCells.add(cell);
+                        } else if (cellIndex == rcInfo.cellIndex - 1) {
+                            this.prevCell = cell;
+                        }
+                    }
+                    public void endRow(Object row, int rowIndex) {
+                    }
+                });
+
+                // Iterate the deletions in reverse, so that we don't mess up
+                // offsets that are in anonymous cells, which are not stored
+                // as Positions.
+                //
+                // Also, to preserve the current caret position, we don't cut
+                // and paste the current column. Instead, we cut the column
+                // to the left of the current column and paste it on the right.
+                for (int i = sourceCells.size() - 1; i >= 0; i--) {
+
+                    Object source = sourceCells.get(i);
+                    final IntRange sourceRange = ActionUtils.getOuterRange(source);
+                    
+                    Object dest = destCells.get(i);
+                    vexWidget.moveTo(ActionUtils.getOuterRange(dest).getEnd());
+
+                    vexWidget.savePosition(new Runnable() {
+                        public void run() {
+                            vexWidget.moveTo(sourceRange.getStart());
+                            vexWidget.moveTo(sourceRange.getEnd(), true);
+                            vexWidget.cutSelection();
+                        }
+                    });
+                    
+                    vexWidget.paste();
+                }
+            }
+        });
+    }
+
+    public boolean isEnabled(IVexWidget vexWidget) {
+        ActionUtils.RowColumnInfo rcInfo = ActionUtils.getRowColumnInfo(vexWidget);
+        return rcInfo != null && rcInfo.cellIndex > 0;
+    }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/MoveColumnRightAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/MoveColumnRightAction.java
new file mode 100644
index 0000000..784c00e
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/MoveColumnRightAction.java
@@ -0,0 +1,91 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.wst.xml.vex.core.internal.core.IntRange;
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+
+
+/**
+ * Moves the current table column to the right.
+ */
+public class MoveColumnRightAction extends AbstractVexAction {
+
+    public void run(final IVexWidget vexWidget) {
+        
+        final ActionUtils.RowColumnInfo rcInfo = ActionUtils.getRowColumnInfo(vexWidget);
+
+        if (rcInfo == null || rcInfo.cellIndex >= rcInfo.maxColumnCount - 1) {
+            return;
+        }
+        
+        vexWidget.doWork(true, new Runnable() {
+            public void run() {
+
+                // Cells to the right of the current column
+                final List sourceCells = new ArrayList();
+                
+                // Cells in the current column
+                final List destCells = new ArrayList();
+
+                ActionUtils.iterateTableCells(vexWidget, new TableCellCallback() {
+                    Object thisCell = null;
+                    public void startRow(Object row, int rowIndex) {
+                    }
+                    public void onCell(Object row, Object cell, int rowIndex, int cellIndex) {
+                        if (cellIndex == rcInfo.cellIndex) {
+                            this.thisCell = cell;
+                        } else if (cellIndex == rcInfo.cellIndex + 1) {
+                            sourceCells.add(cell);
+                            destCells.add(this.thisCell);
+                        }
+                    }
+                    public void endRow(Object row, int rowIndex) {
+                    }
+                });
+
+                // Iterate the deletions in reverse, so that we don't mess up
+                // offsets that are in anonymous cells, which are not stored
+                // as Positions.
+                //
+                // Also, to preserve the current caret position, we don't cut
+                // and paste the current column. Instead, we cut the column
+                // to the right of the current column and paste it on the left.
+                for (int i = sourceCells.size() - 1; i >= 0; i--) {
+
+                    Object source = sourceCells.get(i);
+                    final IntRange sourceRange = ActionUtils.getOuterRange(source);
+                    
+                    Object dest = destCells.get(i);
+                    vexWidget.moveTo(ActionUtils.getOuterRange(dest).getStart());
+
+                    vexWidget.savePosition(new Runnable() {
+                        public void run() {
+                            vexWidget.moveTo(sourceRange.getStart());
+                            vexWidget.moveTo(sourceRange.getEnd(), true);
+                            vexWidget.cutSelection();
+                        }
+                    });
+                    
+                    vexWidget.paste();
+                }
+            }
+        });
+    }
+
+    public boolean isEnabled(IVexWidget vexWidget) {
+        ActionUtils.RowColumnInfo rcInfo = ActionUtils.getRowColumnInfo(vexWidget);
+        return rcInfo != null && rcInfo.cellIndex < rcInfo.maxColumnCount - 1;
+    }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/MoveRowDownAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/MoveRowDownAction.java
new file mode 100644
index 0000000..f4b9df0
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/MoveRowDownAction.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+import org.eclipse.wst.xml.vex.core.internal.core.IntRange;
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+
+
+/**
+ * Moves the current table row down below its next sibling.
+ */
+public class MoveRowDownAction extends AbstractVexAction {
+
+    public void run(final IVexWidget vexWidget) {
+
+        final ActionUtils.SelectedRows selected = ActionUtils.getSelectedTableRows(vexWidget);
+        
+        if (selected.getRows() == null || selected.getRowAfter() == null) {
+            return;
+        }
+        
+        vexWidget.doWork(true, new Runnable() {
+            public void run() {
+                IntRange range = ActionUtils.getOuterRange(selected.getRowAfter());
+                vexWidget.moveTo(range.getStart());
+                vexWidget.moveTo(range.getEnd(), true);
+                vexWidget.cutSelection();
+                
+                Object firstRow = selected.getRows().get(0);
+                vexWidget.moveTo(ActionUtils.getOuterRange(firstRow).getStart());
+                vexWidget.paste();
+            }
+        });
+    }
+
+    public boolean isEnabled(IVexWidget vexWidget) {
+        ActionUtils.SelectedRows selected = ActionUtils.getSelectedTableRows(vexWidget);
+        return selected.getRows() != null && selected.getRowAfter() != null;
+    }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/MoveRowUpAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/MoveRowUpAction.java
new file mode 100644
index 0000000..2da3806
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/MoveRowUpAction.java
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+import java.util.List;
+
+import org.eclipse.wst.xml.vex.core.internal.core.IntRange;
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+
+
+/**
+ * Moves the current table row up above its previous sibling.
+ */
+public class MoveRowUpAction extends AbstractVexAction {
+
+    public void run(final IVexWidget vexWidget) {
+
+        final ActionUtils.SelectedRows selected = ActionUtils.getSelectedTableRows(vexWidget);
+        
+        if (selected.getRows() == null || selected.getRowBefore() == null) {
+            return;
+        }
+        
+        vexWidget.doWork(true, new Runnable() {
+            public void run() {
+                IntRange range = ActionUtils.getOuterRange(selected.getRowBefore());
+                vexWidget.moveTo(range.getStart());
+                vexWidget.moveTo(range.getEnd(), true);
+                vexWidget.cutSelection();
+                
+                List rows = selected.getRows();
+                Object lastRow = rows.get(rows.size() - 1);
+                vexWidget.moveTo(ActionUtils.getOuterRange(lastRow).getEnd());
+                vexWidget.paste();
+            }
+        });
+    }
+
+    public boolean isEnabled(IVexWidget vexWidget) {
+        ActionUtils.SelectedRows selected = ActionUtils.getSelectedTableRows(vexWidget);
+        return selected.getRows() != null && selected.getRowBefore() != null;
+    }
+    
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/MoveSelectionUpAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/MoveSelectionUpAction.java
new file mode 100644
index 0000000..1139873
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/MoveSelectionUpAction.java
@@ -0,0 +1,80 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.layout.BlockBox;
+import org.eclipse.wst.xml.vex.core.internal.layout.Box;
+import org.eclipse.wst.xml.vex.core.internal.widget.IBoxFilter;
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+
+
+/**
+ * Moves the current selection or block element above the previous sibling.
+ * WORK IN PROGRESS.
+ */
+public class MoveSelectionUpAction extends AbstractVexAction {
+
+    public void run(final IVexWidget vexWidget) {
+
+        // First we determine whether we should expand the selection
+        // to contain an entire block box.
+        
+        // Find the lowest block box that completely contains the
+        // selection
+        Box box = vexWidget.findInnermostBox(new IBoxFilter() {
+            public boolean matches(Box box) {
+                return box instanceof BlockBox
+                && box.getElement() != null
+                && box.getStartOffset() <= vexWidget.getSelectionStart()
+                && box.getEndOffset() >= vexWidget.getSelectionEnd();
+            }
+        });
+        
+        Box[] children = box.getChildren();
+        if (children.length > 0 && children[0] instanceof BlockBox) {
+            // The found box contains other block children, so we
+            // do NOT have to expand the selection
+        } else {
+            // Expand selection to the containing box
+            
+            // (Note: This "if" is caused by the fact that getStartOffset is treated
+            // differently between elements and boxes. Boxes own their startOffset,
+            // while elements don't own theirs. Perhaps we should fix this by having
+            // box.getStartOffset() return box.getStartPosition() + 1, but this would
+            // be a VERY large change.)
+            System.out.println("Box is " + box);
+            Element element = box.getElement();
+            if (element != null) {
+                vexWidget.moveTo(element.getEndOffset());
+                vexWidget.moveTo(element.getStartOffset(), true);
+                
+            } else {
+                vexWidget.moveTo(box.getEndOffset());
+                vexWidget.moveTo(box.getStartOffset(), true);
+            }
+        }
+        
+//        final int previousSiblingStart = ActionUtils.getPreviousSiblingStart(vexWidget);
+//        
+//        vexWidget.doWork(new IRunnable() {
+//            public void run() throws Exception {
+//                vexWidget.cutSelection();
+//                vexWidget.moveTo(previousSiblingStart);
+//                vexWidget.paste();
+//                vexWidget.moveTo(previousSiblingStart, true);
+//            }
+//        });
+        
+            
+    }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/NextTableCellAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/NextTableCellAction.java
new file mode 100644
index 0000000..2b4b29e
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/NextTableCellAction.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+import org.eclipse.wst.xml.vex.core.internal.layout.Box;
+import org.eclipse.wst.xml.vex.core.internal.layout.TableRowBox;
+import org.eclipse.wst.xml.vex.core.internal.widget.IBoxFilter;
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+
+
+/**
+ * Moves the caret to the next table cell. The contents of the cell
+ * are selected. If the current cell is the last cell in the table, 
+ * the current row is duplicated.
+ */
+public class NextTableCellAction extends AbstractVexAction {
+
+    public void run(final IVexWidget vexWidget) {
+        
+        final TableRowBox tr = (TableRowBox) vexWidget.findInnermostBox(new IBoxFilter() {
+            public boolean matches(Box box) {
+                return box instanceof TableRowBox;
+            }
+        });
+
+        if (tr == null) {
+            // not in a table row
+            return;
+        }
+        
+        int offset = vexWidget.getCaretOffset();
+        
+        Box[] cells = tr.getChildren();
+        for (int i = 0; i < cells.length; i++) {
+            if (cells[i].getStartOffset() > offset) {
+                vexWidget.moveTo(cells[i].getStartOffset());
+                vexWidget.moveTo(cells[i].getEndOffset(), true);
+                return;
+            }
+        }
+        
+        // No next cell found in this row
+        // Find the next row
+        Box[] rows = tr.getParent().getChildren(); 
+        for (int i = 0; i < rows.length; i++) {
+            if (rows[i].getStartOffset() > offset) {
+                cells = rows[i].getChildren();
+                if (cells.length > 0) {
+                    Box cell = cells[0];
+                    vexWidget.moveTo(cell.getStartOffset());
+                    vexWidget.moveTo(cell.getEndOffset(), true);
+                } else {
+                    System.out.println("TODO - dup row into new empty row");
+                }
+                return;
+            }
+        }
+        
+
+        // We didn't find a "next row", so let's dup the current one
+        ActionUtils.duplicateTableRow(vexWidget, tr);
+ 
+    }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/PasteTextAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/PasteTextAction.java
new file mode 100644
index 0000000..38dca68
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/PasteTextAction.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+
+/**
+ * Paste the clipboard contents into the document as plain text, ignoring
+ * any markup.
+ */
+public class PasteTextAction extends AbstractVexAction {
+
+    public void run(IVexWidget vexWidget) {
+        throw new UnsupportedOperationException("PasteTextAction is not yet implemented.");
+    }
+
+    public boolean isEnabled(IVexWidget vexWidget) {
+        return false;
+    }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/PreviousTableCellAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/PreviousTableCellAction.java
new file mode 100644
index 0000000..045f922
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/PreviousTableCellAction.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+import org.eclipse.wst.xml.vex.core.internal.layout.Box;
+import org.eclipse.wst.xml.vex.core.internal.layout.TableRowBox;
+import org.eclipse.wst.xml.vex.core.internal.widget.IBoxFilter;
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+
+
+/**
+ * Moves the caret to the next table cell. The contents of the cell
+ * are selected. If the current cell is the last cell in the table, 
+ * the current row is duplicated.
+ */
+public class PreviousTableCellAction extends AbstractVexAction {
+
+    public void run(final IVexWidget vexWidget) {
+        
+        final TableRowBox tr = (TableRowBox) vexWidget.findInnermostBox(new IBoxFilter() {
+            public boolean matches(Box box) {
+                return box instanceof TableRowBox;
+            }
+        });
+
+        if (tr == null) {
+            // not in a table row
+            return;
+        }
+        
+        int offset = vexWidget.getCaretOffset();
+        
+        Box[] cells = tr.getChildren();
+        for (int i = cells.length - 1; i >= 0; i--) {
+            if (cells[i].getEndOffset() < offset) {
+                vexWidget.moveTo(cells[i].getStartOffset());
+                vexWidget.moveTo(cells[i].getEndOffset(), true);
+                return;
+            }
+        }
+        
+        // No next cell found in this row
+        // Find the previous row
+        Box[] rows = tr.getParent().getChildren(); 
+        for (int i = rows.length - 1; i >= 0; i--) {
+            if (rows[i].getEndOffset() < offset) {
+                cells = rows[i].getChildren();
+                if (cells.length > 0) {
+                    Box cell = cells[cells.length - 1];
+                    vexWidget.moveTo(cell.getStartOffset());
+                    vexWidget.moveTo(cell.getEndOffset(), true);
+                }
+                return;
+            }
+        }
+        
+    }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/RemoveElementAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/RemoveElementAction.java
new file mode 100644
index 0000000..6cc3a94
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/RemoveElementAction.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentFragment;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+
+
+/**
+ * Removes the current element, adding its content to the parent element.
+ */
+public class RemoveElementAction extends AbstractVexAction {
+
+    public void run(final IVexWidget vexWidget) {
+        vexWidget.doWork(new Runnable() {
+            public void run() {
+                Element element =
+                    vexWidget.getDocument().getElementAt(vexWidget.getCaretOffset());
+                vexWidget.moveTo(element.getStartOffset() + 1, false);
+                vexWidget.moveTo(element.getEndOffset(), true);
+                DocumentFragment frag = vexWidget.getSelectedFragment();
+                vexWidget.deleteSelection();
+                vexWidget.moveBy(-1, false);
+                vexWidget.moveBy(2, true);
+                vexWidget.deleteSelection();
+                vexWidget.insertFragment(frag);
+            }
+        });
+    }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/RestoreLastSelectionAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/RestoreLastSelectionAction.java
new file mode 100644
index 0000000..1956978
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/RestoreLastSelectionAction.java
@@ -0,0 +1,27 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+
+/**
+ * Restore the selection to what it was before it last changed.
+ */
+public class RestoreLastSelectionAction extends AbstractVexAction {
+
+    public void run(IVexWidget vexWidget) {
+        throw new UnsupportedOperationException("RestoreLastSelectionAction is not yet implemented.");
+    }
+
+    public boolean isEnabled(IVexWidget vexWidget) {
+        return false;
+    }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/SplitAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/SplitAction.java
new file mode 100644
index 0000000..d41448c
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/SplitAction.java
@@ -0,0 +1,104 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.wst.xml.vex.core.internal.VEXCorePlugin;
+import org.eclipse.wst.xml.vex.core.internal.css.CSS;
+import org.eclipse.wst.xml.vex.core.internal.css.Styles;
+import org.eclipse.wst.xml.vex.core.internal.dom.Document;
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentFragment;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+
+
+/**
+ * Splits the current block element.
+ */
+public class SplitAction extends AbstractVexAction {
+
+    public void run(final IVexWidget vexWidget) {
+        Element element = vexWidget.getCurrentElement();
+        Styles styles = vexWidget.getStyleSheet().getStyles(element);
+        while (!styles.isBlock()) {
+            element = element.getParent();
+            styles = vexWidget.getStyleSheet().getStyles(element);
+        }
+        splitElement(vexWidget, element);
+    }
+
+
+    /**
+     * Splits the given element.
+     * @param vexWidget IVexWidget containing the document.
+     * @param element Element to be split.
+     */
+    public static void splitElement(final IVexWidget vexWidget, final Element element) {
+        
+        vexWidget.doWork(new Runnable() {
+            public void run() {
+
+            	long start = 0;
+            	if (VEXCorePlugin.getInstance().isDebugging()) {
+            		start = System.currentTimeMillis();
+            	}
+                
+                Styles styles = vexWidget.getStyleSheet().getStyles(element);
+                
+                if (styles.getWhiteSpace().equals(CSS.PRE)) {
+                    // can't call vexWidget.insertText() or we'll get an infinite loop
+                    Document doc = vexWidget.getDocument();
+                    int offset = vexWidget.getCaretOffset();
+                    doc.insertText(offset, "\n");
+                    vexWidget.moveTo(offset + 1);
+                } else {
+                    
+                    // There may be a number of child elements below the given
+                    // element. We cut out the tails of each of these elements
+                    // and put them in a list of fragments to be reconstructed when
+                    // we clone the element.
+                    List children = new ArrayList();
+                    List frags = new ArrayList();
+                    Element child = vexWidget.getCurrentElement();
+                    while (true) {
+                        children.add(child);
+                        vexWidget.moveTo(child.getEndOffset(), true);
+                        frags.add(vexWidget.getSelectedFragment());
+                        vexWidget.deleteSelection();
+                        vexWidget.moveTo(child.getEndOffset() + 1);
+                        if (child == element) {
+                            break;
+                        }
+                        child = child.getParent();
+                    }
+
+                    for (int i = children.size() - 1; i >= 0; i--) {
+                        child = (Element) children.get(i);
+                        DocumentFragment frag = (DocumentFragment) frags.get(i);
+                        vexWidget.insertElement((Element) child.clone());
+                        int offset = vexWidget.getCaretOffset();
+                        if (frag != null) {
+                            vexWidget.insertFragment(frag);
+                        }
+                        vexWidget.moveTo(offset);
+                    }
+                }
+
+                if (VEXCorePlugin.getInstance().isDebugging()) {
+                    long end = System.currentTimeMillis();
+                    System.out.println("split() took " + (end - start) + "ms");
+                }
+            }
+        });
+    }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/SplitItemAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/SplitItemAction.java
new file mode 100644
index 0000000..1c328fb
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/SplitItemAction.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+import org.eclipse.wst.xml.vex.core.internal.css.CSS;
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheet;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.layout.Box;
+import org.eclipse.wst.xml.vex.core.internal.layout.TableRowBox;
+import org.eclipse.wst.xml.vex.core.internal.widget.IBoxFilter;
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+
+
+/**
+ * Splits the nearest enclosing table row or list item. If a table row is being
+ * split, empty versions of the current row's cells are created.
+ */
+public class SplitItemAction extends AbstractVexAction {
+
+    public void run(IVexWidget vexWidget) {
+
+        final StyleSheet ss = vexWidget.getStyleSheet();
+        
+        // Item is either a TableRowBox or a BlockElementBox representing
+        // a list item
+        Box item = vexWidget.findInnermostBox(new IBoxFilter() {
+            public boolean matches(Box box) {
+                if (box instanceof TableRowBox) {
+                    return true;
+                } else {
+                    Element element = box.getElement();
+                    return element != null && ss.getStyles(element).getDisplay().equals(CSS.LIST_ITEM);
+                }
+            }
+        });
+
+        if (item instanceof TableRowBox) {
+            insertRowBelowAction.run(vexWidget);
+            //ActionUtils.duplicateTableRow(vexWidget, (TableRowBox) item);
+        } else if (item != null) {
+            SplitAction.splitElement(vexWidget, item.getElement());
+        }
+    }
+    
+    private static InsertRowBelowAction insertRowBelowAction = new InsertRowBelowAction();
+    
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/TableCellCallback.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/TableCellCallback.java
new file mode 100644
index 0000000..907bc30
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/action/TableCellCallback.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.action;
+
+/**
+ * Callback interface called from LayoutUtils.iterateTableCells.
+ */
+public interface TableCellCallback {
+
+    /**
+     * Called before the first cell in a row is visited.
+     * 
+     * @param row Element or IntRange representing the row.
+     * @param rowIndex Zero-based index of the row.
+     */
+    public void startRow(Object row, int rowIndex);
+    
+    /**
+     * Called when a cell is visited.
+     * 
+     * @param row Element or IntRange representing the row.
+     * @param cell Element or IntRange representing the cell.
+     * @param rowIndex Zero-based index of the current row.
+     * @param cellIndex Zero-based index of the current cell.
+     */
+    public void onCell(Object row, Object cell, int rowIndex, int cellIndex);
+
+    /**
+     * Called after the last cell in a row is visited.
+     * 
+     * @param row Element or IntRange representing the row.
+     * @param rowIndex Zero-based index of the row.
+     */
+    public void endRow(Object row, int rowIndex);
+    
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/Caret.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/Caret.java
new file mode 100644
index 0000000..1078ebc
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/Caret.java
@@ -0,0 +1,67 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.core;
+
+/**
+ * Represents the caret, a line that indicates an insertion point in the 
+ * document.
+ */
+public abstract class Caret {
+
+    private int x;
+    private int y;
+    
+    /**
+     * Class constructor
+     * @param x x-coordinate of the top left corner of the caret
+     * @param y y-coordinate of the top left corner of the caret
+     */
+    public Caret(int x, int y) {
+        this.x = x;
+        this.y = y;
+    }
+    
+    /**
+     * Draws the caret in the given Graphics context.
+     * @param g Graphics within which the caret should be drawn.
+     * @param color Color with which the caret should be drawn.
+     */
+    public abstract void draw(Graphics g, Color color);
+    
+    /**
+     * Returns the smallest rectangle that completely encloses the caret.
+     */
+    public abstract Rectangle getBounds();
+    
+    /**
+     * Returns the x-coordinate of the top left corner of the caret
+     */
+    public int getX() {
+        return this.x;
+    }
+   
+    /**
+     * Returns the y-coordinate of the top left corner of the caret
+     */
+    public int getY() {
+        return this.y;
+    }
+   
+    /**
+     * Moves the caret by the given x and y distance.
+     * @param x amount by which to move the caret to the right
+     * @param y amount by which to move the caret down
+     */
+    public void translate(int x, int y) {
+        this.x += x;
+        this.y += y;
+    }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/Color.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/Color.java
new file mode 100644
index 0000000..420774a
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/Color.java
@@ -0,0 +1,84 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.core;
+
+/**
+ * Toolkit-independent representation of a color. Colors consist of three
+ * integers in the range 0..255 representing red, green, and blue components.
+ * Objects of this class are immutable.
+ */
+public class Color {
+
+    public static final Color BLACK = new Color(0, 0, 0);
+    
+    private int red;
+    private int green;
+    private int blue;
+    
+    /**
+     * Class constructor.
+     *
+     * @param red red value, 0..255
+     * @param green green value, 0..255
+     * @param blue blue value, 0..255
+     */
+    public Color(int red, int green, int blue) {
+        this.red = red;
+        this.green = green;
+        this.blue = blue;
+    }
+
+    /**
+     * Returns the blue component of the color, in the range 0..255
+     */
+    public int getBlue() {
+        return blue;
+    }
+
+    /**
+     * Returns the green component of the color, in the range 0..255
+     */
+    public int getGreen() {
+        return green;
+    }
+
+    /**
+     * Returns the red component of the color, in the range 0..255
+     */
+    public int getRed() {
+        return red;
+    }
+    
+    public boolean equals(Object o) {
+        if (o.getClass() != this.getClass()) {
+            return false;
+        }
+        Color c = (Color) o;
+        return this.red == c.red && this.green == c.green && this.blue == c.blue; 
+    }
+
+    public int hashCode() {
+        return this.red + this.green << 16 + this.blue << 24; 
+    }
+    
+    public String toString() {
+        StringBuffer sb = new StringBuffer(20);
+        sb.append("Color[r=");
+        sb.append(this.red);
+        sb.append(",g=");
+        sb.append(this.green);
+        sb.append(",b=");
+        sb.append(this.blue);
+        sb.append("]");
+        return sb.toString();
+    }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/ColorResource.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/ColorResource.java
new file mode 100644
index 0000000..94ad77f
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/ColorResource.java
@@ -0,0 +1,25 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.core;
+
+/**
+ * Wrapper for a toolkit-defined color. Color objects are system resources.
+ * They should be retrieved with the Graphics.createColor method and should
+ * be disposed when no longer needed.
+ */
+public interface ColorResource {
+
+    public static final int SELECTION_BACKGROUND = 0;
+    public static final int SELECTION_FOREGROUND = 1;
+    
+    public void dispose();
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/DisplayDevice.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/DisplayDevice.java
new file mode 100644
index 0000000..f4527fd
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/DisplayDevice.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.core;
+
+/**
+ * Represents a device that can display graphics. This class is subclassed
+ * for each target system.
+ */
+public abstract class DisplayDevice {
+
+    /**
+     * Class constructor. 
+     */
+    public DisplayDevice() {
+    }
+
+    /**
+     * Returns the current display device.
+     */
+    public static DisplayDevice getCurrent() {
+        return current;
+    }
+    /**
+     * Returns the horizontal resolution of the device, in pixels-per-inch.
+     */
+    public abstract int getHorizontalPPI();
+    
+
+    /**
+     * Returns the horizontal resolution of the device, in pixels-per-inch.
+     */
+    public abstract int getVerticalPPI();
+
+    
+    /**
+     * Sets the current display device. This is typically called by the 
+     * platform-specific widget; 
+     * @param current The device to use as the current device.
+     */
+    public static void setCurrent(DisplayDevice current) {
+        DisplayDevice.current = current;
+    }
+    
+    //======================================================= PRIVATE
+    
+    private static DisplayDevice current;
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/Drawable.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/Drawable.java
new file mode 100644
index 0000000..d393279
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/Drawable.java
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.core;
+
+/**
+ * An object that can be drawn into a Graphics.
+ */
+public interface Drawable {
+    
+    /**
+     * Draw the object.
+     * @param g Graphics into which to draw the object.
+     * @param x x-coordinate where the object should be drawn
+     * @param y y-coordinate where the object should be drawn
+     */
+    public void draw(Graphics g, int x, int y);
+
+    /**
+     * Returns the smallest rectangle that completely encloses the 
+     * drawn shape.
+     */
+    public Rectangle getBounds();
+    
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/FontMetrics.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/FontMetrics.java
new file mode 100644
index 0000000..b758735
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/FontMetrics.java
@@ -0,0 +1,21 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.core;
+
+/**
+ * Toolkit-independent font metrics.
+ */
+public interface FontMetrics {
+    public int getAscent();
+    public int getDescent();
+    public int getHeight();
+    public int getLeading();
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/FontResource.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/FontResource.java
new file mode 100644
index 0000000..58019d6
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/FontResource.java
@@ -0,0 +1,21 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.core;
+
+/**
+ * Wrapper for a toolkit-defined font. Fonts are system-defined resources.
+ * They must be retrieved from the Graphics.createFont method, and must be
+ * disposed when no longer needed.
+ */
+public interface FontResource {
+
+    public void dispose();
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/FontSpec.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/FontSpec.java
new file mode 100644
index 0000000..1aec35d
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/FontSpec.java
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *     Dave Holroyd - Implement text decoration
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.core;
+
+/**
+ * Toolkit-independent specifier of a font. This class does not encapsulate
+ * an actual font, but simply the information needed
+ * for the toolkit to find an actual font.
+ * 
+ * <p>An array of font family names may be specified. If more than one name
+ * is specified, the toolkit should select the first name that matches an
+ * actual font on the platform.</p>
+ */
+public class FontSpec {
+    
+    public static final int PLAIN         = 0x0;
+    public static final int BOLD          = 1<<0;
+    public static final int ITALIC        = 1<<1;
+    public static final int UNDERLINE     = 1<<2;
+    public static final int OVERLINE      = 1<<3;
+    public static final int LINE_THROUGH  = 1<<4;
+
+    private String[] names;
+    private float size;
+    private int style;
+
+    /**
+     * Class constructor.
+     * @param names Array of names of the font family.
+     * @param style Bitwise-OR of the applicable style flages, e.g. BOLD | ITALIC
+     * @param size Size of the font, in points.
+     */
+    public FontSpec(String[] names, int style, float size) {
+        this.names = names;
+        this.style = style;
+        this.size = size;
+    }
+    
+    /**
+     * Returns the names of the font families that match the font.
+     */
+    public String[] getNames() {
+        return names;
+    }
+
+    /**
+     * Returns the size of the font in points.
+     */
+    public float getSize() {
+        return size;
+    }
+
+    /**
+     * Returns a bitwise-OR of the style flags. The following sample checks if
+     * the font is bold.
+     * 
+     * <pre>
+     * if (font.getStyle | VexFont.BOLD) {
+     *     // do something bold...
+     * }
+     * </pre>
+     */
+    public int getStyle() {
+        return style;
+    }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/Graphics.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/Graphics.java
new file mode 100644
index 0000000..1d71711
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/Graphics.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.core;
+
+
+/**
+ * Interface through which Vex performs graphics operations. Implemented
+ * by adapters to the java.awt.Graphics and org.eclipse.swt.graphics.GC
+ * classes.
+ */
+public interface Graphics {
+    
+    public static final int LINE_SOLID = 0;
+    public static final int LINE_DASH = 1;
+    public static final int LINE_DOT = 2;
+    
+    public int charsWidth(char[] data, int offset, int length);
+    public ColorResource createColor(Color rgb);
+    public FontResource createFont(FontSpec fontSpec);
+    public void dispose();
+    public void drawChars(char[] chars, int offset, int length, int x, int y);
+    public void drawLine(int x1, int y1, int x2, int y2);
+    
+    /**
+     * Draw the given string at the given point using the current font.
+     * @param s string to draw
+     * @param x x-coordinate of the top left corner of the text box
+     * @param y y-coordinate of the top left corner of the text box
+     */
+    public void drawString(String s, int x, int y);
+    public void drawOval(int x, int y, int width, int height);
+    public void drawRect(int x, int y, int width, int height);
+    public void fillOval(int x, int y, int width, int height);
+    public void fillRect(int x, int y, int width, int height);
+    public Rectangle getClipBounds();
+    public ColorResource getColor();
+    public FontResource getFont();
+    public int getLineStyle();
+    public int getLineWidth();
+    public ColorResource getSystemColor(int id);
+    public FontMetrics getFontMetrics();
+    public boolean isAntiAliased();
+    public void setAntiAliased(boolean antiAliased);
+    public ColorResource setColor(ColorResource color);
+    public FontResource setFont(FontResource font);
+    public void setLineStyle(int style);
+    public void setLineWidth(int width);
+    
+    public int stringWidth(String s);
+    
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/Insets.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/Insets.java
new file mode 100644
index 0000000..9d63ffa
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/Insets.java
@@ -0,0 +1,84 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.core;
+
+/**
+ * Toolkit-independent insets.
+ */
+public class Insets {
+    
+    private int top;
+    private int left;
+    private int bottom;
+    private int right;
+
+    /** Zero insets */
+    public static final Insets ZERO_INSETS = new Insets(0, 0, 0, 0);
+    
+    /**
+     * Class constructor.
+     * 
+     * @param top Top inset.
+     * @param left Left inset.
+     * @param bottom Bottom inset.
+     * @param right Right inset.
+     */
+    public Insets(int top, int left, int bottom, int right) {
+        this.top = top;
+        this.left = left;
+        this.bottom = bottom;
+        this.right = right;
+    }
+
+    /**
+     * @return Returns the top.
+     */
+    public int getTop() {
+        return top;
+    }
+
+    /**
+     * @return Returns the left.
+     */
+    public int getLeft() {
+        return left;
+    }
+
+    /**
+     * @return Returns the bottom.
+     */
+    public int getBottom() {
+        return bottom;
+    }
+
+    /**
+     * Returns the right inset.
+     */
+    public int getRight() {
+        return right;
+    }
+
+    public String toString() {
+        StringBuffer sb = new StringBuffer(80);
+        sb.append(Insets.class.getName());
+        sb.append("[top=");
+        sb.append(this.getTop());
+        sb.append(",left=");
+        sb.append(this.getLeft());
+        sb.append(",bottom=");
+        sb.append(this.getBottom());
+        sb.append(",right=");
+        sb.append(this.getRight());
+        sb.append("]");
+        return sb.toString();
+    }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/IntRange.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/IntRange.java
new file mode 100644
index 0000000..cdb66b2
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/IntRange.java
@@ -0,0 +1,100 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.core;
+
+/**
+ * Represents a range of integers. Zero-length ranges (i.e. ranges where
+ * start == end) are permitted. This class is immutable.
+ */
+public class IntRange {
+
+    /**
+     * Class constuctor.
+     * @param start Start of the range.
+     * @param end End of the range. Must be >= start.
+     */
+    public IntRange(int start, int end) {
+        if (start > end) {
+            throw new IllegalArgumentException("start (" + start + ") is greater than end (" + end + ")");
+        }
+        this.start = start;
+        this.end = end;
+    }
+    
+    /**
+     * Returns the start of the range.
+     */
+    public int getStart() {
+        return this.start;
+    }
+
+    /**
+     * Returns the end of the range.
+     */
+    public int getEnd() {
+        return this.end;
+    }
+    
+    /**
+     * Returns the range that represents the intersection of this range
+     * and the given range. If the ranges do not intersect, returns null.
+     * May return an empty range.
+     * @param range Range with which to perform an intersection.
+     */
+    public IntRange intersection(IntRange range) {
+        if (this.intersects(range)) {
+            return new IntRange(Math.max(this.start, range.start), Math.min(this.end, range.end));
+        } else {
+            return null;
+        }
+        
+    }
+
+    /**
+     * Returns true if this range intersects the given range, even if the 
+     * result would be an empty range.
+     * @param range Range with which to intersect.
+     */
+    public boolean intersects(IntRange range) {
+        return this.start <= range.end && this.end >= range.start;
+    }
+
+    /**
+     * Returns true if start and end are equal.
+     */
+    public boolean isEmpty() {
+        return start == end;
+    }
+
+    /**
+     * Returns a range that is the union of this range and the given range.
+     * If the ranges are disjoint, the gap between the ranges is included
+     * in the result.
+     * @param range Rnage with which to perform the union
+     */
+    public IntRange union(IntRange range) {
+        return new IntRange(Math.min(this.start, range.start), Math.min(this.end, range.end));
+    }
+    
+    public String toString() {
+        StringBuffer sb = new StringBuffer();
+        sb.append("IntRange(");
+        sb.append(start);
+        sb.append(",");
+        sb.append(end);
+        sb.append(")");
+        return sb.toString();
+    }
+    //============================================================= PRIVATE
+    
+    private int start;
+    private int end;
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/ListenerList.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/ListenerList.java
new file mode 100644
index 0000000..18a4e67
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/ListenerList.java
@@ -0,0 +1,113 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.core;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.EventObject;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * A collection of listener objects. The main point of this class is the 
+ * fireEvent method, which takes care of the
+ * tedium of iterating over the collection and catching exceptions generated
+ * by listeners.
+ */
+public class ListenerList {
+
+    /**
+     * Class constructor.
+     * @param listenerClass Class of the listener interface.
+     * @param eventClass Class of the event objects passed to methods in the
+     * listener interface.
+     */
+    public ListenerList(Class listenerClass, Class eventClass) {
+        this.listenerClass = listenerClass;
+        this.methodParams = new Class[] { eventClass };
+    }
+    
+    /**
+     * Adds a listener to the list. Rejects listeners that are not subclasses
+     * of the listener class passed to the constructor.
+     * @param listener Listener to be added.
+     */
+    public void add(Object listener) {
+        if (!listenerClass.isInstance(listener)) {
+            this.handleException(new IllegalArgumentException("" + listener + " is not an instance of " + listenerClass));
+        }
+        this.listeners.add(listener);
+    }
+    
+    /**
+     * Calls the given method on each registered listener. Any exception
+     * thrown from one of the called methods is passed to handleException, as
+     * is any introspection error, e.g. if the given method doesn't exist.
+     * 
+     * @param methodName Listener method to call.
+     * @param event Event to be passed to each call.
+     */
+    public void fireEvent(String methodName, EventObject event) {
+        
+        Method method = (Method) this.methods.get(methodName);
+        if (method == null) {
+            try {
+                method = listenerClass.getMethod(methodName, methodParams);
+                this.methods.put(methodName, method);
+            } catch (Exception e) {
+                this.handleException(e);
+                return;
+            }
+        }
+        
+        Object[] args = new Object[] { event };
+        for (Iterator it = this.listeners.iterator(); it.hasNext();) {
+            Object listener = it.next();
+            try {
+                method.invoke(listener, args);
+            } catch (Exception ex) {
+                this.handleException(ex);
+            }
+        }
+        
+    }
+    
+    /**
+     * Called from fireEvent whenever a called listener method throws an 
+     * exception, or if there is a problem looking up the listener method
+     * by reflection. By default, simply prints the stack trace to stdout.
+     * Clients may override this method to provide a more suitable 
+     * implementation. 
+     * @param ex Exception thrown by the listener method.
+     */
+    public void handleException(Exception ex) {
+        ex.printStackTrace();
+    }
+    
+    /**
+     * Removes a listener from the list.
+     * @param listener Listener to remove.
+     */
+    public void remove(Object listener) {
+        this.listeners.remove(listener);
+    }
+    
+    //====================================================== PRIVATE
+    
+    private Class listenerClass;
+    private Class[] methodParams;
+    private Collection listeners = new ArrayList();
+    
+    // map methodName => Method object
+    private Map methods = new HashMap();
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/Point.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/Point.java
new file mode 100644
index 0000000..e3614e4
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/Point.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.core;
+
+/**
+ * Toolkit-independent point.
+ */
+public class Point {
+    
+    private int x;
+    private int y;
+
+    /**
+     * Class constructor.
+     * @param x X-coordinate.
+     * @param y Y-coordinate.
+     */
+    public Point(int x, int y) {
+        this.x = x;
+        this.y = y;
+    }
+    
+    public String toString() {
+        StringBuffer sb = new StringBuffer(80);
+        sb.append(Point.class.getName());
+        sb.append("[x=");
+        sb.append(this.getX());
+        sb.append(",y=");
+        sb.append(this.getY());
+        sb.append("]");
+        return sb.toString();
+    }
+
+    /**
+     * Returns the x-coordinate.
+     */
+    public int getX() {
+        return x;
+    }
+
+    /**
+     * Returns the y-coordinate.
+     */
+    public int getY() {
+        return y;
+    }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/Rectangle.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/Rectangle.java
new file mode 100644
index 0000000..3c26055
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/core/Rectangle.java
@@ -0,0 +1,91 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.core;
+
+/**
+ * Toolkit-independent rectangle.
+ */
+public class Rectangle {
+    
+    private int x;
+    private int y;
+    private int width;
+    private int height;
+    
+    public Rectangle(int x, int y, int width, int height) {
+        this.x = x;
+        this.y = y;
+        this.width = width;
+        this.height = height;
+    }
+    
+    public boolean intersects(Rectangle rect) {
+        return rect.x < this.x + this.width
+            && rect.x + rect.width > this.x
+            && rect.y < this.y + this.height
+            && rect.y + rect.height > this.y;
+    }
+
+    public String toString() {
+        StringBuffer sb = new StringBuffer(80);
+        sb.append(Rectangle.class.getName());
+        sb.append("[x=");
+        sb.append(this.getX());
+        sb.append(",y=");
+        sb.append(this.getY());
+        sb.append(",width=");
+        sb.append(this.getWidth());
+        sb.append(",height=");
+        sb.append(this.getHeight());
+        sb.append("]");
+        return sb.toString();
+    }
+
+    /**
+     * @return Returns the x.
+     */
+    public int getX() {
+        return x;
+    }
+
+    /**
+     * @return Returns the y.
+     */
+    public int getY() {
+        return y;
+    }
+
+    /**
+     * @return Returns the width.
+     */
+    public int getWidth() {
+        return width;
+    }
+
+    /**
+     * @return Returns the height.
+     */
+    public int getHeight() {
+        return height;
+    }
+    
+    /**
+     * Returns a Rectangle that is the union of this rectangle with another.
+     * @param rect Rectangle with which to union this one.
+     */
+    public Rectangle union(Rectangle rect) {
+        int left = Math.min(this.x, rect.x);
+        int top = Math.min(this.y, rect.y);
+        int right = Math.max(this.x + this.width, rect.x + rect.width);
+        int bottom = Math.max(this.y + this.height, rect.y + rect.height);
+        return new Rectangle(left, top, right - left, bottom - top);
+    }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/AbstractProperty.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/AbstractProperty.java
new file mode 100644
index 0000000..e217370
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/AbstractProperty.java
@@ -0,0 +1,139 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import org.w3c.css.sac.LexicalUnit;
+
+
+/**
+ * Abstract base class for property classes. Implements the <code>name</code>
+ * property but leaves the implementation of <code>calculate</code> to the
+ * subclass.
+ */
+public abstract class AbstractProperty implements IProperty {
+
+    /**
+     * Class constructor.
+     * @param name Name of the property.
+     */
+    public AbstractProperty(String name) {
+        this.name = name;
+    }
+
+    /**
+     * Returns true if the given lexical unit represents the token "inherit".
+     */
+    public static boolean isInherit(LexicalUnit lu) {
+        return lu != null
+        && lu.getLexicalUnitType() == LexicalUnit.SAC_INHERIT;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public static boolean isPercentage(LexicalUnit lu) {
+        return lu != null &&
+            lu.getLexicalUnitType() == LexicalUnit.SAC_PERCENTAGE;
+    }
+
+
+
+    public static boolean isLength(LexicalUnit lu) {
+        if (lu == null) {
+            return false;
+        }
+        
+        short type = lu.getLexicalUnitType();
+        
+        if (type == LexicalUnit.SAC_INTEGER &&
+                lu.getIntegerValue() == 0) {
+            return true;
+        }
+        
+        return type == LexicalUnit.SAC_CENTIMETER
+        || type == LexicalUnit.SAC_DIMENSION
+        || type == LexicalUnit.SAC_EM
+        || type == LexicalUnit.SAC_EX
+        || type == LexicalUnit.SAC_INCH
+        || type == LexicalUnit.SAC_MILLIMETER
+        || type == LexicalUnit.SAC_PICA
+        || type == LexicalUnit.SAC_PIXEL
+        || type == LexicalUnit.SAC_POINT;
+    }
+
+    public static int getIntLength(LexicalUnit lu, float fontSize, int ppi) {
+        return Math.round(getFloatLength(lu, fontSize, ppi));
+    }
+
+    public static float getFloatLength(LexicalUnit lu, float fontSize, int ppi) {
+    
+        float value = 0f;
+        
+        switch (lu.getLexicalUnitType()) {
+        
+        case LexicalUnit.SAC_CENTIMETER:
+            value = lu.getFloatValue() * ppi / 2.54f;
+        break;
+        case LexicalUnit.SAC_EM:
+            value = lu.getFloatValue() * fontSize;
+        break;
+        case LexicalUnit.SAC_EX:
+            value = lu.getFloatValue() * fontSize * EX_FACTOR;
+        break;
+        case LexicalUnit.SAC_INCH:
+            value = lu.getFloatValue() * ppi;
+        break;
+        case LexicalUnit.SAC_INTEGER:
+            value = 0; // 0 is the only valid length w/o a dimension
+        break;
+        case LexicalUnit.SAC_MILLIMETER:
+            value = lu.getFloatValue() * ppi / 25.4f;
+        break;
+        case LexicalUnit.SAC_PICA:
+            value = lu.getFloatValue() * ppi / 6;
+        break;
+        case LexicalUnit.SAC_PIXEL:
+            value = lu.getFloatValue();
+        break;
+        case LexicalUnit.SAC_POINT:
+            value = lu.getFloatValue() * ppi / 72;
+        break;
+        }
+        return value;
+    }
+
+
+    //============================================================== PRIVATE
+    
+    public static boolean isNumber(LexicalUnit lu) {
+        return lu != null &&
+            (lu.getLexicalUnitType() == LexicalUnit.SAC_INTEGER
+            || lu.getLexicalUnitType() == LexicalUnit.SAC_REAL);
+    }
+
+
+    public static float getNumber(LexicalUnit lu) {
+        if (lu.getLexicalUnitType() == LexicalUnit.SAC_INTEGER) {
+            return lu.getIntegerValue();
+        } else if (lu.getLexicalUnitType() == LexicalUnit.SAC_REAL) {
+            return lu.getFloatValue();
+        } else {
+            throw new RuntimeException("LexicalUnit type " + lu.getLexicalUnitType() + " is not a numeric type.");
+        }
+    }
+
+
+    private String name;
+
+    private static final float EX_FACTOR = 0.6f;
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/BorderSpacingProperty.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/BorderSpacingProperty.java
new file mode 100644
index 0000000..1c096ec
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/BorderSpacingProperty.java
@@ -0,0 +1,86 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+
+import org.eclipse.wst.xml.vex.core.internal.core.DisplayDevice;
+import org.w3c.css.sac.LexicalUnit;
+
+/**
+ * The CSS 'border-spacing' property.
+ */
+public class BorderSpacingProperty extends AbstractProperty {
+
+    /**
+     * Represents the computed value of border-spacing, which is a pair
+     * of values representing vertical and horizontal spacing.
+     */
+    public static class Value {
+
+        private int horizontal;
+        private int vertical;
+
+        public static final Value ZERO = new Value(0, 0);
+        
+        public Value(int horizontal, int vertical) {
+            this.horizontal = horizontal;
+            this.vertical = vertical;
+        }
+        
+        /**
+         * Returns the horizontal spacing, in pixels.
+         */
+        public int getHorizontal() {
+            return this.horizontal;
+        }
+        
+        /**
+         * Returns the vertical spacing, in pixels.
+         */
+        public int getVertical() {
+            return this.vertical;
+        }
+    }
+    
+    /**
+     * Class constructor.
+     */
+    public BorderSpacingProperty() {
+        super(CSS.BORDER_SPACING);
+    }
+
+    public Object calculate(LexicalUnit lu, Styles parentStyles, Styles styles) {
+
+        int horizontal = 0;
+        int vertical = 0;
+        
+        DisplayDevice device = DisplayDevice.getCurrent();
+
+        if (isLength(lu)) {
+            horizontal = getIntLength(lu, styles.getFontSize(), device.getHorizontalPPI());
+            lu = lu.getNextLexicalUnit();
+            if (isLength(lu)) {
+                vertical = getIntLength(lu, styles.getFontSize(), device.getVerticalPPI());
+            } else {
+                vertical = horizontal;
+            }
+            return new Value(horizontal, vertical);
+        } else {
+            // 'inherit' or an invalid value
+            if (parentStyles == null) {
+                return Value.ZERO;
+            } else {
+                return parentStyles.getBorderSpacing();
+            }
+        }
+    }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/BorderStyleProperty.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/BorderStyleProperty.java
new file mode 100644
index 0000000..44b9f22
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/BorderStyleProperty.java
@@ -0,0 +1,63 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import org.w3c.css.sac.LexicalUnit;
+
+/**
+ * The border-XXX-style CSS property.
+ */
+public class BorderStyleProperty extends AbstractProperty {
+
+    /**
+     * Class constructor.
+     * @param name Name of the property.
+     */
+    public BorderStyleProperty(String name) {
+        super(name);
+    }
+
+    /**
+     * Returns true if the given lexical unit represents a border style.
+     *
+     * @param lu LexicalUnit to check.
+     */
+    public static boolean isBorderStyle(LexicalUnit lu) {
+        if (lu == null) {
+            return false;
+        } else if (lu.getLexicalUnitType() == LexicalUnit.SAC_IDENT) {
+            String s = lu.getStringValue();
+            return s.equals(CSS.NONE)
+            || s.equals(CSS.HIDDEN)
+            || s.equals(CSS.DOTTED)
+            || s.equals(CSS.DASHED)
+            || s.equals(CSS.SOLID)
+            || s.equals(CSS.DOUBLE)
+            || s.equals(CSS.GROOVE)
+            || s.equals(CSS.RIDGE)
+            || s.equals(CSS.INSET)
+            || s.equals(CSS.OUTSET);
+        }
+        
+        return false;
+    }
+
+    public Object calculate(LexicalUnit lu, Styles parentStyles, Styles styles) {
+        if (isBorderStyle(lu)) {
+            return lu.getStringValue();
+        } else if (isInherit(lu) && parentStyles != null) {
+            return parentStyles.get(this.getName());
+        } else {
+            return CSS.NONE;
+        }
+    }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/BorderWidthProperty.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/BorderWidthProperty.java
new file mode 100644
index 0000000..bb256dd
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/BorderWidthProperty.java
@@ -0,0 +1,118 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+
+import org.eclipse.wst.xml.vex.core.internal.core.DisplayDevice;
+import org.w3c.css.sac.LexicalUnit;
+
+/**
+ * The border-XXX-width CSS property. Since the value of this property
+ * depends on the corresponding border-XXX-style property, the style
+ * property must be calculated first and placed in the styles, and its
+ * name given to the constructor of this class.
+ */
+public class BorderWidthProperty extends AbstractProperty {
+
+
+    /**
+     * Class constructor.
+     * @param name Name of the property.
+     * @param borderStyleName Name of the corresponding border style
+     * property. For example, if name is CSS.BORDER_TOP_WIDTH, then
+     * borderStyleName should be CSS.BORDER_TOP_STYLE.
+     * @param axis AXIS_HORIZONTAL (for left and right borders) or 
+     * AXIS_VERTICAL (for top and bottom borders). 
+     */
+    public BorderWidthProperty(String name, String borderStyleName, byte axis) {
+        super(name);
+        this.borderStyleName = borderStyleName;
+        this.axis = axis;
+    }
+
+    /**
+     * Returns true if the given lexical unit represents a border width.
+     *
+     * @param lu LexicalUnit to check.
+     */
+    public static boolean isBorderWidth(LexicalUnit lu) {
+        if (lu == null) {
+            return false;
+        } else if (isLength(lu)) {
+            return true;
+        } else if (lu.getLexicalUnitType() == LexicalUnit.SAC_IDENT) {
+            String s = lu.getStringValue();
+            return s.equals(CSS.THIN)
+            || s.equals(CSS.MEDIUM)
+            || s.equals(CSS.THICK);
+        } else {
+            return false;
+        }
+    }
+    
+    public Object calculate(LexicalUnit lu, Styles parentStyles, Styles styles) {
+        return new Integer(this.calculateInternal(lu, parentStyles, styles));
+    }
+
+    private int calculateInternal(LexicalUnit lu, Styles parentStyles, Styles styles) {
+        
+        DisplayDevice device = DisplayDevice.getCurrent();
+        int ppi = this.axis == AXIS_HORIZONTAL ? device.getHorizontalPPI() : device.getVerticalPPI();
+        
+        String borderStyle = (String) styles.get(this.borderStyleName);
+        
+        if (borderStyle.equals(CSS.NONE) || borderStyle.equals(CSS.HIDDEN)) {
+            return 0;
+        } else if (isBorderWidth(lu)) {
+            return getBorderWidth(lu, styles.getFontSize(), ppi);
+        } else if (isInherit(lu) && parentStyles != null) {
+            return ((Integer) parentStyles.get(this.getName())).intValue();
+        } else {
+            // not specified, "none", or other unknown value
+            return BORDER_WIDTH_MEDIUM;
+        }
+    }
+
+    //=================================================== PRIVATE
+
+    // Name of the corresponding border style property
+    private String borderStyleName;
+
+    // Axis along which the border width is measured.
+    private byte axis;
+
+    // named border widths
+    private static final int BORDER_WIDTH_THIN = 1;
+    private static final int BORDER_WIDTH_MEDIUM = 3;
+    private static final int BORDER_WIDTH_THICK = 5;
+
+
+    private static int getBorderWidth(LexicalUnit lu, float fontSize, int ppi) {
+        if (isLength(lu)) {
+            return getIntLength(lu, fontSize, ppi);
+        } else if (lu.getLexicalUnitType() == LexicalUnit.SAC_IDENT) {
+            String s = lu.getStringValue();
+            if (s.equals(CSS.THIN)) {
+                return BORDER_WIDTH_THIN;
+            } else if (s.equals(CSS.MEDIUM)) {
+                return BORDER_WIDTH_MEDIUM;
+            } else if (s.equals(CSS.THICK)) {
+                return BORDER_WIDTH_THICK;
+            } else {
+                return 0;
+            }
+        } else {
+            return 0;
+        }
+    }
+    
+    
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/CSS.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/CSS.java
new file mode 100644
index 0000000..1adcf06
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/CSS.java
@@ -0,0 +1,233 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *     Dave Holroyd - Implement text decoration
+ *     John Austin - More complete CSS constants.  Add the colour "orange".
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+/**
+ * CSS constants.
+ */
+public interface CSS {
+
+    // property names
+    public static final String AZIMUTH = "azimuth";
+    public static final String BACKGROUND = "background";
+    public static final String BACKGROUND_ATTACHMENT = "background-attachment";
+    public static final String BACKGROUND_COLOR = "background-color";
+    public static final String BACKGROUND_IMAGE = "background-image";
+    public static final String BACKGROUND_POSITION = "background-position";
+    public static final String BACKGROUND_REPEAT = "background-repeat";   
+    public static final String BORDER = "border";
+    public static final String BORDER_BOTTOM = "border-bottom";
+    public static final String BORDER_BOTTOM_COLOR = "border-bottom-color";
+    public static final String BORDER_BOTTOM_STYLE = "border-bottom-style";
+    public static final String BORDER_BOTTOM_WIDTH = "border-bottom-width";
+    public static final String BORDER_COLOR = "border-color";
+    public static final String BORDER_LEFT = "border-left";
+    public static final String BORDER_LEFT_COLOR = "border-left-color";
+    public static final String BORDER_LEFT_STYLE = "border-left-style";
+    public static final String BORDER_LEFT_WIDTH = "border-left-width";
+    public static final String BORDER_RIGHT = "border-right";
+    public static final String BORDER_RIGHT_COLOR = "border-right-color";
+    public static final String BORDER_RIGHT_STYLE = "border-right-style";
+    public static final String BORDER_RIGHT_WIDTH = "border-right-width";
+    public static final String BORDER_SPACING = "border-spacing";
+    public static final String BORDER_STYLE = "border-style";
+    public static final String BORDER_TOP = "border-top";
+    public static final String BORDER_TOP_COLOR = "border-top-color";
+    public static final String BORDER_TOP_STYLE = "border-top-style";
+    public static final String BORDER_TOP_WIDTH = "border-top-width";
+    public static final String BORDER_WIDTH = "border-width";
+    public static final String BOTTOM = "bottom";   
+    public static final String CAPTION_SIDE = "caption-side";
+    public static final String CLEAR = "clear";
+    public static final String CLIP= "clip";
+    public static final String COLOR = "color";
+    public static final String CONTENT = "content";
+    public static final String COUNTER_INCREMENT = "counter-increment";
+    public static final String COUNTER_RESET= "counter-reset";
+    public static final String CUE = "cue";
+    public static final String CUE_AFTER = "cue-after";
+    public static final String CUE_BEFORE = "cue-before";
+    public static final String CURSOR = "cursor";
+    public static final String DIRECTION = "direction";
+    public static final String DISPLAY = "display";
+    public static final String ELEVATION = "elevation";
+    public static final String EMPTY_CELLS = "empty-cells";
+    public static final String FLOAT = "float";
+    public static final String FONT = "font";
+    public static final String FONT_FAMILY = "font-family";
+    public static final String FONT_SIZE = "font-size";
+    public static final String FONT_STYLE = "font-style";
+    public static final String FONT_VARIANT = "font-variant";
+    public static final String FONT_WEIGHT = "font-weight";
+    public static final String HEIGHT = "height";
+    public static final String HIDDEN = "hidden";
+    public static final String LETTER_SPACING = "letter-spacing";
+    public static final String LINE_HEIGHT = "line-height";
+    public static final String LIST_STYLE = "list-style";
+    public static final String LIST_STYLE_IMAGE = "list-style-image";
+    public static final String LIST_STYLE_POSITION = "list-style-position";
+    public static final String LIST_STYLE_TYPE = "list-style-type";
+    public static final String MARGIN = "margin";
+    public static final String MARGIN_BOTTOM = "margin-bottom";
+    public static final String MARGIN_LEFT = "margin-left";
+    public static final String MARGIN_RIGHT = "margin-right";
+    public static final String MARGIN_TOP = "margin-top";
+    public static final String MAX_HEIGHT = "max-height";
+    public static final String MAX_WIDTH = "max-width";
+    public static final String MIN_HEIGHT = "min-height";
+    public static final String MIN_WIDTH = "min-width";
+    public static final String ORPHANS = "orphans";
+    public static final String OUTLINE = "outline";
+    public static final String OVERFLOW = "overflow";
+    public static final String OUTLINE_COLOR = "outline-color";
+    public static final String OUTLINE_STYLE = "outline-style";
+    public static final String OUTLINE_WIDTH = "outline-width";
+    public static final String PADDING = "padding";
+    public static final String PADDING_BOTTOM = "padding-bottom";
+    public static final String PADDING_LEFT = "padding-left";
+    public static final String PADDING_RIGHT = "padding-right";
+    public static final String PADDING_TOP = "padding-top";
+    public static final String PAUSE = "pause";
+    public static final String PAUSE_AFTER = "pause-after";
+    public static final String PAUSE_BEFORE = "pause-before";
+    public static final String PITCH = "pitch";
+    public static final String PITCH_RANGE = "pitch-range";
+    public static final String PLAY_DURING = "play-during";
+    public static final String POSITION = "position";
+    public static final String QUOTES = "quotes";
+    public static final String RICHNESS = "richness";
+    public static final String SPEAK = "speak";
+    public static final String SPEAK_HEADER = "speak-header";
+    public static final String SPEAK_NUMERAL = "speak-numeral";
+    public static final String SPEAK_PUNCTUATION= "speak-punctuation";
+    public static final String SPEECH_RATE = "speech-rate";
+    public static final String STRESS = "stress";
+    public static final String TABLE_LAYOUT = "table-layout";
+    public static final String TEXT_ALIGN = "text-align";
+    public static final String TEXT_DECORATION = "text-decoration";
+    public static final String TEXT_INDENT = "text-indent";
+    public static final String TEXT_TRANSFORM = "text-transform";
+    public static final String UNICODE_BIDI = "unicode-bidi";
+    public static final String VERTICAL_ALIGN= "vertical-align";
+    public static final String VISIBILITY = "visibility";
+    public static final String VOICE_FAMILY = "voice-family";
+    public static final String VOLUME = "volume";
+    public static final String WHITE_SPACE = "white-space";
+    public static final String WIDOWS = "widows";
+    public static final String WIDTH = "width";
+    public static final String WORD_SPACING = "word-spacing";
+    public static final String Z_SPACING = "z-spacing";
+
+    // suffixes to BORDER_XXX
+    public static final String COLOR_SUFFIX = "-color";
+    public static final String STYLE_SUFFIX = "-style";
+    public static final String WIDTH_SUFFIX = "-width";
+
+    // color values
+    public static final String AQUA = "aqua";
+    public static final String BLACK = "black";
+    public static final String BLUE = "blue";
+    public static final String FUCHSIA = "fuchsia";
+    public static final String GRAY = "gray";
+    public static final String GREEN = "green";
+    public static final String LIME = "lime";
+    public static final String MAROON = "maroon";
+    public static final String NAVY = "navy";
+    public static final String OLIVE = "olive";
+    public static final String ORANGE = "orange";
+    public static final String PURPLE = "purple";
+    public static final String RED = "red";
+    public static final String SILVER = "silver";
+    public static final String TEAL = "teal";
+    public static final String WHITE = "white";
+    public static final String YELLOW = "yellow";
+
+    // list-style values
+    public static final String ARMENIAN = "armenian";
+    public static final String CIRCLE = "circle";
+    public static final String CJK_IDEOGRAPHIC = "cjk-ideographic";
+    public static final String DECIMAL = "decimal";
+    public static final String DECIMAL_LEADING_ZERO = "decimal-leading-zero";
+    public static final String DISC = "disc";
+    public static final String GEORGIAN = "georgian";
+    public static final String HEBREW = "hebrew";
+    public static final String HIRAGANA = "hiragana";
+    public static final String HIRAGANA_IROHA = "hiragana-iroha";
+    public static final String KATAKANA = "katakana";
+    public static final String KATAKANA_IROHA = "katakana-iroha";
+    public static final String LOWER_ALPHA = "lower-alpha";
+    public static final String LOWER_GREEK = "lower-greek";
+    public static final String LOWER_LATIN = "lower-latin";
+    public static final String LOWER_ROMAN = "lower-roman";
+    public static final String SQUARE = "square";
+    public static final String UPPER_ALPHA = "upper-alpha";
+    public static final String UPPER_LATIN = "upper-latin";
+    public static final String UPPER_ROMAN = "upper-roman";
+    
+    
+    // other values
+    public static final String BLINK = "blink";
+    public static final String BLOCK = "block";
+    public static final String BOLD = "bold";
+    public static final String BOLDER = "bolder";
+    public static final String CENTER = "center";
+    public static final String DASHED = "dashed";
+    public static final String DOTTED = "dotted";
+    public static final String DOUBLE = "double";
+    public static final String GROOVE = "groove";
+    public static final String INLINE = "inline";
+    public static final String INLINE_BLOCK = "inline-block";
+    public static final String INLINE_TABLE = "inline-table";
+    public static final String INSET = "inset";
+    public static final String ITALIC = "italic";
+    public static final String JUSTIFY = "justify";
+    public static final String LARGE = "large";
+    public static final String LARGER = "larger";
+    public static final String LEFT = "left";
+    public static final String LIGHTER = "lighter";
+    public static final String LINE_THROUGH = "line-through";
+    public static final String LIST_ITEM = "list-item";
+    public static final String MEDIUM = "medium";
+    public static final String NONE = "none";
+    public static final String NORMAL = "normal";
+    public static final String NOWRAP = "nowrap";
+    public static final String OBLIQUE = "oblique";
+    public static final String OUTSET = "outset";
+    public static final String OVERLINE = "overline";
+    public static final String PRE = "pre";
+    public static final String RIDGE = "ridge";
+    public static final String RIGHT = "right";
+    public static final String RUN_IN = "run-in";
+    public static final String SOLID = "solid";
+    public static final String SMALL = "small";
+    public static final String SMALL_CAPS = "small-caps";
+    public static final String SMALLER = "smaller";
+    public static final String TABLE = "table";
+    public static final String TABLE_CAPTION = "table-caption";
+    public static final String TABLE_CELL = "table-cell";
+    public static final String TABLE_COLUMN = "table-column";
+    public static final String TABLE_COLUMN_GROUP = "table-column-group";
+    public static final String TABLE_FOOTER_GROUP = "table-footer-group";
+    public static final String TABLE_HEADER_GROUP = "table-header-group";
+    public static final String TABLE_ROW = "table-row";
+    public static final String TABLE_ROW_GROUP = "table-row-group";
+    public static final String THICK = "thick";
+    public static final String THIN = "thin";
+    public static final String UNDERLINE = "underline";
+    public static final String X_LARGE = "x-large";
+    public static final String X_SMALL = "x-small";
+    public static final String XX_LARGE = "xx-large";
+    public static final String XX_SMALL = "xx-small";
+    
+}
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/ColorProperty.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/ColorProperty.java
new file mode 100644
index 0000000..1dfd0b8
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/ColorProperty.java
@@ -0,0 +1,152 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import java.util.HashMap;
+import java.util.Map;
+
+
+import org.eclipse.wst.xml.vex.core.internal.core.Color;
+import org.w3c.css.sac.LexicalUnit;
+
+/**
+ * Color-valued properties.
+ */
+public class ColorProperty extends AbstractProperty {
+
+    private static Map colorNames = new HashMap();
+
+    static {
+        colorNames.put(CSS.AQUA, new Color(0, 255, 255));
+        colorNames.put(CSS.BLACK, new Color(0, 0, 0));
+        colorNames.put(CSS.BLUE, new Color(0, 0, 255));
+        colorNames.put(CSS.FUCHSIA, new Color(255, 0, 255));
+        colorNames.put(CSS.GRAY, new Color(128, 128, 128));
+        colorNames.put(CSS.GREEN, new Color(0, 128, 0));
+        colorNames.put(CSS.LIME, new Color(0, 255, 0));
+        colorNames.put(CSS.MAROON, new Color(128, 0, 0));
+        colorNames.put(CSS.NAVY, new Color(0, 0, 128));
+        colorNames.put(CSS.OLIVE, new Color(128, 128, 0));
+        colorNames.put(CSS.ORANGE, new Color( 255, 165, 0));
+        colorNames.put(CSS.PURPLE, new Color(128, 0, 128));
+        colorNames.put(CSS.RED, new Color(255, 0, 0));
+        colorNames.put(CSS.SILVER, new Color(192, 192, 192));
+        colorNames.put(CSS.TEAL, new Color(0, 128, 128));
+        colorNames.put(CSS.WHITE, new Color(255, 255, 255));
+        colorNames.put(CSS.YELLOW, new Color(255, 255, 0));
+    }
+
+    /**
+     * Class constructor. The names CSS.COLOR and
+     * CSS.BACKGROUND_COLOR are treated specially, as follows.
+     * 
+     * <ul>
+     * <li>If name is CSS.COLOR, it is inherited and defaults to black.</li>
+     * <li>If name is CSS.BACKGROUND_COLOR, it is not inherited and defaults
+     * to transparent (null).</li>
+     * <li>Otherwise, it is not inherited and defaults to the current color.</li>
+     * </ul>
+     * 
+     * <p>Because of the default in the third case, the ColorProperty
+     * for CSS.COLOR must be processed before any others.</p>
+     * @param name Name of the element.
+     */
+    public ColorProperty(String name) {
+        super(name);
+    }
+
+    public Object calculate(LexicalUnit lu, Styles parentStyles, Styles styles) {
+        
+        boolean inherit = isInherit(lu) || this.getName().equals(CSS.COLOR);
+        
+        if (isColor(lu)) {
+            return getColor(lu);
+        } else if (inherit && parentStyles != null) {
+            return parentStyles.get(this.getName());
+        } else {
+            if (this.getName().equals(CSS.COLOR)) {
+                return Color.BLACK; 
+            } else if (this.getName().equals(CSS.BACKGROUND_COLOR)) {
+                return null; // transparent
+            } else {
+                return styles.getColor();
+            }
+        }
+    }
+
+    /**
+     * Returns true if the given lexical unit represents a color.
+     *
+     * @param lu LexicalUnit to check.
+     */
+    public static boolean isColor(LexicalUnit lu) {
+        if (lu == null) {
+            return false;
+        } else if (lu.getLexicalUnitType() == LexicalUnit.SAC_IDENT &&
+                colorNames.containsKey(lu.getStringValue())) {
+            return true;
+        } else if (lu.getLexicalUnitType() == LexicalUnit.SAC_RGBCOLOR) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    //========================================================== PRIVATE
+
+    private static Color getColor(LexicalUnit lu) {
+        if (lu.getLexicalUnitType() == LexicalUnit.SAC_IDENT) {
+            String s = lu.getStringValue();
+            if (colorNames.containsKey(s)) {
+                return (Color) colorNames.get(s);
+            } else {
+                return null;
+            }
+        } else if (lu.getLexicalUnitType() == LexicalUnit.SAC_RGBCOLOR) {
+            lu = lu.getParameters();
+            int red = getColorPart(lu);
+            lu = lu.getNextLexicalUnit(); // gobble comma
+            lu = lu.getNextLexicalUnit();
+            int green = getColorPart(lu);
+            lu = lu.getNextLexicalUnit(); // gobble comma
+            lu = lu.getNextLexicalUnit();
+            int blue = getColorPart(lu);
+            
+            if (red == -1 || green == -1 || blue == -1) {
+                return null;
+            } else {
+                return new Color(red, green, blue);
+            }
+        } else {
+            System.out.println("WARNING: unsupported color type: " + lu);
+            return null;
+        }
+    }
+    
+    /**
+     * Converts one of the color channels into an int from 0-255, or -1
+     * if there's an error.
+     */
+    private static int getColorPart(LexicalUnit lu) {
+        int value;
+        if (lu.getLexicalUnitType() == LexicalUnit.SAC_INTEGER) {
+            value = lu.getIntegerValue();
+        } else if (lu.getLexicalUnitType() == LexicalUnit.SAC_PERCENTAGE) {
+            value = Math.round(lu.getFloatValue() * 255);
+        } else {
+            System.out.println("WARNING: unsupported color part: " + lu);
+            return -1;
+        }
+        return Math.max(0, Math.min(255, value));
+    }
+    
+    
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/DisplayProperty.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/DisplayProperty.java
new file mode 100644
index 0000000..919a17c
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/DisplayProperty.java
@@ -0,0 +1,73 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import org.w3c.css.sac.LexicalUnit;
+
+/**
+ * The CSS 'display' property.
+ */
+public class DisplayProperty extends AbstractProperty {
+
+    /**
+     * Class constructor.
+     */
+    public DisplayProperty() {
+        super(CSS.DISPLAY);
+    }
+
+    public Object calculate(LexicalUnit lu, Styles parentStyles,
+            Styles styles) {
+        
+        if (isDisplay(lu)) {
+            return lu.getStringValue();
+        } else if (isInherit(lu) && parentStyles != null) {
+            return parentStyles.getDisplay();
+        } else {
+            // not specified or other unknown value
+            return CSS.INLINE;
+        }
+    }
+
+    //======================================================== PRIVATE
+    
+    /**
+     * Returns true if the value of the given LexicalUnit represents 
+     * a valid value for this property.
+     * @param lu LexicalUnit to inspect.
+     */
+    private static boolean isDisplay(LexicalUnit lu) {
+        if (lu == null) {
+            return false; 
+        } else if (lu.getLexicalUnitType() == LexicalUnit.SAC_IDENT) {
+            String s = lu.getStringValue();
+            return s.equals(CSS.BLOCK)
+            || s.equals(CSS.INLINE)
+            || s.equals(CSS.INLINE_BLOCK)
+            || s.equals(CSS.INLINE_TABLE)
+            || s.equals(CSS.LIST_ITEM)
+            || s.equals(CSS.NONE)
+            || s.equals(CSS.RUN_IN)
+            || s.equals(CSS.TABLE)
+            || s.equals(CSS.TABLE_CAPTION)
+            || s.equals(CSS.TABLE_CELL)
+            || s.equals(CSS.TABLE_COLUMN)
+            || s.equals(CSS.TABLE_COLUMN_GROUP)
+            || s.equals(CSS.TABLE_FOOTER_GROUP)
+            || s.equals(CSS.TABLE_HEADER_GROUP)
+            || s.equals(CSS.TABLE_ROW)
+            || s.equals(CSS.TABLE_ROW_GROUP);
+        } else {
+            return false;
+        }
+    }
+    
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/FontFamilyProperty.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/FontFamilyProperty.java
new file mode 100644
index 0000000..ca857b3
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/FontFamilyProperty.java
@@ -0,0 +1,71 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.w3c.css.sac.LexicalUnit;
+
+/**
+ * The CSS 'font-family' property.
+ */
+public class FontFamilyProperty extends AbstractProperty {
+
+    /**
+     * Class constructor.
+     */
+    public FontFamilyProperty() {
+        super(CSS.FONT_FAMILY);
+    }
+
+    /**
+     *
+     */
+
+    public Object calculate(LexicalUnit lu, Styles parentStyles,
+            Styles styles) {
+        if (isFontFamily(lu)) {
+            return getFontFamilies(lu);
+        } else {
+            // not specified, "inherit", or some other value
+            if (parentStyles != null) {
+                return parentStyles.getFontFamilies();
+            } else {
+                return DEFAULT_FONT_FAMILY;
+            }
+        }
+    }
+
+    //================================================= PRIVATE
+    
+    private static final String[] DEFAULT_FONT_FAMILY = new String[] { "sans-serif" };
+
+    private static boolean isFontFamily(LexicalUnit lu) {
+        return lu != null 
+        && (lu.getLexicalUnitType() == LexicalUnit.SAC_STRING_VALUE
+                || lu.getLexicalUnitType() == LexicalUnit.SAC_IDENT);
+    }
+
+    private static String[] getFontFamilies(LexicalUnit lu) {
+        List list = new ArrayList();
+        while (lu != null) {
+            if (lu.getLexicalUnitType() == LexicalUnit.SAC_STRING_VALUE
+                    || lu.getLexicalUnitType() == LexicalUnit.SAC_IDENT) {
+                
+                list.add(lu.getStringValue());
+            }
+            lu = lu.getNextLexicalUnit();
+        }
+        return (String[]) list.toArray(new String[list.size()]);
+    }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/FontSizeProperty.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/FontSizeProperty.java
new file mode 100644
index 0000000..8f30991
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/FontSizeProperty.java
@@ -0,0 +1,147 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+
+import org.eclipse.wst.xml.vex.core.internal.core.DisplayDevice;
+import org.w3c.css.sac.LexicalUnit;
+
+/**
+ * The CSS font-size property. Note that other lengths depend on the
+ * computed value of this property, so this should be evaluated early
+ * on in the stylesheet, before any other lengths.
+ */
+public class FontSizeProperty extends AbstractProperty {
+
+    /**
+     * Class constructor,
+     */
+    public FontSizeProperty() {
+        super(CSS.FONT_SIZE);
+    }
+
+    public Object calculate(LexicalUnit lu, Styles parentStyles, Styles styles) {
+        return new Float(this.calculateInternal(lu, parentStyles, styles));
+    }
+
+    /**
+     * Returns true if the given lexical unit represents a font size.
+     *
+     * @param lu LexicalUnit to check.
+     */
+    public static boolean isFontSize(LexicalUnit lu) {
+        if (lu == null) {
+            return false;
+        } else if (isLength(lu)) {
+            return true;
+        } else if (isPercentage(lu)) {
+            return true;
+        } else if (lu.getLexicalUnitType() == LexicalUnit.SAC_IDENT) {
+            String s = lu.getStringValue();
+            return s.equals(CSS.XX_SMALL)
+            || s.equals(CSS.X_SMALL)
+            || s.equals(CSS.SMALL)
+            || s.equals(CSS.MEDIUM)
+            || s.equals(CSS.LARGE)
+            || s.equals(CSS.X_LARGE)
+            || s.equals(CSS.XX_LARGE)
+            || s.equals(CSS.SMALLER)
+            || s.equals(CSS.LARGER);
+        } else {
+            return false;
+        }
+    }
+    
+    //======================================================== PRIVATE
+    
+    private float calculateInternal(LexicalUnit lu, Styles parentStyles, Styles styles) {
+
+        DisplayDevice device = DisplayDevice.getCurrent();
+        float baseFontSize = DEFAULT_FONT_SIZE_POINTS * device.getVerticalPPI() / 72;
+        
+        if (parentStyles != null) {
+            baseFontSize = parentStyles.getFontSize();
+        }
+        
+        if (lu == null) {
+            return baseFontSize;
+        } else if (isLength(lu)) {
+            return getFloatLength(lu, baseFontSize, device.getVerticalPPI());
+        } else if (isPercentage(lu)) {
+            return baseFontSize * lu.getFloatValue() / 100;
+        } else if (lu.getLexicalUnitType() == LexicalUnit.SAC_IDENT) {
+            String s = lu.getStringValue();
+            
+            if (s.equals(CSS.XX_SMALL)) {
+                return baseFontSize * FONT_FACTOR_XX_SMALL;
+            
+            } else if (s.equals(CSS.X_SMALL)) {
+                return baseFontSize * FONT_FACTOR_X_SMALL;
+            
+            } else if (s.equals(CSS.SMALL)) {
+                return baseFontSize * FONT_FACTOR_SMALL;
+            
+            } else if (s.equals(CSS.MEDIUM)) {
+                return baseFontSize * FONT_FACTOR_MEDIUM;
+            
+            } else if (s.equals(CSS.LARGE)) {
+                return baseFontSize * FONT_FACTOR_LARGE;
+            
+            } else if (s.equals(CSS.X_LARGE)) {
+                return baseFontSize * FONT_FACTOR_X_LARGE;
+            
+            } else if (s.equals(CSS.XX_LARGE)) {
+                return baseFontSize * FONT_FACTOR_XX_LARGE;
+            
+            } else if (s.equals(CSS.SMALLER)) {
+                return baseFontSize / FONT_SIZE_FACTOR;
+            
+            } else if (s.equals(CSS.LARGER)) {
+                return baseFontSize * FONT_SIZE_FACTOR;
+            
+            } else {
+                return baseFontSize;
+            }
+        } else {
+            return baseFontSize;
+        }
+        
+        
+    }
+
+    private static final float DEFAULT_FONT_SIZE_POINTS = 12;
+
+    // relative size of adjacent font size names
+    private static final float FONT_SIZE_FACTOR = 1.2f;
+
+    // Sizes of named font sizes, relative to "medium"
+    private static final float FONT_FACTOR_MEDIUM = 1.0f;
+    
+    private static final float FONT_FACTOR_SMALL = 
+        FONT_FACTOR_MEDIUM / FONT_SIZE_FACTOR;
+    
+    private static final float FONT_FACTOR_X_SMALL = 
+        FONT_FACTOR_SMALL / FONT_SIZE_FACTOR;
+    
+    private static final float FONT_FACTOR_XX_SMALL = 
+        FONT_FACTOR_X_SMALL / FONT_SIZE_FACTOR;
+    
+    private static final float FONT_FACTOR_LARGE = 
+        FONT_FACTOR_MEDIUM * FONT_SIZE_FACTOR;
+    
+    private static final float FONT_FACTOR_X_LARGE = 
+        FONT_FACTOR_LARGE * FONT_SIZE_FACTOR;
+    
+    private static final float FONT_FACTOR_XX_LARGE = 
+        FONT_FACTOR_X_LARGE * FONT_SIZE_FACTOR;
+
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/FontStyleProperty.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/FontStyleProperty.java
new file mode 100644
index 0000000..ebf432d
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/FontStyleProperty.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import org.w3c.css.sac.LexicalUnit;
+
+/**
+ * The CSS font-style property.
+ */
+public class FontStyleProperty extends AbstractProperty {
+
+    /**
+     * Class constructor.
+     */
+    public FontStyleProperty() {
+        super(CSS.FONT_STYLE);
+    }
+
+    public Object calculate(LexicalUnit lu, Styles parentStyles, Styles styles) {
+        if (isFontStyle(lu)) {
+            return lu.getStringValue();
+        } else {
+            // not specified, "inherit", or some other value
+            if (parentStyles != null) {
+                return parentStyles.getFontStyle();
+            } else {
+                return CSS.NORMAL;
+            }
+        }
+
+    }
+
+    /**
+     * Returns true if the given lexical unit represents a font style.
+     *
+     * @param lu LexicalUnit to check.
+     */
+    public static boolean isFontStyle(LexicalUnit lu) {
+        if (lu == null) {
+            return false;
+        } else if (lu.getLexicalUnitType() == LexicalUnit.SAC_IDENT) {
+            String s = lu.getStringValue();
+            return s.equals(CSS.NORMAL)
+            || s.equals(CSS.ITALIC)
+            || s.equals(CSS.OBLIQUE);
+        } else {
+            return false;
+        }
+    }
+    
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/FontVariantProperty.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/FontVariantProperty.java
new file mode 100644
index 0000000..e6b57ca
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/FontVariantProperty.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import org.w3c.css.sac.LexicalUnit;
+
+/**
+ * The CSS font-variant property.
+ */
+public class FontVariantProperty extends AbstractProperty {
+
+    /**
+     * Class constructor.
+     */
+    public FontVariantProperty() {
+        super(CSS.FONT_VARIANT);
+    }
+
+    public Object calculate(LexicalUnit lu, Styles parentStyles, Styles styles) {
+        if (isFontVariant(lu)) {
+            return lu.getStringValue();
+        } else {
+            // not specified, "inherit", or some other value
+            if (parentStyles != null) {
+                return parentStyles.getFontStyle();
+            } else {
+                return CSS.NORMAL;
+            }
+        }
+
+    }
+
+    /**
+     * Returns true if the given lexical unit represents a font variant.
+     *
+     * @param lu LexicalUnit to check.
+     */
+    public static boolean isFontVariant(LexicalUnit lu) {
+        if (lu == null) {
+            return false;
+        } else if (lu.getLexicalUnitType() == LexicalUnit.SAC_IDENT) {
+            String s = lu.getStringValue();
+            return s.equals(CSS.NORMAL)
+            || s.equals(CSS.SMALL_CAPS);
+        } else {
+            return false;
+        }
+    }
+    
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/FontWeightProperty.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/FontWeightProperty.java
new file mode 100644
index 0000000..9336790
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/FontWeightProperty.java
@@ -0,0 +1,100 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import org.w3c.css.sac.LexicalUnit;
+
+/**
+ * The CSS font-weight property.
+ */
+public class FontWeightProperty extends AbstractProperty {
+
+    private static final int FONT_WEIGHT_NORMAL = 400;
+    private static final int FONT_WEIGHT_BOLD = 700;
+
+    /**
+     * Class constructor.
+     */
+    public FontWeightProperty() {
+        super(CSS.FONT_WEIGHT);
+    }
+
+    public Object calculate(LexicalUnit lu, Styles parentStyles, Styles styles) {
+        return new Integer(this.calculateInternal(lu, parentStyles, styles));
+    }
+
+    public int calculateInternal(LexicalUnit lu, Styles parentStyles, Styles styles) {
+        if (isFontWeight(lu)) {
+            return getFontWeight(lu, parentStyles);
+        } else {
+            // not specified, "inherit", or some other value
+            if (parentStyles != null) {
+                return parentStyles.getFontWeight();
+            } else {
+                return FONT_WEIGHT_NORMAL;
+            }
+        }
+        
+    }
+
+    /**
+     * Returns true if the given lexical unit represents a font weight.
+     *
+     * @param lu LexicalUnit to check.
+     */
+    public static boolean isFontWeight(LexicalUnit lu) {
+    if (lu == null) {
+        return false;
+    } else if (lu.getLexicalUnitType() == LexicalUnit.SAC_INTEGER) {
+        return true;
+    } else if (lu.getLexicalUnitType() == LexicalUnit.SAC_IDENT) {
+        String s = lu.getStringValue();
+        return s.equals(CSS.NORMAL)
+    	|| s.equals(CSS.BOLD)
+    	|| s.equals(CSS.BOLDER)
+    	|| s.equals(CSS.LIGHTER);
+    } else {
+        return false;
+    }
+    }
+
+    private static int getFontWeight(LexicalUnit lu, Styles parentStyles) {
+        if (lu == null) {
+            return FONT_WEIGHT_NORMAL;
+        } else if (lu.getLexicalUnitType() == LexicalUnit.SAC_INTEGER) {
+            return lu.getIntegerValue();
+        } else if (lu.getLexicalUnitType() == LexicalUnit.SAC_IDENT) {
+            String s = lu.getStringValue();
+            if (s.equals(CSS.NORMAL)) {
+                return FONT_WEIGHT_NORMAL;
+            } else if (s.equals(CSS.BOLD)) {
+                return FONT_WEIGHT_BOLD;
+            } else if (s.equals(CSS.BOLDER)) {
+                if (parentStyles != null) {
+                    return parentStyles.getFontWeight() + 151;
+                } else {
+                    return FONT_WEIGHT_BOLD;
+                }
+            } else if (s.equals(CSS.LIGHTER)) {
+                if (parentStyles != null) {
+                    return parentStyles.getFontWeight() - 151;
+                } else {
+                    return FONT_WEIGHT_NORMAL;
+                }
+            } else {
+                return FONT_WEIGHT_NORMAL;
+            }
+        } else {
+            return FONT_WEIGHT_NORMAL;
+        }
+    }
+    
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/IProperty.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/IProperty.java
new file mode 100644
index 0000000..3190b35
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/IProperty.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import org.w3c.css.sac.LexicalUnit;
+
+/**
+ * Represents a CSS property.
+ */
+public interface IProperty {
+    
+    /** Constant indicating the length is along the horizontal axis. */
+    public static final byte AXIS_HORIZONTAL = 0;
+
+    /** Constant indicating the length is along the vertical axis. */
+    public static final byte AXIS_VERTICAL = 1;
+
+
+    /**
+     * Returns the name of the property.
+     */
+    public String getName();
+
+    /**
+     * Calculates the value of a property given a LexicalUnit.
+     * @param lu LexicalUnit to interpret.
+     * @param parentStyles Styles of the parent element. These are used
+     * when the property inherits a value.
+     * @param styles Styles currently in effect. Often, the calculated
+     * value depends on previously calculated styles such as font size 
+     * and color.
+     */
+    public Object calculate(LexicalUnit lu, Styles parentStyles, Styles styles);
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/LengthProperty.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/LengthProperty.java
new file mode 100644
index 0000000..fc6c74e
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/LengthProperty.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+
+import org.eclipse.wst.xml.vex.core.internal.core.DisplayDevice;
+import org.w3c.css.sac.LexicalUnit;
+
+/**
+ * A property that represents lengths, such as a margin or padding.
+ */
+public class LengthProperty extends AbstractProperty {
+
+    public LengthProperty(String name, byte axis) {
+        super(name);
+        this.axis = axis;
+    }
+
+    public Object calculate(LexicalUnit lu, Styles parentStyles, Styles styles) {
+
+        DisplayDevice device = DisplayDevice.getCurrent();
+        int ppi = this.axis == AXIS_HORIZONTAL ? device.getHorizontalPPI() : device.getVerticalPPI();
+        
+        if (isLength(lu)) {
+            int length = getIntLength(lu, styles.getFontSize(), ppi);
+            return RelativeLength.createAbsolute(length);
+        } else if (isPercentage(lu)) {
+            return RelativeLength.createRelative(lu.getFloatValue() / 100);
+        } else if (isInherit(lu) && parentStyles != null) {
+            return parentStyles.get(this.getName());
+        } else {
+            // not specified, "auto", or other unknown value
+            return RelativeLength.createAbsolute(0);
+        }
+    }
+
+
+    //============================================================ PRIVATE
+
+    private int axis;
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/LineHeightProperty.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/LineHeightProperty.java
new file mode 100644
index 0000000..8ef5f2f
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/LineHeightProperty.java
@@ -0,0 +1,63 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+
+import org.eclipse.wst.xml.vex.core.internal.core.DisplayDevice;
+import org.w3c.css.sac.LexicalUnit;
+
+/**
+ * The CSS line-height property.
+ */
+public class LineHeightProperty extends AbstractProperty {
+
+    private static final float LINE_HEIGHT_NORMAL = 1.2f;
+
+    /**
+     * Class constructor.
+     */
+    public LineHeightProperty() {
+        super(CSS.LINE_HEIGHT);
+    }
+
+    /**
+     * Calculates the value of the property given a LexicalUnit. Returns
+     * a RelativeLength that is relative to the current font size.
+     */
+    public Object calculate(LexicalUnit lu, Styles parentStyles, Styles styles) {
+        
+        int ppi = DisplayDevice.getCurrent().getVerticalPPI();
+        
+        if (isLength(lu)) {
+            return RelativeLength.createAbsolute(Math.round(getIntLength(lu, styles.getFontSize(), ppi) / styles.getFontSize()));
+        } else if (isNumber(lu)) {
+            if (getNumber(lu) <= 0) {
+                return RelativeLength.createRelative(LINE_HEIGHT_NORMAL);
+            } else {
+                return RelativeLength.createRelative(getNumber(lu));
+            }
+        } else if (isPercentage(lu)) {
+            if (lu.getFloatValue() <= 0) {
+                return RelativeLength.createRelative(LINE_HEIGHT_NORMAL);
+            } else {
+                return RelativeLength.createRelative(lu.getFloatValue() / 100);
+            }
+        } else {
+            // not specified, "inherit", or other unknown value
+            if (parentStyles == null) {
+                return RelativeLength.createRelative(LINE_HEIGHT_NORMAL);
+            } else {
+                return (RelativeLength) parentStyles.get(CSS.LINE_HEIGHT);
+            }
+        }
+    }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/ListStyleTypeProperty.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/ListStyleTypeProperty.java
new file mode 100644
index 0000000..efad554
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/ListStyleTypeProperty.java
@@ -0,0 +1,67 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import org.w3c.css.sac.LexicalUnit;
+
+/**
+ * The CSS list-style-type property.
+ */
+public class ListStyleTypeProperty extends AbstractProperty {
+
+    public ListStyleTypeProperty() {
+        super(CSS.LIST_STYLE_TYPE);
+    }
+
+    public Object calculate(LexicalUnit lu, Styles parentStyles, Styles styles) {
+        if (isListStyleType(lu)) {
+            return lu.getStringValue();
+        } else {
+            if (parentStyles == null) {
+                return CSS.DISC;
+            } else {
+                return parentStyles.getListStyleType();
+            }
+        }
+        
+    }
+
+    private static boolean isListStyleType(LexicalUnit lu) {
+        
+        if (lu == null || lu.getLexicalUnitType() != LexicalUnit.SAC_IDENT) {
+            return false;
+        }
+        
+        String s = lu.getStringValue();
+        return s.equals(CSS.ARMENIAN)
+            || s.equals(CSS.CIRCLE)
+            || s.equals(CSS.CJK_IDEOGRAPHIC)
+            || s.equals(CSS.DECIMAL)
+            || s.equals(CSS.DECIMAL_LEADING_ZERO)
+            || s.equals(CSS.DISC)
+            || s.equals(CSS.GEORGIAN)
+            || s.equals(CSS.HEBREW)
+            || s.equals(CSS.HIRAGANA)
+            || s.equals(CSS.HIRAGANA_IROHA)
+            || s.equals(CSS.KATAKANA)
+            || s.equals(CSS.KATAKANA_IROHA)
+            || s.equals(CSS.LOWER_ALPHA)
+            || s.equals(CSS.LOWER_GREEK)
+            || s.equals(CSS.LOWER_LATIN)
+            || s.equals(CSS.LOWER_ROMAN)
+            || s.equals(CSS.NONE)
+            || s.equals(CSS.SQUARE)
+            || s.equals(CSS.UPPER_ALPHA)
+            || s.equals(CSS.UPPER_LATIN)
+            || s.equals(CSS.UPPER_ROMAN);
+    }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/PropertyDecl.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/PropertyDecl.java
new file mode 100644
index 0000000..76d21a3
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/PropertyDecl.java
@@ -0,0 +1,121 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import java.io.Serializable;
+
+import org.w3c.css.sac.LexicalUnit;
+
+/**
+ * Represents a particular CSS property declaration.
+ */
+public class PropertyDecl implements Comparable, Serializable {
+
+    private static final long serialVersionUID = 1L;
+    
+    public static final byte SOURCE_DEFAULT = 0;
+    public static final byte SOURCE_AUTHOR = 1;
+    public static final byte SOURCE_USER = 2;
+
+    private Rule rule;
+    private String property;
+    private LexicalUnit value;
+    private boolean important;
+
+    /**
+     * Class constructor.
+     */
+    public PropertyDecl(Rule rule,
+			String property,
+			LexicalUnit value,
+			boolean important) {
+	this.rule = rule;
+	this.property = property;
+	this.value = value;
+	this.important = important;
+    }
+
+    /**
+     * Implementation of <code>Comparable.compareTo(Object)</code>
+     * that implements CSS cascade ordering.
+     */
+    public int compareTo(Object o) {
+	PropertyDecl other = (PropertyDecl) o;
+	int thisWeight = this.getWeight();
+	int otherWeight = other.getWeight();
+	if (thisWeight != otherWeight) {
+	    return thisWeight - otherWeight;
+	}
+
+	int thisSpec = this.getRule().getSpecificity();
+	int otherSpec = other.getRule().getSpecificity();
+
+	return thisSpec - otherSpec;
+    }
+
+    /**
+     * Return the value of the <code>important</code> property.
+     */
+    public boolean isImportant() {
+        return this.important;
+    }
+
+    /**
+     * Return the value of the <code>property</code> property.
+     */
+    public String getProperty() {
+        return this.property;
+    }
+
+    /**
+     * Return the value of the <code>rule</code> property.
+     */
+    public Rule getRule() {
+        return this.rule;
+    }
+
+    /**
+     * Return the value of the <code>value</code> property.
+     */
+    public LexicalUnit getValue() {
+        return this.value;
+    }
+
+
+    //===================================================== PRIVATE
+
+    /**
+     * Returns the weight of this declaration, as follows...
+     *
+     * <pre>
+     * 4 => user stylesheet, important decl
+     * 3 => author stylesheet, important decl
+     * 2 => author stylesheet, not important
+     * 1 => user stylesheet, not important
+     * 0 => default stylesheet
+     * </pre>
+     */
+    private int getWeight() {
+        int source = this.getRule().getSource();
+        if (this.isImportant() && source == StyleSheet.SOURCE_USER) {
+            return 4;
+        } else if (this.isImportant() && source == StyleSheet.SOURCE_AUTHOR) {
+            return 3;
+        } else if (!this.isImportant() && source == StyleSheet.SOURCE_AUTHOR) {
+            return 2;
+        } else if (!this.isImportant() && source == StyleSheet.SOURCE_USER) {
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+}
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/PseudoElement.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/PseudoElement.java
new file mode 100644
index 0000000..bb8fbda
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/PseudoElement.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+
+/**
+ * Represents a :before or :after pseudo-element.
+ */
+public class PseudoElement extends Element {
+    
+    public static final String AFTER = "after";
+    public static final String BEFORE = "before";
+
+    /**
+     * Class constructor.
+     * @param parent Parent element to this pseudo-element.
+     * @param name Name of this pseudo-element, e.g. PseudoElement.BEFORE.
+     */    
+    public PseudoElement(Element parent, String name) {
+        super(name);
+        this.setParent(parent);
+    }
+    
+    /**
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    public boolean equals(Object o) {
+        if (o == null || o.getClass() != this.getClass()) {
+            return false;
+        }
+        PseudoElement other = (PseudoElement) o;
+        return this.getParent() == other.getParent()
+            && this.getName().equals(other.getName());
+    }
+    
+    /**
+     * @see java.lang.Object#hashCode()
+     */
+    public int hashCode() {
+        return this.getParent().hashCode() + this.getName().hashCode();
+    }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/RelativeLength.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/RelativeLength.java
new file mode 100644
index 0000000..86eed18
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/RelativeLength.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import java.io.Serializable;
+
+/**
+ * A length that may be expressed as an absolute or relative value.
+ */
+public class RelativeLength implements Serializable {
+    
+    private static final long serialVersionUID = 1L;
+
+    private float percentage;
+    private int absolute;
+    boolean isAbsolute;
+    
+    private static RelativeLength ZERO = new RelativeLength(0, 0, true);
+    
+    /**
+     * Create a relative length representing an absolute value.
+     * @return the new RelativeLength value.
+     */
+    public static RelativeLength createAbsolute(int value) {
+        if (value == 0) {
+            return ZERO;
+        } else {
+            return new RelativeLength(0, value, true);
+        }
+    }
+
+    /**
+     * Create a relative length representing a relative value.
+     * @return the new RelativeLength value.
+     */
+    public static RelativeLength createRelative(float percentage) {
+        return new RelativeLength(percentage, 0, false);
+    }
+
+    /**
+     * Return the value of the length given a reference value. If this
+     * object represents an absolute value, that value is simply returned.
+     * Otherwise, returns the given reference length multiplied by the given
+     * percentage and rounded to the nearest integer.
+     * @param referenceLength reference length by which percentage lengths
+     * will by multiplied.
+     * @return the actual value
+     */
+    public int get(int referenceLength) {
+        if (this.isAbsolute) {
+            return this.absolute;
+        } else {
+            return Math.round(this.percentage * referenceLength);
+        }
+    }
+    
+    //==================================================== PRIVATE
+    
+    private RelativeLength(float percentage, int absolute, boolean isAbsolute) {
+        this.percentage = percentage;
+        this.absolute = absolute;
+        this.isAbsolute = isAbsolute;
+    }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/Rule.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/Rule.java
new file mode 100644
index 0000000..bc4e1e4
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/Rule.java
@@ -0,0 +1,342 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *     Dave Holroyd - Proper specificity for wildcard selector
+ *     John Austin - Implement sibling selectors
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.StringTokenizer;
+
+
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.w3c.css.sac.AttributeCondition;
+import org.w3c.css.sac.CombinatorCondition;
+import org.w3c.css.sac.Condition;
+import org.w3c.css.sac.ConditionalSelector;
+import org.w3c.css.sac.DescendantSelector;
+import org.w3c.css.sac.ElementSelector;
+import org.w3c.css.sac.NegativeSelector;
+import org.w3c.css.sac.Selector;
+import org.w3c.css.sac.SiblingSelector;
+
+/**
+ * Represents a pairing of a selector with a list of styles. This does
+ * not exactly correspond to a rule in a style sheet; there is only
+ * one selector associated with an instance of this class, whereas
+ * multiple selectors may be associated with a style sheet rule.
+ *
+ * Note: <code>Rule</code> implements the <code>Comparable</code>
+ * interface in order to be sorted by "specificity" as defined by the
+ * CSS spec. However, this ordering is <em>not</em> consistent with
+ * <code>equals</code> (rules with the same specificity may not be
+ * equal). Therefore, <code>Rule</code> objects should not be used
+ * with sorted collections or maps in the <code>java.util</code>
+ * package, unless a suitable <code>Comparator</code> is also used.
+ */
+public class Rule implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private byte source;
+    private Selector selector;
+    private List propertyDecls = new ArrayList();
+
+    /**
+     * Class constructor.
+     * 
+     * @param source Source of the rule.
+     * @param selector Selector for the rule.
+     */
+    public Rule(byte source, Selector selector) {
+	this.source = source;
+	this.selector = selector;
+    }
+
+    /**
+     * Adds a property declaration to the rule.
+     *
+     * @param decl new property declaration to add
+     */
+    public void add(PropertyDecl decl) {
+	propertyDecls.add(decl);
+    }
+
+    /**
+     * Returns the selector for the rule.
+     */
+    public Selector getSelector() {
+	return this.selector;
+    }
+
+    /**
+     * Returns the source of this rule.
+     * @return one of StyleSheet.SOURCE_DEFAULT, StyleSheet.SOURCE_AUTHOR, or
+     * StyleSheet.SOURCE_USER.
+     */
+    public byte getSource() {
+	return this.source;
+    }
+
+    /**
+     * Returns an array of the property declarations in this rule.
+     */
+    public PropertyDecl[] getPropertyDecls() {
+	return (PropertyDecl[]) 
+	    this.propertyDecls.toArray(new PropertyDecl[propertyDecls.size()]);
+    }
+
+    /**
+     * Calculates the specificity for the selector associated with
+     * this rule. The specificity is represented as an integer whose
+     * base-10 representation, xxxyyyzzz, can be decomposed into the
+     * number of "id" selectors (xxx), "class" selectors (yyy), and
+     * "element" selectors (zzz). Composite selectors result in a
+     * recursive call.
+     */
+    public int getSpecificity() {
+	return specificity(this.getSelector());
+    }
+
+    /**
+     * Returns true if the given element matches this rule's selector.
+     *
+     * @param element Element to check.
+     */
+    public boolean matches(Element element) {
+        return matches(this.selector, element);
+    }
+
+    //==================================================== PRIVATE
+    
+    /**
+     * Returns true if the given element matches the given selector.
+     */
+    private static boolean matches(Selector selector, Element element) {
+
+        if (element == null) {
+            // This can happen when, e.g., with the rule "foo > *".
+            // Since the root element matches the "*", we check if
+            // its parent matches "foo", but of course its parent 
+            // is null
+            return false;
+        }
+        
+        String elementName = element.getName();
+        int selectorType = selector.getSelectorType();
+
+        switch (selectorType) {
+        
+        case Selector.SAC_ANY_NODE_SELECTOR:
+            // You'd think we land here if we have a * rule, but instead
+            // it appears we get a SAC_ELEMENT_NODE_SELECTOR with localName==null
+            return true;
+
+        case Selector.SAC_CONDITIONAL_SELECTOR:
+            // This little wart is the product of a mismatch btn the CSS
+            // spec an the Flute parser. CSS treats pseudo-elements as elements
+            // attached to their parents, while Flute treats them like attributes
+            ConditionalSelector cs = (ConditionalSelector) selector;
+            if (cs.getCondition().getConditionType() == Condition.SAC_PSEUDO_CLASS_CONDITION) {
+                if (element instanceof PseudoElement) {
+                    AttributeCondition ac = (AttributeCondition) cs.getCondition();
+                    return ac.getValue().equals(element.getName())
+                        && matches(cs.getSimpleSelector(), element.getParent());
+                } else {
+                    return false;
+                }
+            } else {
+                return matches(cs.getSimpleSelector(), element) &&
+                    matchesCondition(cs.getCondition(), element);
+            }
+
+        case Selector.SAC_ELEMENT_NODE_SELECTOR:
+            String selectorName = ((ElementSelector)selector).getLocalName();
+            if (selectorName == null) {
+                // We land here if we have a wildcard selector (*) or 
+                // a pseudocondition w/o an element name (:before)
+                // Probably other situations too (conditional w/o element
+                // name? e.g. [attr=value])
+                return true;
+            }
+            if (selectorName.equals(elementName)) {
+                return true;
+            }
+            break;
+
+        case Selector.SAC_DESCENDANT_SELECTOR:
+            DescendantSelector ds = (DescendantSelector) selector;
+            return matches(ds.getSimpleSelector(), element) &&
+                matchesAncestor(ds.getAncestorSelector(), element.getParent()); 
+                
+        case Selector.SAC_CHILD_SELECTOR:
+            DescendantSelector ds2 = (DescendantSelector) selector;
+            Element parent = element.getParent();
+            if (element instanceof PseudoElement) {
+                parent = parent.getParent(); // sigh - this looks inelegant, but whatcha gonna do?
+            }
+            return matches(ds2.getSimpleSelector(), element) &&
+                matches(ds2.getAncestorSelector(), parent); 
+                
+        case Selector.SAC_DIRECT_ADJACENT_SELECTOR:
+            
+            SiblingSelector ss = (SiblingSelector) selector;
+        
+            if (element != null && element.getParent() != null 
+                    && matches(ss.getSiblingSelector(), element) ) {
+                
+                // find next sibling
+                
+                final Iterator i = element.getParent().getChildIterator();
+                Element e = null;
+                Element f = null;
+                
+                while (i.hasNext() && e != element) {
+                    f = e; 
+                    e = (Element)i.next();
+                }
+                
+                if (e == element) {
+                    return matches(ss.getSelector(), f);
+                }
+            }
+            return false;
+            
+        default:
+            //System.out.println("DEBUG: selector type not supported");
+            // TODO: warning: selector not supported
+        }
+        return false;
+    }
+    
+    /**
+     * Returns true if some ancestor of the given element matches the given
+     * selector.
+     */
+    private static boolean matchesAncestor(Selector selector, Element element) {
+        Element e = element;
+        while (e != null) {
+            if (matches(selector, e)) {
+                return true;
+            }
+            e = e.getParent();
+        }
+        return false;
+    }
+    
+    private static boolean matchesCondition(Condition condition, Element element) {
+        
+        AttributeCondition acon;
+        String attributeName;
+        String value;
+        
+        switch (condition.getConditionType()) {
+        case Condition.SAC_PSEUDO_CLASS_CONDITION:
+            return false;
+
+        case Condition.SAC_ATTRIBUTE_CONDITION:
+            acon = (AttributeCondition)condition;
+            value = element.getAttribute(acon.getLocalName());
+            if (acon.getValue() != null) {
+                return acon.getValue().equals(value);
+            } else { 
+                return value != null;
+            }
+            
+        case Condition.SAC_ONE_OF_ATTRIBUTE_CONDITION:
+        case Condition.SAC_CLASS_CONDITION:
+            
+            acon = (AttributeCondition)condition;
+
+            if (condition.getConditionType() == Condition.SAC_CLASS_CONDITION) {
+                attributeName = "class";
+            } else {
+                attributeName = acon.getLocalName();
+            }
+            
+            value = element.getAttribute(attributeName);
+            if (value == null) {
+                return false;
+            }
+            StringTokenizer st = new StringTokenizer(value);
+            while (st.hasMoreTokens()) {
+                if (st.nextToken().equals(acon.getValue())) {
+                    return true;
+                }
+            }
+            return false;
+               
+            
+        case Condition.SAC_AND_CONDITION:
+            CombinatorCondition ccon = (CombinatorCondition)condition;
+            return matchesCondition(ccon.getFirstCondition(), element)
+                && matchesCondition(ccon.getSecondCondition(), element);
+            
+        default:
+            // TODO: warning: condition not supported
+            System.out.println("Unsupported condition type: " + condition.getConditionType());
+        }
+        return false;
+    }
+
+    /**
+     * Calculates the specificity for a selector.
+     */
+    private static int specificity(Selector sel) {
+	if (sel instanceof ElementSelector) {
+            if (((ElementSelector)sel).getLocalName() == null) {
+                // actually wildcard selector -- see comment in matches()
+                return 0;
+            } else {
+	        return 1;
+            }
+	} else if (sel instanceof DescendantSelector) {
+	    DescendantSelector ds = (DescendantSelector) sel;
+	    return specificity(ds.getAncestorSelector()) +
+		specificity(ds.getSimpleSelector());
+	} else if (sel instanceof SiblingSelector) {
+	    SiblingSelector ss = (SiblingSelector) sel;
+	    return specificity(ss.getSelector()) +
+		specificity(ss.getSiblingSelector());
+	} else if (sel instanceof NegativeSelector) {
+	    NegativeSelector ns = (NegativeSelector) sel;
+	    return specificity(ns.getSimpleSelector());
+	} else if (sel instanceof ConditionalSelector) {
+	    ConditionalSelector cs = (ConditionalSelector) sel;
+	    return specificity(cs.getCondition()) +
+		specificity(cs.getSimpleSelector());
+	} else {
+	    return 0;
+	}
+    }
+
+    /**
+     * Calculates the specificity for a condition.
+     */
+    private static int specificity(Condition cond) {
+	if (cond instanceof CombinatorCondition) {
+	    CombinatorCondition cc = (CombinatorCondition) cond;
+	    return specificity(cc.getFirstCondition()) +
+		specificity(cc.getSecondCondition());
+	} else if (cond instanceof AttributeCondition) {
+	    if (cond.getConditionType() == Condition.SAC_ID_CONDITION) {
+		return 1000000;
+	    } else {
+		return 1000;
+	    }
+	} else {
+	    return 0;
+	}
+    }
+}
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/SacFactory.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/SacFactory.java
new file mode 100644
index 0000000..9ef27b4
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/SacFactory.java
@@ -0,0 +1,448 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.w3c.css.sac.AttributeCondition;
+import org.w3c.css.sac.CharacterDataSelector;
+import org.w3c.css.sac.CombinatorCondition;
+import org.w3c.css.sac.Condition;
+import org.w3c.css.sac.ConditionalSelector;
+import org.w3c.css.sac.ContentCondition;
+import org.w3c.css.sac.DescendantSelector;
+import org.w3c.css.sac.ElementSelector;
+import org.w3c.css.sac.LangCondition;
+import org.w3c.css.sac.LexicalUnit;
+import org.w3c.css.sac.NegativeCondition;
+import org.w3c.css.sac.NegativeSelector;
+import org.w3c.css.sac.PositionalCondition;
+import org.w3c.css.sac.ProcessingInstructionSelector;
+import org.w3c.css.sac.Selector;
+import org.w3c.css.sac.SiblingSelector;
+import org.w3c.css.sac.SimpleSelector;
+
+/**
+ * Factory for producing serializable Conditions, LexicalUnits, and 
+ * Selectors. The SAC parser creates instances of these that may not be
+ * serializable (and in fact, those from Flute aren't). To serialize
+ * StyleSheets, which contain references to these SAC objects, we must
+ * clone the objects into our own serializable ones.
+ */
+public class SacFactory {
+
+    /**
+     * Clone the given condition, returning one that is serializable.
+     * @param condition Condition to clone.
+     */
+    public Condition cloneCondition(Condition condition) {
+        Condition clone = null;
+        if (condition == null) {
+            return null;
+        } else if (condition instanceof AttributeCondition) {
+            clone = new AttributeConditionImpl((AttributeCondition) condition);
+        } else if (condition instanceof CombinatorCondition) {
+            clone = new CombinatorConditionImpl((CombinatorCondition) condition, this);
+        } else if (condition instanceof ContentCondition) {
+            clone = new ContentConditionImpl((ContentCondition) condition);
+        } else if (condition instanceof LangCondition) {
+            clone = new LangConditionImpl((LangCondition) condition);
+        } else if (condition instanceof NegativeCondition) {
+            clone = new NegativeConditionImpl((NegativeCondition) condition, this);
+        } else if (condition instanceof PositionalCondition) {
+            clone = new PositionalConditionImpl((PositionalCondition) condition);
+        } else {
+            throw new IllegalArgumentException("Unsupported condition type: " + condition.getClass());
+        }
+        return clone;
+    }
+
+    /**
+     * Clone the given lexical unit, returning one that is serializable.
+     * @param lu LexicalUnit to clone.
+     */
+    public LexicalUnit cloneLexicalUnit(LexicalUnit lu) {
+        if (lu == null) {
+            return null;
+        } else if (this.cache.containsKey(lu)) {
+            return (LexicalUnit) this.cache.get(lu);
+        } else {
+            return new LexicalUnitImpl(lu, this);
+        }
+    }
+    
+    /**
+     * Clone the given selector, returning one that is serializable.
+     */
+    public Selector cloneSelector(Selector selector) {
+        Selector clone = null;
+        if (selector == null) {
+            return null;
+        } else if (selector instanceof CharacterDataSelector) {
+            clone = new CharacterDataSelectorImpl((CharacterDataSelector) selector);
+        } else if (selector instanceof ConditionalSelector) {
+            clone = new ConditionalSelectorImpl((ConditionalSelector) selector, this);
+        } else if (selector instanceof DescendantSelector) {
+            clone = new DescendantSelectorImpl((DescendantSelector) selector, this);
+        } else if (selector instanceof ElementSelector) {
+            clone = new ElementSelectorImpl((ElementSelector) selector);
+        } else if (selector instanceof NegativeSelector) {
+            clone = new NegativeSelectorImpl((NegativeSelector) selector, this);
+        } else if (selector instanceof ProcessingInstructionSelector) {
+            clone = new ProcessingInstructionSelectorImpl((ProcessingInstructionSelector) selector);
+        } else if (selector instanceof SiblingSelector) {
+            clone = new SiblingSelectorImpl((SiblingSelector) selector, this);
+        } else {
+            throw new IllegalArgumentException("Unsupported selector type: " + selector.getClass());
+        }
+        return clone;
+    }
+
+    //===================================================== PRIVATE
+    
+    private Map cache = new HashMap();
+    
+    private static class ConditionImpl implements Condition, Serializable {
+        public ConditionImpl(Condition condition) {
+            this.type = condition.getConditionType();
+        }
+        public short getConditionType() {
+            return this.type;
+        }
+        private short type;
+    }
+    
+    private static class AttributeConditionImpl extends ConditionImpl
+    implements AttributeCondition {
+        public AttributeConditionImpl(AttributeCondition condition) {
+            super(condition);
+            this.namespaceURI = condition.getNamespaceURI();
+            this.localName = condition.getLocalName();
+            this.specified = condition.getSpecified();
+            this.value = condition.getValue();
+        }
+        public String getNamespaceURI() {
+            return this.namespaceURI;
+        }
+        public String getLocalName() {
+            return this.localName;
+        }
+        public boolean getSpecified() {
+            return this.specified;
+        }
+        public String getValue() {
+            return this.value;
+        }
+        private String namespaceURI;
+        private String localName;
+        private boolean specified;
+        private String value;
+    }
+    
+    private static class CombinatorConditionImpl extends ConditionImpl
+    implements CombinatorCondition {
+        public CombinatorConditionImpl(CombinatorCondition condition, SacFactory factory) {
+            super(condition);
+            this.firstCondition = factory.cloneCondition(condition.getFirstCondition());
+            this.secondCondition = factory.cloneCondition(condition.getSecondCondition());
+        }
+        public Condition getFirstCondition() {
+            return this.firstCondition;
+        }
+        public Condition getSecondCondition() {
+            return this.secondCondition;
+        }
+        private Condition firstCondition;
+        private Condition secondCondition;
+    }
+    
+    private static class ContentConditionImpl extends ConditionImpl
+    implements ContentCondition {
+        public ContentConditionImpl(ContentCondition condition) {
+            super(condition);
+            this.data = condition.getData();
+        }
+        public String getData() {
+            return this.data;
+        }
+        private String data;
+    }
+    
+    private static class LangConditionImpl extends ConditionImpl
+    implements LangCondition {
+        public LangConditionImpl(LangCondition condition) {
+            super(condition);
+            this.lang = condition.getLang();
+        }
+        public String getLang() {
+            return this.lang;
+        }
+        private String lang;
+    }
+    
+    private static class NegativeConditionImpl extends ConditionImpl
+    implements NegativeCondition {
+        public NegativeConditionImpl(NegativeCondition condition, SacFactory factory) {
+            super(condition);
+            this.condition = factory.cloneCondition(condition.getCondition());
+        }
+        public Condition getCondition() {
+            return this.condition;
+        }
+        private Condition condition;
+    }
+    
+    private static class PositionalConditionImpl extends ConditionImpl
+    implements PositionalCondition {
+        public PositionalConditionImpl(PositionalCondition condition) {
+            super(condition);
+            this.position = condition.getPosition();
+            this.typeNode = condition.getTypeNode();
+            this.type = condition.getType();
+        }
+        public int getPosition() {
+            return this.position;
+        }
+        public boolean getTypeNode() {
+            return this.typeNode;
+        }
+        public boolean getType() {
+            return this.type;
+        }
+        private int position;
+        private boolean typeNode;
+        private boolean type;
+    }
+    
+    
+    private static class LexicalUnitImpl implements LexicalUnit, Serializable {
+
+        public LexicalUnitImpl(LexicalUnit lu, SacFactory factory) {
+            factory.cache.put(lu, this);
+            this.type = lu.getLexicalUnitType();
+            this.s = lu.getStringValue();
+            this.i = lu.getIntegerValue();
+            this.f = lu.getFloatValue();
+            this.functionName = lu.getFunctionName();
+            this.next = factory.cloneLexicalUnit(lu.getNextLexicalUnit());
+            this.prev = factory.cloneLexicalUnit(lu.getPreviousLexicalUnit());
+            this.parameters = factory.cloneLexicalUnit(lu.getParameters());
+            if (this.type == SAC_PERCENTAGE
+                    || this.type == SAC_EM
+                    || this.type == SAC_EX
+                    || this.type == SAC_PIXEL
+                    || this.type == SAC_CENTIMETER
+                    || this.type == SAC_MILLIMETER
+                    || this.type == SAC_INCH
+                    || this.type == SAC_POINT
+                    || this.type == SAC_PICA
+                    || this.type == SAC_DEGREE
+                    || this.type == SAC_RADIAN
+                    || this.type == SAC_GRADIAN
+                    || this.type == SAC_MILLISECOND
+                    || this.type == SAC_SECOND
+                    || this.type == SAC_HERTZ
+                    || this.type == SAC_KILOHERTZ
+                    || this.type == SAC_DIMENSION) {
+                this.dimensionText = lu.getDimensionUnitText();
+            }
+            this.stringRepresentation = lu.toString();
+        }
+
+        public short getLexicalUnitType() {
+            return this.type;
+        }
+
+        public LexicalUnit getNextLexicalUnit() {
+            return this.next;
+        }
+
+        public LexicalUnit getPreviousLexicalUnit() {
+            return this.prev;
+        }
+
+        public int getIntegerValue() {
+            return this.i;
+        }
+
+        public float getFloatValue() {
+            return this.f;
+        }
+
+        public String getDimensionUnitText() {
+            return this.dimensionText;
+        }
+
+        public String getFunctionName() {
+            return this.functionName;
+        }
+
+        public LexicalUnit getParameters() {
+            return this.parameters;
+        }
+
+        public String getStringValue() {
+            return this.s;
+        }
+
+        public String getStringRepresentation() {
+            return this.stringRepresentation;
+        }
+        
+        public LexicalUnit getSubValues() {
+            return this.parameters;
+        }
+
+        //================================================= PRIVATE
+        
+        private LexicalUnitImpl() {
+        }
+
+        private short type;
+        private LexicalUnit next;
+        private LexicalUnit prev;
+        private LexicalUnit parameters;
+        private String s;
+        private int i;
+        private float f;
+        private String functionName;
+        private String dimensionText;
+        private String stringRepresentation;
+    }
+
+    private static class SelectorImpl implements Selector, Serializable {
+        public SelectorImpl(Selector selector) {
+            this.type = selector.getSelectorType();
+        }
+        public short getSelectorType() {
+            return this.type;
+        }
+        private short type;
+    }
+    
+    private static class CharacterDataSelectorImpl extends SelectorImpl
+    implements CharacterDataSelector {
+        public CharacterDataSelectorImpl(CharacterDataSelector selector) {
+            super(selector);
+            this.data = selector.getData();
+        }
+        public String getData() {
+            return this.data;
+        }
+        private String data;
+    }
+    
+    private static class ConditionalSelectorImpl extends SelectorImpl 
+    implements ConditionalSelector {
+        public ConditionalSelectorImpl(ConditionalSelector selector, SacFactory factory) {
+            super(selector);
+            this.condition = factory.cloneCondition(selector.getCondition());
+            this.simpleSelector = 
+                (SimpleSelector) factory.cloneSelector(selector.getSimpleSelector());
+        }
+        public SimpleSelector getSimpleSelector() {
+            return this.simpleSelector;
+        }
+        public Condition getCondition() {
+            return this.condition;
+        }
+        private Condition condition;
+        private SimpleSelector simpleSelector;
+    }
+    
+    private static class DescendantSelectorImpl extends SelectorImpl
+    implements DescendantSelector {
+        public DescendantSelectorImpl(DescendantSelector selector, SacFactory factory) {
+            super(selector);
+            this.ancestor = factory.cloneSelector(selector.getAncestorSelector());
+            this.simpleSelector = 
+                (SimpleSelector) factory.cloneSelector(selector.getSimpleSelector());
+        }
+        public Selector getAncestorSelector() {
+            return this.ancestor;
+        }
+        public SimpleSelector getSimpleSelector() {
+            return this.simpleSelector;
+        }
+        private Selector ancestor;
+        private SimpleSelector simpleSelector;
+    }
+    
+    private static class ElementSelectorImpl extends SelectorImpl
+    implements ElementSelector {
+        public ElementSelectorImpl(ElementSelector selector) {
+            super(selector);
+            this.namespaceURI = selector.getNamespaceURI();
+            this.localName = selector.getLocalName();
+        }
+        public String getNamespaceURI() {
+            return this.namespaceURI;
+        }
+        public String getLocalName() {
+            return this.localName;
+        }
+        private String namespaceURI;
+        private String localName;
+    }
+
+    private static class NegativeSelectorImpl extends SelectorImpl
+    implements NegativeSelector {
+        public NegativeSelectorImpl(NegativeSelector selector, SacFactory factory) {
+            super(selector);
+            this.simpleSelector = (SimpleSelector) factory.cloneSelector(selector.getSimpleSelector());
+        }
+        public SimpleSelector getSimpleSelector() {
+            return this.simpleSelector;
+        }
+        private SimpleSelector simpleSelector;
+    }
+    
+    private static class ProcessingInstructionSelectorImpl extends SelectorImpl
+    implements ProcessingInstructionSelector {
+        public ProcessingInstructionSelectorImpl(ProcessingInstructionSelector selector) {
+            super(selector);
+            this.target = selector.getTarget();
+            this.data = selector.getData();
+        }
+        public String getTarget() {
+            return this.target;
+        }
+        public String getData() {
+            return this.data;
+        }
+        private String target;
+        private String data;
+    }
+    
+    private static class SiblingSelectorImpl extends SelectorImpl
+    implements SiblingSelector {
+        private SiblingSelectorImpl(SiblingSelector selector, SacFactory factory) {
+            super(selector);
+            this.nodeType = selector.getNodeType();
+            this.selector = factory.cloneSelector(selector.getSelector());
+            this.siblingSelector = (SimpleSelector) factory.cloneSelector(selector.getSiblingSelector());
+        }
+        public short getNodeType() {
+            return this.nodeType;
+        }
+        public Selector getSelector() {
+            return this.selector;
+        }
+        public SimpleSelector getSiblingSelector() {
+            return this.siblingSelector;
+        }
+        private short nodeType;
+        private Selector selector;
+        private SimpleSelector siblingSelector;
+    }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/StyleSheet.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/StyleSheet.java
new file mode 100644
index 0000000..f33e939
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/StyleSheet.java
@@ -0,0 +1,351 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *     Dave Holroyd - Implement font-weight:bolder
+ *     Dave Holroyd - Implement text decoration
+ *     John Austin - More complete CSS constants. Add the colour "orange".
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import java.io.Serializable;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+
+import org.eclipse.wst.xml.vex.core.internal.core.FontSpec;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.w3c.css.sac.LexicalUnit;
+
+/**
+ * Represents a CSS style sheet.
+ */
+public class StyleSheet implements Serializable {
+
+    /**
+     * Standard CSS properties.
+     */
+    private static final IProperty[] CSS_PROPERTIES = new IProperty[] {
+            new DisplayProperty(),
+            new LineHeightProperty(),
+            new ListStyleTypeProperty(),
+            new TextAlignProperty(),
+            new WhiteSpaceProperty(),
+            
+            new FontFamilyProperty(),
+            new FontSizeProperty(),
+            new FontStyleProperty(),
+            new FontWeightProperty(),
+            new TextDecorationProperty(),
+            
+            new ColorProperty(CSS.COLOR),
+            new ColorProperty(CSS.BACKGROUND_COLOR),
+            
+            new LengthProperty(CSS.MARGIN_BOTTOM, IProperty.AXIS_VERTICAL),
+            new LengthProperty(CSS.MARGIN_LEFT, IProperty.AXIS_HORIZONTAL),
+            new LengthProperty(CSS.MARGIN_RIGHT, IProperty.AXIS_HORIZONTAL),
+            new LengthProperty(CSS.MARGIN_TOP, IProperty.AXIS_VERTICAL),
+            
+            new LengthProperty(CSS.PADDING_BOTTOM, IProperty.AXIS_VERTICAL),
+            new LengthProperty(CSS.PADDING_LEFT, IProperty.AXIS_HORIZONTAL),
+            new LengthProperty(CSS.PADDING_RIGHT, IProperty.AXIS_HORIZONTAL),
+            new LengthProperty(CSS.PADDING_TOP, IProperty.AXIS_VERTICAL),
+            
+            new ColorProperty(CSS.BORDER_BOTTOM_COLOR),
+            new ColorProperty(CSS.BORDER_LEFT_COLOR),
+            new ColorProperty(CSS.BORDER_RIGHT_COLOR),
+            new ColorProperty(CSS.BORDER_TOP_COLOR),
+            new BorderStyleProperty(CSS.BORDER_BOTTOM_STYLE),
+            new BorderStyleProperty(CSS.BORDER_LEFT_STYLE),
+            new BorderStyleProperty(CSS.BORDER_RIGHT_STYLE),
+            new BorderStyleProperty(CSS.BORDER_TOP_STYLE),
+            new BorderWidthProperty(CSS.BORDER_BOTTOM_WIDTH, CSS.BORDER_BOTTOM_STYLE, IProperty.AXIS_VERTICAL),
+            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(),
+    };
+
+    /**
+     * The properties to calculate. This can be changed by the app.
+     */
+    private static IProperty[] properties = CSS_PROPERTIES;
+
+    /**
+     * Style sheet is the default for the renderer.
+     */
+    public static final byte SOURCE_DEFAULT = 0;
+
+    /**
+     * Style sheet was provided by the document author.
+     */
+    public static final byte SOURCE_AUTHOR = 1;
+
+    /**
+     * Style sheet was provided by the user.
+     */
+    public static final byte SOURCE_USER = 2;
+
+    /**
+     * The rules that comprise the stylesheet.
+     */
+    private Rule[] rules;
+    
+    /**
+     * Computing styles can be expensive, e.g. we have to calculate the styles
+     * of all parents of an element. We therefore cache styles in a map of
+     * element => WeakReference(styles). A weak hash map is used to avoid
+     * leaking memory as elements are deleted. By using weak references to
+     * the values, we also ensure the cache is memory-sensitive.
+     * 
+     * This must be transient to prevent it from being serialized, as
+     * WeakHashMaps are not serializable.
+     */
+    private transient Map styleMap = null;
+    
+    /**
+     * Class constructor.
+     *
+     * @param rules Rules that constitute the style sheet.
+     */
+    public StyleSheet(Rule[] rules) {
+	this.rules = rules;
+    }
+
+    /**
+     * Flush any cached styles for the given element.
+     * @param element Element for which styles are to be flushed.
+     */
+    public void flushStyles(Element element) {
+        this.getStyleMap().remove(element);
+    }
+    
+    /**
+     * Returns a pseudo-element representing content to be displayed
+     * after the given element, or null if there is no such content.
+     * @param element Parent element of the pseudo-element.
+     */
+    public Element getAfterElement(Element element) {
+        PseudoElement pe = new PseudoElement(element, PseudoElement.AFTER);
+        Styles styles = this.getStyles(pe);
+        if (styles == null) {
+            return null;    
+        } else {
+            return pe;
+        }
+    }
+    
+    /**
+     * Returns a pseudo-element representing content to be displayed
+     * before the given element, or null if there is no such content.
+     * @param element Parent element of the pseudo-element.
+     */
+    public Element getBeforeElement(Element element) {
+        PseudoElement pe = new PseudoElement(element, PseudoElement.BEFORE);
+        Styles styles = this.getStyles(pe);
+        if (styles == null) {
+            return null;    
+        } else {
+            return pe;
+        }
+    }
+    
+    /**
+     * Returns the array of standard CSS properties.
+     */
+    public static IProperty[] getCssProperties() {
+        return CSS_PROPERTIES;
+    }
+
+    /**
+     * Returns the styles for the given element. The styles are cached to
+     * ensure reasonable performance.
+     *
+     * @param element Element for which to calculate the styles.
+     */
+    public Styles getStyles(Element element) {
+
+        Styles styles;
+        WeakReference ref = (WeakReference) this.getStyleMap().get(element);
+        
+        if (ref != null) {
+            // can't combine these tests, since calling ref.get() twice
+            // (once to query for null, once to get the value) would
+            // cause a race condition should the GC happen btn the two.
+            styles = (Styles) ref.get();
+            if (styles != null) {
+                return styles;
+            }
+         } else if (this.getStyleMap().containsKey(element)) {
+            // this must be a pseudo-element with no content
+            return null;
+        }
+
+        styles = calculateStyles(element);
+
+        if (styles == null) {
+            // Yes, they can be null if element is a PseudoElement with no content property
+            this.getStyleMap().put(element, null);
+        } else {
+            this.getStyleMap().put(element, new WeakReference(styles));
+        }
+
+        return styles;
+    }
+    
+    
+    
+    private Styles calculateStyles(Element element) {
+        
+        Styles styles = new Styles();
+        Styles parentStyles = null;
+        if (element.getParent() != null) {
+            parentStyles = this.getStyles(element.getParent());
+        }
+        
+	Map decls = this.getApplicableDecls(element);
+
+	LexicalUnit lu;
+    
+        // If we're finding a pseudo-element, look at the 'content' property
+        // first, since most of the time it'll be empty and we'll return null.
+        if (element instanceof PseudoElement) {
+            lu = (LexicalUnit) decls.get(CSS.CONTENT);
+            if (lu == null) {
+                return null;
+            }
+            
+            List content = new ArrayList();
+            while (lu != null) {
+                if (lu.getLexicalUnitType() == LexicalUnit.SAC_STRING_VALUE) {
+                    content.add(lu.getStringValue());
+                }
+                lu = lu.getNextLexicalUnit();
+            }
+            styles.setContent(content);
+        }
+    
+        for (int i = 0; i < properties.length; i++) {
+            IProperty property = properties[i];
+            lu = (LexicalUnit) decls.get(property.getName());
+            Object value = property.calculate(lu, parentStyles, styles);
+            styles.put(property.getName(), value);
+        }
+        
+	// Now, map font-family, font-style, font-weight, and font-size onto
+	// an AWT font.
+
+	int styleFlags = FontSpec.PLAIN;
+	String fontStyle = styles.getFontStyle();
+	if (fontStyle.equals(CSS.ITALIC) || fontStyle.equals(CSS.OBLIQUE)) {
+	    styleFlags |= FontSpec.ITALIC;
+	}
+	if (styles.getFontWeight() > 550) {
+	    // 550 is halfway btn normal (400) and bold (700)
+	    styleFlags |= FontSpec.BOLD;
+	}
+        String textDecoration = styles.getTextDecoration();
+        if (textDecoration.equals(CSS.UNDERLINE)) {
+            styleFlags |= FontSpec.UNDERLINE;
+        } else if (textDecoration.equals(CSS.OVERLINE)) {
+            styleFlags |= FontSpec.OVERLINE;
+        } else if (textDecoration.equals(CSS.LINE_THROUGH)) {
+            styleFlags |= FontSpec.LINE_THROUGH;
+        }
+
+	styles.setFont(new FontSpec(styles.getFontFamilies(), 
+				styleFlags, 
+				Math.round(styles.getFontSize())));
+
+	return styles;
+    }
+
+    /**
+     * Returns the list of properties to be parsed by StyleSheets in this app.
+     */
+    public static IProperty[] getProperties() {
+        return StyleSheet.properties;
+    }
+    
+    /**
+     * Returns the rules comprising this stylesheet.
+     */
+    public Rule[] getRules() {
+        return this.rules;
+    }
+
+
+    /**
+     * Sets the list of properties to be used by StyleSheets in this application.
+     * @param properties New array of IProperty objects to be used.
+     */
+    public static void setProperties(IProperty[] properties) {
+        StyleSheet.properties = properties;
+    }
+    
+    //========================================================= PRIVATE
+    
+    
+    /**
+     * Returns all the declarations that apply to the given element.
+     */
+    private Map getApplicableDecls(Element element) {
+	// Find all the property declarations that apply to this element.
+	List declList = new ArrayList();
+	Rule[] rules = this.getRules();
+	
+	for (int i = 0; i < rules.length; i++) {
+	    Rule rule = rules[i];
+	    if (rule.matches(element)) {
+		PropertyDecl[] ruleDecls = rule.getPropertyDecls();
+		for (int j = 0; j < ruleDecls.length; j++) {
+		    declList.add(ruleDecls[j]);
+		}
+	    }
+	}
+
+	// Sort in cascade order. We can then just stuff them into a
+	// map and get the right values since higher-priority values
+	// come later and overwrite lower-priority ones.
+	Collections.sort(declList);
+
+	Map decls = new HashMap();
+	Iterator iter = declList.iterator();
+	while (iter.hasNext()) {
+	    PropertyDecl decl = (PropertyDecl) iter.next();
+	    PropertyDecl prevDecl = (PropertyDecl) decls.get(decl.getProperty());
+	    if (prevDecl == null || !prevDecl.isImportant() || decl.isImportant()) {
+	        decls.put(decl.getProperty(), decl);
+	    }
+	}
+
+	Map values = new HashMap();
+	for (Iterator it = decls.keySet().iterator(); it.hasNext();) {
+	    PropertyDecl decl = (PropertyDecl) decls.get(it.next());
+	    values.put(decl.getProperty(), decl.getValue());
+	}
+
+	return values;
+    }
+
+
+    private Map getStyleMap() {
+        if (this.styleMap == null) {
+            this.styleMap = new WeakHashMap();
+        }
+        return this.styleMap;
+    }
+}
+
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/StyleSheetReader.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/StyleSheetReader.java
new file mode 100644
index 0000000..b32d4c8
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/StyleSheetReader.java
@@ -0,0 +1,545 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import java.io.CharArrayReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.w3c.css.sac.CSSException;
+import org.w3c.css.sac.DocumentHandler;
+import org.w3c.css.sac.InputSource;
+import org.w3c.css.sac.LexicalUnit;
+import org.w3c.css.sac.Parser;
+import org.w3c.css.sac.SACMediaList;
+import org.w3c.css.sac.Selector;
+import org.w3c.css.sac.SelectorList;
+
+/**
+ * Driver for the creation of StyleSheet objects.
+ */
+public class StyleSheetReader {
+
+    /**
+     * Class constructor.
+     */
+    public StyleSheetReader() {
+        
+    }
+    
+    protected static Parser createParser() {
+        //return new org.apache.batik.css.parser.Parser();
+        return new org.w3c.flute.parser.Parser();
+    }
+    
+    /**
+     * Creates a new StyleSheet object from a URL.
+     * @param url URL from which to read the style sheet.
+     */
+    public StyleSheet read(URL url) throws IOException {
+        return this.read(new InputSource(url.toString()), url);
+    }
+
+    /**
+     * Creates a style sheet from a string. This is mainly used for small
+     * style sheets within unit tests.
+     * @param s String containing the style sheet.
+     */
+    public StyleSheet read(String s) throws CSSException, IOException {
+        Reader reader = new CharArrayReader(s.toCharArray());
+        return this.read(new InputSource(reader), null);
+    }
+    
+    /**
+     * Creates a new Stylesheet from an input source.
+     * @param inputSource InputSource from which to read the stylesheet.
+     * @param url URL representing the input source, used to resolve @import
+     * rules with relative URIs. May be null, in which case @import rules
+     * are ignored.
+     */
+    public StyleSheet read(InputSource inputSource, URL url) 
+            throws CSSException, IOException {
+        
+        Parser parser = createParser();
+        List rules = new ArrayList();
+        StyleSheetBuilder ssBuilder = new StyleSheetBuilder(this.source, rules, url);
+        parser.setDocumentHandler(ssBuilder);
+        parser.parseStyleSheet(inputSource);
+        Rule[] ruleArray = (Rule[]) rules.toArray(new Rule[rules.size()]);
+        return new StyleSheet(ruleArray);
+    }
+    
+    /**
+     * Returns the source of the stylesheet.
+     */
+    public byte getSource() {
+        return source;
+    }
+
+    /**
+     * Sets the source of the stylesheet. Must be one of StyleSheet.SOURCE_USER,
+     * StyleSheet.SOURCE_AUTHOR, or StyleSheet.SOURCE_DEFAULT.
+     * @param source The source to set.
+     */
+    public void setSource(byte source) {
+        this.source = source;
+    }
+
+    //======================================================== PRIVATE
+
+    private byte source = StyleSheet.SOURCE_DEFAULT;
+    
+    private static class StyleSheetBuilder implements DocumentHandler {
+
+        private byte source;
+        
+        // The rules that will be added to the stylesheet
+        private List rules;
+        
+        // The rules to which decls are currently being added
+        private List currentRules;
+        
+        // URL from which @import rules relative URIs are resolved.
+        // May be null!
+        private URL url;
+        
+        // Factory for creating serializable clones of SAC objects
+        SacFactory factory = new SacFactory();
+
+        public StyleSheetBuilder(byte source, List rules, URL url) {
+            this.source = source;
+            this.rules = rules;
+            this.url = url;
+        }
+        
+        //-------------------------------------------- DocumentHandler methods
+
+        public void comment(java.lang.String text) {
+        }
+
+        public void endDocument(InputSource source) {
+        }
+
+        public void endFontFace() {
+        }
+
+        public void endMedia(SACMediaList media) {
+        }
+
+        public void endPage(String name, String pseudo_page) {
+        }
+
+        public void endSelector(SelectorList selectors) {
+            this.rules.addAll(this.currentRules);
+            this.currentRules = null;
+        }
+
+        public void ignorableAtRule(String atRule) {
+        }
+
+        public void importStyle(
+            String uri,
+            SACMediaList media,
+            String defaultNamespaceURI) {
+            
+            if (this.url == null) {
+                return;
+            }
+
+            try {
+                Parser parser = createParser();
+                URL importUrl = new URL(this.url, uri);
+                StyleSheetBuilder ssBuilder = new StyleSheetBuilder(this.source, rules, importUrl);
+                parser.setDocumentHandler(ssBuilder);
+                parser.parseStyleSheet(new InputSource(importUrl.toString()));
+            } catch (CSSException e) {
+            } catch (IOException e) {
+            }
+            
+        }
+
+        public void namespaceDeclaration(String prefix, String uri) {
+        }
+
+        public void property(
+            String name,
+            LexicalUnit value,
+            boolean important) {
+
+            // Create a serializable clone of the value for storage in our
+            // stylesheet.
+            LexicalUnit val = factory.cloneLexicalUnit(value);
+            
+            if (name.equals(CSS.BORDER)) {
+                this.expandBorder(val, important);
+            } else if (name.equals(CSS.BORDER_BOTTOM)) {
+                this.expandBorder(val, CSS.BORDER_BOTTOM, important);
+            } else if (name.equals(CSS.BORDER_LEFT)) {
+                this.expandBorder(val, CSS.BORDER_LEFT, important);
+            } else if (name.equals(CSS.BORDER_RIGHT)) {
+                this.expandBorder(val, CSS.BORDER_RIGHT, important);
+            } else if (name.equals(CSS.BORDER_TOP)) {
+                this.expandBorder(val, CSS.BORDER_TOP, important);
+            } else if (name.equals(CSS.BORDER_COLOR)) {
+                this.expandBorderColor(val, important);
+            } else if (name.equals(CSS.BORDER_STYLE)) {
+                this.expandBorderStyle(val, important);
+            } else if (name.equals(CSS.BORDER_WIDTH)) {
+                this.expandBorderWidth(val, important);
+            } else if (name.equals(CSS.FONT)) {
+                this.expandFont(val, important);
+            } else if (name.equals(CSS.MARGIN)) {
+                this.expandMargin(val, important);
+            } else if (name.equals(CSS.PADDING)) {
+                this.expandPadding(val, important);
+            } else {
+                this.addDecl(name, val, important);
+            }
+        }
+
+        public void startDocument(InputSource source) {
+        }
+
+        public void startFontFace() {
+        }
+
+        public void startMedia(SACMediaList media) {
+        }
+
+        public void startPage(String name, String pseudo_page) {
+        }
+
+        public void startSelector(SelectorList selectors) {
+            this.currentRules = new ArrayList();
+            for (int i = 0; i < selectors.getLength(); i++) {
+                Selector selector = factory.cloneSelector(selectors.item(i));
+                Rule rule = new Rule(this.source, selector);
+                this.currentRules.add(rule);
+            }
+        }
+
+        //----------------------------------------- DocumentHandler methods end
+
+        //======================================================= PRIVATE
+
+        /**
+         * Adds a PropertyDecl to the current set of rules.
+         */
+        private void addDecl(String name, LexicalUnit value, boolean important) {
+            Iterator iter = this.currentRules.iterator();
+            while (iter.hasNext()) {
+                Rule rule = (Rule) iter.next();
+                rule.add(new PropertyDecl(rule, name, value, important));
+            }
+        }
+
+        /**
+         * Expand the "border" shorthand property.
+         */
+        private void expandBorder(LexicalUnit value, boolean important) {
+            this.expandBorder(value, CSS.BORDER_BOTTOM, important);
+            this.expandBorder(value, CSS.BORDER_LEFT, important);
+            this.expandBorder(value, CSS.BORDER_RIGHT, important);
+            this.expandBorder(value, CSS.BORDER_TOP, important);
+        }
+
+        /**
+         * Expand one of the the "border-xxx" shorthand
+         * properties. whichBorder must be one of CSS.BORDER_BOTTOM,
+         * CSS.BORDER_LEFT, CSS.BORDER_RIGHT, CSS.BORDER_TOP.
+         */
+        private void expandBorder(
+            LexicalUnit value,
+            String whichBorder,
+            boolean important) {
+
+            if (AbstractProperty.isInherit(value)) {
+                this.addDecl(whichBorder + CSS.COLOR_SUFFIX, value, important);
+                this.addDecl(whichBorder + CSS.STYLE_SUFFIX, value, important);
+                this.addDecl(whichBorder + CSS.WIDTH_SUFFIX, value, important);
+                return;
+            }
+
+            LexicalUnit[] lus = getLexicalUnitList(value);
+            int i = 0;
+            if (BorderWidthProperty.isBorderWidth(lus[i])) {
+                this.addDecl(whichBorder + CSS.WIDTH_SUFFIX, lus[i], important);
+                i++;
+            }
+
+            if (i < lus.length && BorderStyleProperty.isBorderStyle(lus[i])) {
+                this.addDecl(whichBorder + CSS.STYLE_SUFFIX, lus[i], important);
+                i++;
+            }
+
+            if (i < lus.length && ColorProperty.isColor(lus[i])) {
+                this.addDecl(whichBorder + CSS.COLOR_SUFFIX, lus[i], important);
+                i++;
+            }
+
+        }
+
+        /**
+         * Expand the "border-color" shorthand property.
+         */
+        private void expandBorderColor(LexicalUnit value, boolean important) {
+
+            if (AbstractProperty.isInherit(value)) {
+                this.addDecl(CSS.BORDER_TOP_COLOR, value, important);
+                this.addDecl(CSS.BORDER_LEFT_COLOR, value, important);
+                this.addDecl(CSS.BORDER_RIGHT_COLOR, value, important);
+                this.addDecl(CSS.BORDER_BOTTOM_COLOR, value, important);
+                return;
+            }
+
+            LexicalUnit[] lus = getLexicalUnitList(value);
+            if (lus.length >= 4) {
+                this.addDecl(CSS.BORDER_TOP_COLOR, lus[0], important);
+                this.addDecl(CSS.BORDER_RIGHT_COLOR, lus[1], important);
+                this.addDecl(CSS.BORDER_BOTTOM_COLOR, lus[2], important);
+                this.addDecl(CSS.BORDER_LEFT_COLOR, lus[3], important);
+            } else if (lus.length == 3) {
+                this.addDecl(CSS.BORDER_TOP_COLOR, lus[0], important);
+                this.addDecl(CSS.BORDER_LEFT_COLOR, lus[1], important);
+                this.addDecl(CSS.BORDER_RIGHT_COLOR, lus[1], important);
+                this.addDecl(CSS.BORDER_BOTTOM_COLOR, lus[2], important);
+            } else if (lus.length == 2) {
+                this.addDecl(CSS.BORDER_TOP_COLOR, lus[0], important);
+                this.addDecl(CSS.BORDER_LEFT_COLOR, lus[1], important);
+                this.addDecl(CSS.BORDER_RIGHT_COLOR, lus[1], important);
+                this.addDecl(CSS.BORDER_BOTTOM_COLOR, lus[0], important);
+            } else if (lus.length == 1) {
+                this.addDecl(CSS.BORDER_TOP_COLOR, lus[0], important);
+                this.addDecl(CSS.BORDER_LEFT_COLOR, lus[0], important);
+                this.addDecl(CSS.BORDER_RIGHT_COLOR, lus[0], important);
+                this.addDecl(CSS.BORDER_BOTTOM_COLOR, lus[0], important);
+            }
+        }
+
+        /**
+         * Expand the "border-style" shorthand property.
+         */
+        private void expandBorderStyle(LexicalUnit value, boolean important) {
+
+            if (AbstractProperty.isInherit(value)) {
+                this.addDecl(CSS.BORDER_TOP_STYLE, value, important);
+                this.addDecl(CSS.BORDER_LEFT_STYLE, value, important);
+                this.addDecl(CSS.BORDER_RIGHT_STYLE, value, important);
+                this.addDecl(CSS.BORDER_BOTTOM_STYLE, value, important);
+                return;
+            }
+
+            LexicalUnit[] lus = getLexicalUnitList(value);
+            if (lus.length >= 4) {
+                this.addDecl(CSS.BORDER_TOP_STYLE, lus[0], important);
+                this.addDecl(CSS.BORDER_RIGHT_STYLE, lus[1], important);
+                this.addDecl(CSS.BORDER_BOTTOM_STYLE, lus[2], important);
+                this.addDecl(CSS.BORDER_LEFT_STYLE, lus[3], important);
+            } else if (lus.length == 3) {
+                this.addDecl(CSS.BORDER_TOP_STYLE, lus[0], important);
+                this.addDecl(CSS.BORDER_LEFT_STYLE, lus[1], important);
+                this.addDecl(CSS.BORDER_RIGHT_STYLE, lus[1], important);
+                this.addDecl(CSS.BORDER_BOTTOM_STYLE, lus[2], important);
+            } else if (lus.length == 2) {
+                this.addDecl(CSS.BORDER_TOP_STYLE, lus[0], important);
+                this.addDecl(CSS.BORDER_LEFT_STYLE, lus[1], important);
+                this.addDecl(CSS.BORDER_RIGHT_STYLE, lus[1], important);
+                this.addDecl(CSS.BORDER_BOTTOM_STYLE, lus[0], important);
+            } else if (lus.length == 1) {
+                this.addDecl(CSS.BORDER_TOP_STYLE, lus[0], important);
+                this.addDecl(CSS.BORDER_LEFT_STYLE, lus[0], important);
+                this.addDecl(CSS.BORDER_RIGHT_STYLE, lus[0], important);
+                this.addDecl(CSS.BORDER_BOTTOM_STYLE, lus[0], important);
+            }
+        }
+
+        /**
+         * Expand the "border-width" shorthand property.
+         */
+        private void expandBorderWidth(LexicalUnit value, boolean important) {
+
+            if (AbstractProperty.isInherit(value)) {
+                this.addDecl(CSS.BORDER_TOP_WIDTH, value, important);
+                this.addDecl(CSS.BORDER_LEFT_WIDTH, value, important);
+                this.addDecl(CSS.BORDER_RIGHT_WIDTH, value, important);
+                this.addDecl(CSS.BORDER_BOTTOM_WIDTH, value, important);
+                return;
+            }
+
+            LexicalUnit[] lus = getLexicalUnitList(value);
+            if (lus.length >= 4) {
+                this.addDecl(CSS.BORDER_TOP_WIDTH, lus[0], important);
+                this.addDecl(CSS.BORDER_RIGHT_WIDTH, lus[1], important);
+                this.addDecl(CSS.BORDER_BOTTOM_WIDTH, lus[2], important);
+                this.addDecl(CSS.BORDER_LEFT_WIDTH, lus[3], important);
+            } else if (lus.length == 3) {
+                this.addDecl(CSS.BORDER_TOP_WIDTH, lus[0], important);
+                this.addDecl(CSS.BORDER_LEFT_WIDTH, lus[1], important);
+                this.addDecl(CSS.BORDER_RIGHT_WIDTH, lus[1], important);
+                this.addDecl(CSS.BORDER_BOTTOM_WIDTH, lus[2], important);
+            } else if (lus.length == 2) {
+                this.addDecl(CSS.BORDER_TOP_WIDTH, lus[0], important);
+                this.addDecl(CSS.BORDER_LEFT_WIDTH, lus[1], important);
+                this.addDecl(CSS.BORDER_RIGHT_WIDTH, lus[1], important);
+                this.addDecl(CSS.BORDER_BOTTOM_WIDTH, lus[0], important);
+            } else if (lus.length == 1) {
+                this.addDecl(CSS.BORDER_TOP_WIDTH, lus[0], important);
+                this.addDecl(CSS.BORDER_LEFT_WIDTH, lus[0], important);
+                this.addDecl(CSS.BORDER_RIGHT_WIDTH, lus[0], important);
+                this.addDecl(CSS.BORDER_BOTTOM_WIDTH, lus[0], important);
+            }
+        }
+
+        /**
+         * Expand the "font" shorthand property.
+         */
+        private void expandFont(LexicalUnit value, boolean important) {
+
+            if (AbstractProperty.isInherit(value)) {
+                this.addDecl(CSS.FONT_STYLE, value, important);
+                this.addDecl(CSS.FONT_VARIANT, value, important);
+                this.addDecl(CSS.FONT_WEIGHT, value, important);
+                this.addDecl(CSS.FONT_SIZE, value, important);
+                this.addDecl(CSS.FONT_FAMILY, value, important);
+                return;
+            }
+
+            LexicalUnit[] lus = getLexicalUnitList(value);
+            int n = lus.length;
+            int i = 0;
+            if (i < n && FontStyleProperty.isFontStyle(lus[i])) {
+                this.addDecl(CSS.FONT_STYLE, lus[i], important);
+                i++;
+            }
+
+            if (i < n && FontVariantProperty.isFontVariant(lus[i])) {
+                this.addDecl(CSS.FONT_VARIANT, lus[i], important);
+                i++;
+            }
+
+            if (i < n && FontWeightProperty.isFontWeight(lus[i])) {
+                this.addDecl(CSS.FONT_WEIGHT, lus[i], important);
+                i++;
+            }
+
+            if (i < n && FontSizeProperty.isFontSize(lus[i])) {
+                this.addDecl(CSS.FONT_SIZE, lus[i], important);
+                i++;
+            }
+
+            if (i < n
+                && lus[i].getLexicalUnitType()
+                    == LexicalUnit.SAC_OPERATOR_SLASH) {
+                i++; // gobble slash
+                if (i < n) {
+                    this.addDecl(CSS.LINE_HEIGHT, lus[i], important);
+                }
+                i++;
+            }
+
+            if (i < n) {
+                this.addDecl(CSS.FONT_FAMILY, lus[i], important);
+            }
+        }
+
+        /**
+         * Expand the "margin" shorthand property.
+         */
+        private void expandMargin(LexicalUnit value, boolean important) {
+
+            if (AbstractProperty.isInherit(value)) {
+                this.addDecl(CSS.MARGIN_TOP, value, important);
+                this.addDecl(CSS.MARGIN_RIGHT, value, important);
+                this.addDecl(CSS.MARGIN_BOTTOM, value, important);
+                this.addDecl(CSS.MARGIN_LEFT, value, important);
+                return;
+            }
+
+            LexicalUnit[] lus = getLexicalUnitList(value);
+            if (lus.length >= 4) {
+                this.addDecl(CSS.MARGIN_TOP, lus[0], important);
+                this.addDecl(CSS.MARGIN_RIGHT, lus[1], important);
+                this.addDecl(CSS.MARGIN_BOTTOM, lus[2], important);
+                this.addDecl(CSS.MARGIN_LEFT, lus[3], important);
+            } else if (lus.length == 3) {
+                this.addDecl(CSS.MARGIN_TOP, lus[0], important);
+                this.addDecl(CSS.MARGIN_LEFT, lus[1], important);
+                this.addDecl(CSS.MARGIN_RIGHT, lus[1], important);
+                this.addDecl(CSS.MARGIN_BOTTOM, lus[2], important);
+            } else if (lus.length == 2) {
+                this.addDecl(CSS.MARGIN_TOP, lus[0], important);
+                this.addDecl(CSS.MARGIN_LEFT, lus[1], important);
+                this.addDecl(CSS.MARGIN_RIGHT, lus[1], important);
+                this.addDecl(CSS.MARGIN_BOTTOM, lus[0], important);
+            } else if (lus.length == 1) {
+                this.addDecl(CSS.MARGIN_TOP, lus[0], important);
+                this.addDecl(CSS.MARGIN_LEFT, lus[0], important);
+                this.addDecl(CSS.MARGIN_RIGHT, lus[0], important);
+                this.addDecl(CSS.MARGIN_BOTTOM, lus[0], important);
+            }
+        }
+
+        /**
+         * Expand the "padding" shorthand property.
+         */
+        private void expandPadding(LexicalUnit value, boolean important) {
+
+            if (AbstractProperty.isInherit(value)) {
+                this.addDecl(CSS.PADDING_TOP, value, important);
+                this.addDecl(CSS.PADDING_LEFT, value, important);
+                this.addDecl(CSS.PADDING_RIGHT, value, important);
+                this.addDecl(CSS.PADDING_BOTTOM, value, important);
+                return;
+            }
+
+            LexicalUnit[] lus = getLexicalUnitList(value);
+            if (lus.length >= 4) {
+                this.addDecl(CSS.PADDING_TOP, lus[0], important);
+                this.addDecl(CSS.PADDING_RIGHT, lus[1], important);
+                this.addDecl(CSS.PADDING_BOTTOM, lus[2], important);
+                this.addDecl(CSS.PADDING_LEFT, lus[3], important);
+            } else if (lus.length == 3) {
+                this.addDecl(CSS.PADDING_TOP, lus[0], important);
+                this.addDecl(CSS.PADDING_LEFT, lus[1], important);
+                this.addDecl(CSS.PADDING_RIGHT, lus[1], important);
+                this.addDecl(CSS.PADDING_BOTTOM, lus[2], important);
+            } else if (lus.length == 2) {
+                this.addDecl(CSS.PADDING_TOP, lus[0], important);
+                this.addDecl(CSS.PADDING_LEFT, lus[1], important);
+                this.addDecl(CSS.PADDING_RIGHT, lus[1], important);
+                this.addDecl(CSS.PADDING_BOTTOM, lus[0], important);
+            } else if (lus.length == 1) {
+                this.addDecl(CSS.PADDING_TOP, lus[0], important);
+                this.addDecl(CSS.PADDING_LEFT, lus[0], important);
+                this.addDecl(CSS.PADDING_RIGHT, lus[0], important);
+                this.addDecl(CSS.PADDING_BOTTOM, lus[0], important);
+            }
+        }
+
+        /**
+         * Returns an array of <code>LexicalUnit</code> objects, the first
+         * of which is given.
+         */
+        private static LexicalUnit[] getLexicalUnitList(LexicalUnit lu) {
+            List lus = new ArrayList();
+            while (lu != null) {
+                lus.add(lu);
+                lu = lu.getNextLexicalUnit();
+            }
+            return (LexicalUnit[]) lus.toArray(new LexicalUnit[lus.size()]);
+        }
+
+    }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/Styles.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/Styles.java
new file mode 100644
index 0000000..0a2a7d5
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/Styles.java
@@ -0,0 +1,335 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *     Dave Holroyd - Implement text decoration
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.wst.xml.vex.core.internal.core.Color;
+import org.eclipse.wst.xml.vex.core.internal.core.FontSpec;
+
+
+
+/**
+ * Represents the computed style properties for a particular element.
+ */
+public class Styles {
+
+    /** Maps property name (String) => value (Object) */
+    private Map values = new HashMap();
+    
+    private List content;
+    private FontSpec font;
+
+    /**
+     * Returns the value of the given property, or null if the property
+     * does not have a value.
+     * @param propertyName
+     * @return
+     */
+    public Object get(String propertyName) {
+        return this.values.get(propertyName);
+    }
+    
+    /**
+     * Returns the value of the <code>backgroundColor</code> property.
+     */
+    public Color getBackgroundColor() {
+        return (Color) this.values.get(CSS.BACKGROUND_COLOR);
+    }
+
+    /**
+     * Returns the value of the <code>borderBottomColor</code> property.
+     */
+    public Color getBorderBottomColor() {
+        return (Color) this.values.get(CSS.BORDER_BOTTOM_COLOR);
+    }
+
+    /**
+     * Returns the value of the <code>borderBottomStyle</code> property.
+     */
+    public String getBorderBottomStyle() {
+        return (String) this.values.get(CSS.BORDER_BOTTOM_STYLE);
+    }
+
+    /**
+     * Returns the value of the <code>borderLeftColor</code> property.
+     */
+    public Color getBorderLeftColor() {
+        return (Color) this.values.get(CSS.BORDER_LEFT_COLOR);
+    }
+
+    /**
+     * Returns the value of the <code>borderLeftStyle</code> property.
+     */
+    public String getBorderLeftStyle() {
+        return (String) this.values.get(CSS.BORDER_LEFT_STYLE);
+    }
+
+    /**
+     * Returns the value of the <code>borderRightColor</code> property.
+     */
+    public Color getBorderRightColor() {
+        return (Color) this.values.get(CSS.BORDER_RIGHT_COLOR);
+    }
+
+    /**
+     * Returns the value of the <code>borderRightStyle</code> property.
+     */
+    public String getBorderRightStyle() {
+        return (String) this.values.get(CSS.BORDER_RIGHT_STYLE);
+    }
+
+    /**
+     * Returns the value of the <code>borderSpacing</code> property.
+     */
+    public BorderSpacingProperty.Value getBorderSpacing() {
+        return (BorderSpacingProperty.Value) this.values.get(CSS.BORDER_SPACING);
+    }
+
+    /**
+     * Returns the value of the <code>borderTopColor</code> property.
+     */
+    public Color getBorderTopColor() {
+        return (Color) this.values.get(CSS.BORDER_TOP_COLOR);
+    }
+
+    /**
+     * Returns the value of the <code>borderTopStyle</code> property.
+     */
+    public String getBorderTopStyle() {
+        return (String) this.values.get(CSS.BORDER_TOP_STYLE);
+    }
+
+    /**
+     * Returns the value of the <code>color</code> property.
+     */
+    public Color getColor() {
+        return (Color) this.values.get(CSS.COLOR);
+    }
+
+    /**
+     * Returns a <code>List</code> of <code>ContentPart</code> objects
+     * representing the <code>content</code> property.
+     */
+    public List getContent() {
+        return content;
+    }
+    
+    /**
+     * Returns the value of the <code>display</code> property.
+     */
+    public String getDisplay() {
+        return (String) this.values.get(CSS.DISPLAY);
+    }
+
+    /**
+     * Returns the value of the <code>font</code> property.
+     */
+    public FontSpec getFont() {
+        return font;
+    }
+
+    /**
+     * Returns the value of the <code>fontFamily</code> property.
+     */
+    public String[] getFontFamilies() {
+        return (String[]) this.values.get(CSS.FONT_FAMILY);
+    }
+
+    /**
+     * Returns the value of the <code>fontSize</code> property.
+     */
+    public float getFontSize() {
+        return ((Float) this.values.get(CSS.FONT_SIZE)).floatValue();
+    }
+
+    /**
+     * Returns the value of the <code>fontStyle</code> property.
+     */
+    public String getFontStyle() {
+        return (String) this.values.get(CSS.FONT_STYLE);
+    }
+
+    /**
+     * Returns the value of the <code>fontWeight</code> property.
+     */
+    public int getFontWeight() {
+        return ((Integer) this.values.get(CSS.FONT_WEIGHT)).intValue();
+    }
+
+    /**
+     * Returns the value of the <code>lineHeight</code> property.
+     */
+    public int getLineHeight() {
+        return ((RelativeLength) this.values.get(CSS.LINE_HEIGHT)).get(Math.round(this.getFontSize()));
+    }
+
+    /**
+     * Returns the value of the <code>listStyleType</code> property.
+     */
+    public String getListStyleType() {
+        return (String) this.values.get(CSS.LIST_STYLE_TYPE);
+    }
+
+    /**
+     * Returns the value of the <code>textAlign</code> property.
+     */
+    public String getTextAlign() {
+        return (String) this.values.get(CSS.TEXT_ALIGN);
+    }
+
+    /**
+     * Returns the value of the <code>textDecoration</code> property.
+     */
+    public String getTextDecoration() {
+        return (String) this.values.get(CSS.TEXT_DECORATION);
+    }
+
+    /**
+     * Returns the value of the <code>whiteSpace</code> property.
+     */
+    public String getWhiteSpace() {
+        return (String) this.values.get(CSS.WHITE_SPACE);
+    }
+    
+    /**
+     * Returns true if this element is block-formatted, or false if it
+     * is inline-formatted.
+     */
+    public boolean isBlock() {
+        return this.getDisplay().equals(CSS.BLOCK)
+            || this.getDisplay().equals(CSS.LIST_ITEM)
+            || this.getDisplay().equals(CSS.TABLE)
+            || this.getDisplay().equals(CSS.TABLE_CAPTION)
+            || this.getDisplay().equals(CSS.TABLE_CELL)
+            || this.getDisplay().equals(CSS.TABLE_COLUMN)
+            || this.getDisplay().equals(CSS.TABLE_COLUMN_GROUP)
+            || this.getDisplay().equals(CSS.TABLE_FOOTER_GROUP)
+            || this.getDisplay().equals(CSS.TABLE_HEADER_GROUP)
+            || this.getDisplay().equals(CSS.TABLE_ROW)
+            || this.getDisplay().equals(CSS.TABLE_ROW_GROUP);
+    }
+
+    /**
+     * Sets the value of a property in this stylesheet.
+     * @param propertyName Name of the property being set.
+     * @param value Value of the property.
+     */
+    public void put(String propertyName, Object value) {
+        this.values.put(propertyName, value);
+    }
+    
+    /**
+     * Sets the vale of the <code>content</code> property.
+     * @param content <code>List</code> of <code>ContentPart</code> objects
+     * representing the content.
+     */
+    public void setContent(List content) {
+        this.content = content;
+    }
+    
+    /**
+     * Sets the value of the <code>font</code> property.
+     * @param font new value for the <code>font</code> property.
+     */
+    public void setFont(FontSpec font) {
+        this.font = font;
+    }
+
+    /**
+     * @return the value of border-bottom-width
+     */
+    public int getBorderBottomWidth() {
+        return ((Integer) this.values.get(CSS.BORDER_BOTTOM_WIDTH)).intValue();
+    }
+
+    /**
+     * @return the value of border-left-width
+     */
+    public int getBorderLeftWidth() {
+        return ((Integer) this.values.get(CSS.BORDER_LEFT_WIDTH)).intValue();
+    }
+
+    /**
+     * @return the value of border-right-width
+     */
+    public int getBorderRightWidth() {
+        return ((Integer) this.values.get(CSS.BORDER_RIGHT_WIDTH)).intValue();
+    }
+
+    /**
+     * @return the value of border-top-width
+     */
+    public int getBorderTopWidth() {
+        return ((Integer) this.values.get(CSS.BORDER_TOP_WIDTH)).intValue();
+    }
+
+    /**
+     * @return the value of margin-bottom
+     */
+    public RelativeLength getMarginBottom() {
+        return (RelativeLength) this.values.get(CSS.MARGIN_BOTTOM);
+        //return marginBottom;
+    }
+
+    /**
+     * @return the value of margin-left
+     */
+    public RelativeLength getMarginLeft() {
+        return (RelativeLength) this.values.get(CSS.MARGIN_LEFT);
+    }
+
+    /**
+     * @return the value of margin-right
+     */
+    public RelativeLength getMarginRight() {
+        return (RelativeLength) this.values.get(CSS.MARGIN_RIGHT);
+    }
+
+    /**
+     * @return the value of margin-top
+     */
+    public RelativeLength getMarginTop() {
+        return (RelativeLength) this.values.get(CSS.MARGIN_TOP);
+    }
+
+    /**
+     * @return the value of padding-bottom
+     */
+    public RelativeLength getPaddingBottom() {
+        return (RelativeLength) this.values.get(CSS.PADDING_BOTTOM);
+    }
+
+    /**
+     * @return the value of padding-left
+     */
+    public RelativeLength getPaddingLeft() {
+        return (RelativeLength) this.values.get(CSS.PADDING_LEFT);
+    }
+
+    /**
+     * @return the value of padding-right
+     */
+    public RelativeLength getPaddingRight() {
+        return (RelativeLength) this.values.get(CSS.PADDING_RIGHT);
+    }
+
+    /**
+     * @return the value of padding-top
+     */
+    public RelativeLength getPaddingTop() {
+        return (RelativeLength) this.values.get(CSS.PADDING_TOP);
+    }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/TextAlignProperty.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/TextAlignProperty.java
new file mode 100644
index 0000000..c67d9c2
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/TextAlignProperty.java
@@ -0,0 +1,53 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import org.w3c.css.sac.LexicalUnit;
+
+/**
+ * The CSS text-align property.
+ */
+public class TextAlignProperty extends AbstractProperty {
+
+    /**
+     * Class constructor
+     */
+    public TextAlignProperty() {
+        super(CSS.TEXT_ALIGN);
+    }
+
+    public Object calculate(LexicalUnit lu, Styles parentStyles, Styles styles) {
+        if (TextAlignProperty.isTextAlign(lu)) {
+            return lu.getStringValue();
+        } else {
+            // not specified, "inherit", or some other value
+            if (parentStyles != null) {
+                return parentStyles.getTextAlign();
+            } else {
+                return CSS.LEFT;
+            }
+        }
+
+    }
+
+    //=================================================== PRIVATE
+    
+    private static boolean isTextAlign(LexicalUnit lu) {
+        if (lu == null) {
+            return false; 
+        } else if (lu.getLexicalUnitType() == LexicalUnit.SAC_IDENT) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/TextDecorationProperty.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/TextDecorationProperty.java
new file mode 100644
index 0000000..ceb2f7d
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/TextDecorationProperty.java
@@ -0,0 +1,62 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import org.w3c.css.sac.LexicalUnit;
+
+/**
+ * The CSS text-decoration property.
+ */
+public class TextDecorationProperty extends AbstractProperty {
+
+    /**
+     * Class constructor.
+     */
+    public TextDecorationProperty() {
+        super(CSS.TEXT_DECORATION);
+    }
+
+    public Object calculate(LexicalUnit lu, Styles parentStyles, Styles styles) {
+        if (isTextDecoration(lu)) {
+            return lu.getStringValue();
+        } else {
+            // not specified, "inherit", or some other value
+            if (parentStyles != null) {
+                return parentStyles.getTextDecoration();
+            } else {
+                return CSS.NONE;
+            }
+        }
+    }
+
+    //=================================================== PRIVATE
+    
+    /**
+     * Returns true if the given lexical unit represents a text decoration.
+     *
+     * @param lu LexicalUnit to check.
+     */
+    private static boolean isTextDecoration(LexicalUnit lu) {
+        if (lu == null) {
+            return false;
+        } else if (lu.getLexicalUnitType() == LexicalUnit.SAC_IDENT) {
+            String s = lu.getStringValue();
+            return s.equals(CSS.NONE)
+                || s.equals(CSS.UNDERLINE)
+                || s.equals(CSS.OVERLINE)
+                || s.equals(CSS.LINE_THROUGH)
+                || s.equals(CSS.BLINK);
+        } else {
+            return false;
+        }
+    }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/WhiteSpaceProperty.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/WhiteSpaceProperty.java
new file mode 100644
index 0000000..ef60a50
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/WhiteSpaceProperty.java
@@ -0,0 +1,63 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.css;
+
+import org.w3c.css.sac.LexicalUnit;
+
+/**
+ * The CSS white-space property.
+ */
+public class WhiteSpaceProperty extends AbstractProperty {
+
+    /**
+     * Class constructor.
+     */
+    public WhiteSpaceProperty() {
+        super(CSS.WHITE_SPACE);
+    }
+
+    /**
+     *
+     */
+
+    public Object calculate(LexicalUnit lu, Styles parentStyles, Styles styles) {
+        if (isWhiteSpace(lu)) {
+            return lu.getStringValue();
+        } else {
+            // not specified, "inherit", or some other value
+            if (parentStyles != null) {
+                return parentStyles.getWhiteSpace();
+            } else {
+                return CSS.NORMAL;
+            }
+        }
+        
+    }
+
+    /**
+     * Returns true if the given lexical unit represents a white space value.
+     *
+     * @param lu LexicalUnit to check.
+     */
+    public static boolean isWhiteSpace(LexicalUnit lu) {
+    if (lu == null) {
+        return false;
+    } else if (lu.getLexicalUnitType() == LexicalUnit.SAC_IDENT) {
+        String s = lu.getStringValue();
+        return s.equals(CSS.NORMAL)
+    	|| s.equals(CSS.PRE)
+    	|| s.equals(CSS.NOWRAP);
+    } else {
+        return false;
+    }
+    }
+    
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/package.html b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/package.html
new file mode 100644
index 0000000..44d5ee2
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/css/package.html
@@ -0,0 +1,15 @@
+<html>
+  <head>
+    <title>net.sf.vex.css</title>
+  </head>
+  <body>
+  
+    <p>Classes implementing an object model for CSS stylesheets. Style
+    sheets can be built from an input stream using a
+    <code>StyleSheetBuilder</code> and a SAC (Simple API for CSS)
+    parser. The <code>StyleSheet</code> class encapsulates the
+    style sheet data and implements the the CSS algorithms for
+    calculating computed style values.</p>
+    
+  </body>
+</html>
\ No newline at end of file
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/AbstractValidator.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/AbstractValidator.java
new file mode 100644
index 0000000..4138c4b
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/AbstractValidator.java
@@ -0,0 +1,63 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Partial implementation of the Validator interface.
+ */
+public abstract class AbstractValidator implements Validator {
+
+    
+    /**
+     * @see Validator#isValidSequence
+     */
+    public boolean isValidSequence(
+        String element,
+        String[] seq1,
+        String[] seq2,
+        String[] seq3,
+        boolean partial) {
+            
+        List list = new ArrayList();
+        for (int i = 0; i < seq1.length; i++) {
+            list.add(seq1[i]);
+        }
+        if (seq2 != null) {
+            for (int i = 0; i < seq2.length; i++) {
+                if (i == 0 && seq2[i].equals(Validator.PCDATA) && list.size() > 0
+                    && list.get(list.size() - 1).equals(Validator.PCDATA)) {
+                    // Avoid consecutive PCDATA's
+                    continue;
+                }
+                list.add(seq2[i]);
+            }
+        }
+        if (seq3 != null) {
+            for (int i = 0; i < seq3.length; i++) {
+                if (i == 0 && seq3[i].equals(Validator.PCDATA) && list.size() > 0
+                    && list.get(list.size() - 1).equals(Validator.PCDATA)) {
+                    // Avoid consecutive PCDATA's
+                    continue;
+                }
+                list.add(seq3[i]);
+            }
+        }
+
+        String[] nodes = (String[]) list.toArray(new String[list.size()]);
+        return this.isValidSequence(element, nodes, partial);    
+    }
+
+
+}
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/AttributeDefinition.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/AttributeDefinition.java
new file mode 100644
index 0000000..ca5ffe9
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/AttributeDefinition.java
@@ -0,0 +1,165 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+import java.io.ObjectStreamException;
+import java.io.Serializable;
+
+
+/**
+ * <code>AttributeDefinition</code> represents an attribute definition in a DTD.
+ */
+public class AttributeDefinition implements Comparable, Serializable {
+
+    private String name;
+    private Type type;
+    private String defaultValue;
+    private String[] values;
+    private boolean required;
+    private boolean fixed;
+
+    /**
+     * Enumeration of attribute types.
+     */
+    public static final class Type implements Serializable {
+
+        private String s;
+
+        public static final Type CDATA = new Type("CDATA");
+        public static final Type ID = new Type("ID");
+        public static final Type IDREF = new Type("IDREF");
+        public static final Type IDREFS = new Type("IDREFS");
+        public static final Type NMTOKEN = new Type("NMTOKEN");
+        public static final Type NMTOKENS = new Type("NMTOKENS");
+        public static final Type ENTITY = new Type("ENTITY");
+        public static final Type ENTITIES = new Type("ENTITIES");
+        public static final Type NOTATION = new Type("NOTATION");
+        public static final Type ENUMERATION = new Type("ENUMERATION");
+
+        private Type(String s) {
+            this.s = s;
+        }
+
+        public static Type get(String s) {
+            if (s.equals(CDATA.toString())) {
+                return CDATA;
+            } else if (s.equals(ID.toString())) {
+                return ID;
+            } else if (s.equals(IDREF.toString())) {
+                return IDREF;
+            } else if (s.equals(IDREFS.toString())) {
+                return IDREFS;
+            } else if (s.equals(NMTOKEN.toString())) {
+                return NMTOKEN;
+            } else if (s.equals(NMTOKENS.toString())) {
+                return NMTOKENS;
+            } else if (s.equals(ENTITY.toString())) {
+                return ENTITY;
+            } else if (s.equals(ENTITIES.toString())) {
+                return ENTITIES;
+            } else if (s.equals(NOTATION.toString())) {
+                return NOTATION;
+            } else if (s.equals(ENUMERATION.toString())) {
+                return ENUMERATION;
+            } else {
+                throw new IllegalArgumentException(
+                    "Attribute type '" + s + "' not recognized");
+            }
+        }
+
+        public String toString() {
+            return this.s;
+        }
+        
+        /**
+         * Serialization method, to ensure that we do not introduce new 
+         * instances.
+         */
+        private Object readResolve() throws ObjectStreamException {
+            return get(this.toString());
+        }
+    }
+
+
+    /**
+     * Class constructor.
+     */
+    public AttributeDefinition(
+        String name, 
+        Type type, 
+        String defaultValue,
+        String[] values,
+        boolean required,
+        boolean fixed) {
+            
+	this.name = name;
+	this.type = type;
+	this.defaultValue = defaultValue;
+        this.values = values;
+        this.required = required;
+        this.fixed = fixed;
+    }
+
+    /**
+     * Implements <code>Comparable.compareTo</code> to sort alphabetically
+     * by name.
+     *
+     * @param other The attribute to which this one is to be compared.
+     */
+    public int compareTo(Object other) {
+	return this.name.compareTo(((AttributeDefinition)other).name);
+    }
+
+    /**
+     * Returns the attribute's type.
+     */
+    public Type getType() {
+	return this.type;
+    }
+
+    /**
+     * Returns the default value of the attribute. 
+     */
+    public String getDefaultValue() {
+        return defaultValue;
+    }
+
+    /**
+     * Returns true if the attribute value is fixed.
+     */
+    public boolean isFixed() {
+        return fixed;
+    }
+
+    /**
+     * Returns the name of the attribute.
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Returns true if the attribute is required.
+     */
+    public boolean isRequired() {
+        return required;
+    }
+
+    /**
+     * Returns an array of acceptable values for the attribute.
+     * If null is returned, any value is acceptable for the attribute. 
+     */
+    public String[] getValues() {
+        return values;
+    }
+
+}
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/Content.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/Content.java
new file mode 100644
index 0000000..758f3e8
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/Content.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+/**
+ * Interface for classes that manage a string of characters representing
+ * the content of a document.
+ */
+public interface Content {
+
+    /**
+     * Creates a new Position object at the given initial offset.
+     *
+     * @param offset initial offset of the position
+     */
+    public Position createPosition(int offset);
+
+    /**
+     * Insert a string into the content.
+     *
+     * @param offset Offset at which to insert the string.
+     * @param s String to insert.
+     */
+    public void insertString(int offset, String s);
+
+    /**
+     * Deletes the given range of characters.
+     *
+     * @param offset Offset from which characters should be deleted.
+     * @param length Number of characters to delete.
+     */
+    public void remove(int offset, int length);
+
+    /**
+     * Gets a substring of the content.
+     *
+     * @param offset Offset at which the string begins.
+     * @param length Number of characters to return.
+     */
+    public String getString(int offset, int length);
+
+    /**
+     * Return the length of the content.
+     */
+    public int getLength();
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DFABuilder.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DFABuilder.java
new file mode 100644
index 0000000..5e4671c
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DFABuilder.java
@@ -0,0 +1,498 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+
+
+/**
+ * Tools for building a deterministic finite automaton (DFA)
+ * recognizer for regular expression-like languages.
+ */
+public class DFABuilder {
+
+    /**
+     * Node represents a node in an abstract syntax tree. The first
+     * step to creating a DFA is to build an AST using the given
+     * createXxx methods.
+     */
+    public interface Node {
+	public void accept(NodeVisitor visitor);
+	public Object clone();
+	public Set getFirstPos();
+	public Set getLastPos();
+	public boolean isNullable();
+    }
+
+    /**
+     * Create a node that represents a choice between two nodes.
+     *
+     * @param child1 first choice
+     * @param child2 second choice
+     */
+    public static Node createChoiceNode(Node child1, Node child2) {
+	return new OrNode(child1, child2);
+    }
+
+    /**
+     * Create a DFA given the root node of the syntax tree.
+     *
+     * @return Initial state of the resulting DFA.
+     * @param root Root node of the syntax tree.
+     */
+    public static DFAState createDFA(Node root) {
+
+	// Append a sentinel to indicate accepting states.
+	SymbolNode sentinelNode = new SymbolNode(Sentinel.getInstance());
+	Node fakeRoot = new CatNode(root, sentinelNode);
+
+	// map symbol node set => state in the new DFA
+	Map stateMap = new HashMap();
+
+	// symbol node sets we have considered
+	Set marked = new HashSet();
+
+	// stack of symbol node sets we have yet to consider
+	Stack unmarked = new Stack();
+
+	// calculate followPos and symbolMap
+	FollowPosBuilder fpb = new FollowPosBuilder();
+	fakeRoot.accept(fpb);
+
+	// map symbol node => set of symbol nodes that follow it
+	Map followPos = fpb.getFollowPos();
+
+	// map symbol => set of symbol nodes that represent it
+	Map symbolMap = fpb.getSymbolMap();
+
+	Set nodeSet = fakeRoot.getFirstPos();
+	DFAState startState = new DFAState();
+	if (nodeSet.contains(sentinelNode)) {
+	    startState.setAccepting(true);
+	}
+
+	stateMap.put(nodeSet, startState);
+
+	unmarked.push(nodeSet);
+
+	while (unmarked.size() > 0) {
+	    nodeSet = (Set) unmarked.pop();
+	    marked.add(nodeSet);
+	    DFAState state = (DFAState) stateMap.get(nodeSet);
+	    if (state == null) {
+		state = new DFAState();
+		stateMap.put(nodeSet, state);
+	    }
+
+	    Iterator iterSymbols = symbolMap.keySet().iterator();
+	    while (iterSymbols.hasNext()) {
+		Object symbol = iterSymbols.next();
+		Set targetSet = new HashSet();
+		Iterator iterNodes = nodeSet.iterator();
+		while (iterNodes.hasNext()) {
+		    SymbolNode node = (SymbolNode) iterNodes.next();
+		    if (node.getSymbol().equals(symbol)) {
+			targetSet.addAll((Set)followPos.get(node));
+		    }
+		}
+
+		if (!targetSet.isEmpty()) { 
+		    if (!unmarked.contains(targetSet) 
+			&& !marked.contains(targetSet)) {
+
+			unmarked.push(targetSet);
+		    }
+
+		    DFAState targetState = (DFAState) 
+			stateMap.get(targetSet);
+
+		    if (targetState == null) {
+			targetState = new DFAState();
+			if (targetSet.contains(sentinelNode)) {
+			    targetState.setAccepting(true);
+			}
+			stateMap.put(targetSet, targetState);
+		    }
+
+		    state.addTransition(symbol, targetState);
+		}
+	    }
+
+	    
+	}
+
+	return startState;
+    }
+
+    /**
+     * Create optional node.
+     *
+     * @param child Node that is optional.
+     */
+    public static Node createOptionalNode(Node child) {
+	return new OrNode(child, new NullNode());
+    }
+
+    /**
+     * Create a repeating node.
+     *
+     * @param child Node that can be repeated.
+     * @param minRepeat minimum number of times the node can be repeated.
+     */
+    public static Node createRepeatingNode(Node child, int minRepeat) {
+	Node node = new StarNode(child);
+	for (int i = 0; i < minRepeat; i++) {
+	    node = new CatNode(node, (Node) child.clone());
+	}
+	return node;
+    }
+
+    /**
+     * Creates a node representing a sequence of two other nodes.
+     *
+     * @param child1 first node in the sequence.
+     * @param child2 second node in the sequence.
+     */
+    public static Node createSequenceNode(Node child1, Node child2) {
+	return new CatNode(child1, child2);
+    }
+
+    /**
+     * Create a node for a symbol.
+     *
+     * @param symbol Symbol contained by the node.
+     */
+    public static Node createSymbolNode(Object symbol) {
+	return new SymbolNode(symbol);
+    }
+
+    //============================================================ PRIVATE
+
+    /**
+     * Implementation of node that keeps firstPos, lastPos, and nullable
+     * as instance variables. The accept method is undefined.
+     */
+    private abstract static class AbstractNode implements Node {
+	protected Set firstPos;
+	protected Set lastPos;
+	protected boolean nullable;
+
+	public abstract Object clone();
+
+	public Set getFirstPos() {
+	    return this.firstPos;
+	}
+	public Set getLastPos() {
+	    return this.lastPos;
+	}
+	public boolean isNullable() {
+	    return this.nullable;
+	}
+
+	protected Set union(Set set1, Set set2) {
+	    Set retval = new HashSet();
+	    retval.addAll(set1);
+	    retval.addAll(set2);
+	    return retval;
+	}
+    }
+
+    /**
+     * Node representing a sequence of two nodes.
+     */
+    private static class CatNode extends AbstractNode {
+	private Node leftChild;
+	private Node rightChild;
+
+	public CatNode(Node leftChild, Node rightChild) {
+	    this.leftChild = leftChild;
+	    this.rightChild = rightChild;
+
+	    if (leftChild.isNullable()) {
+		this.firstPos = union(leftChild.getFirstPos(), 
+				      rightChild.getFirstPos());
+	    } else {
+		this.firstPos = leftChild.getFirstPos();
+	    }
+
+	    if (rightChild.isNullable()) {
+		this.lastPos = union(leftChild.getLastPos(), 
+				     rightChild.getLastPos());
+	    } else {
+		this.lastPos = rightChild.getLastPos();
+	    }
+
+	    this.nullable = leftChild.isNullable() && rightChild.isNullable();
+	}
+
+	public void accept(NodeVisitor visitor) { 
+	    leftChild.accept(visitor);
+	    rightChild.accept(visitor);
+	    visitor.visitCatNode(this);
+	}
+
+	public Object clone() {
+	    return new CatNode((Node) this.leftChild.clone(),
+			       (Node) this.rightChild.clone());
+	}
+
+	public Node getLeftChild() {
+	    return this.leftChild;
+	}
+
+	public Node getRightChild() {
+	    return this.rightChild;
+	}
+    }
+
+    /**
+     * Builds the followPos function. The function is represented by a
+     * map from symbol nodes to sets of symbol nodes that can follow
+     * them.  Also generates a map of symbols to sets of symbol nodes
+     * that represent them.
+     */
+    private static class FollowPosBuilder implements NodeVisitor {
+	private Map followPos = new HashMap();
+	private Map symbolMap = new HashMap();
+
+	public Map getFollowPos() {
+	    return this.followPos;
+	}
+
+	public Map getSymbolMap() {
+	    return this.symbolMap;
+	}
+
+	public void visitCatNode(CatNode node) {
+	    Iterator iter = node.getLeftChild().getLastPos().iterator();
+	    while (iter.hasNext()) {
+		SymbolNode symbolNode = (SymbolNode) iter.next();
+		Set set = this.getFollowPos(symbolNode);
+		set.addAll(node.getRightChild().getFirstPos());
+	    }
+	}
+
+	public void visitNullNode(NullNode node) {
+	}
+
+	public void visitOrNode(OrNode node) {
+	}
+
+	public void visitStarNode(StarNode node) {
+	    Iterator iter = node.getChild().getLastPos().iterator();
+	    while (iter.hasNext()) {
+		SymbolNode symbolNode = (SymbolNode) iter.next();
+		Set set = this.getFollowPos(symbolNode);
+		set.addAll(node.getChild().getFirstPos());
+	    }
+	}
+
+	public void visitSymbolNode(SymbolNode node) {
+
+	    // Done by getFollowPos(SymbolNode)
+	    //this.followPos.put(node, new HashSet());
+
+	    // Ensure we have an entry for this symbol
+	    this.getFollowPos(node);
+
+	    Object symbol = node.getSymbol();
+	    Set symbolNodeSet = (Set) this.symbolMap.get(symbol);
+	    if (symbolNodeSet == null) {
+		symbolNodeSet = new HashSet();
+		this.symbolMap.put(symbol, symbolNodeSet);
+	    }
+	    symbolNodeSet.add(node);
+	}
+
+	private Set getFollowPos(SymbolNode node) {
+	    Set ret = (Set) this.followPos.get(node);
+	    if (ret == null) {
+		ret = new HashSet();
+		this.followPos.put(node, ret);
+	    }
+	    return ret;
+	}
+
+    }
+
+    /**
+     * Describes a visitor that can walk an AST.
+     */
+    private interface NodeVisitor {
+	public void visitCatNode(CatNode node);
+	public void visitNullNode(NullNode node);
+	public void visitOrNode(OrNode node);
+	public void visitStarNode(StarNode node);
+	public void visitSymbolNode(SymbolNode node);
+    }
+
+    /**
+     * Node representing nothing. It is used with OrNode to construct an
+     * optional entry.
+     */
+    private static class NullNode extends AbstractNode {
+	public NullNode() {
+	    this.firstPos = Collections.EMPTY_SET;
+	    this.lastPos = Collections.EMPTY_SET;
+	    this.nullable = true;
+	}
+
+	public void accept(NodeVisitor visitor) { 
+	    visitor.visitNullNode(this);
+	}
+
+	public Object clone() {
+	    return new NullNode();
+	}
+    }
+
+    /**
+     * Node representing a choice between two alternatives.
+     */
+    private static class OrNode extends AbstractNode {
+	private Node leftChild;
+	private Node rightChild;
+
+	public OrNode(Node leftChild, Node rightChild) {
+	    this.leftChild = leftChild;
+	    this.rightChild = rightChild;
+
+	    this.firstPos = union(leftChild.getFirstPos(), 
+				  rightChild.getFirstPos());
+	    this.lastPos = union(leftChild.getLastPos(),
+				 rightChild.getLastPos());
+
+	    this.nullable = leftChild.isNullable() || rightChild.isNullable();
+	}
+
+	public void accept(NodeVisitor visitor) { 
+	    leftChild.accept(visitor);
+	    rightChild.accept(visitor);
+	    visitor.visitOrNode(this);
+	}
+
+	public Object clone() {
+	    return new OrNode((Node) this.leftChild.clone(),
+					 (Node) this.rightChild.clone());
+	}
+
+	public Node getLeftChild() {
+	    return this.leftChild;
+	}
+
+	public Node getRightChild() {
+	    return this.rightChild;
+	}
+    }
+
+    /**
+     * Symbol appended to the AST to mark accepting states.
+     */
+    private static class Sentinel {
+	private static final Sentinel instance = new Sentinel();
+
+	private Sentinel() {
+	}
+
+	public static Sentinel getInstance() {
+	    return instance;
+	}
+
+	public String toString() {
+	    return "#";
+	}
+    }
+
+    /**
+     * Node representing zero or more repetitions of its child.
+     */
+    private static class StarNode extends AbstractNode {
+	private Node child;
+
+	public StarNode(Node child) {
+	    this.child = child;
+	    this.firstPos = child.getFirstPos();
+	    this.lastPos = child.getLastPos();
+	    this.nullable = true;
+	}
+	
+	public void accept(NodeVisitor visitor) { 
+	    child.accept(visitor);
+	    visitor.visitStarNode(this);
+	}
+
+	public Object clone() {
+	    return new StarNode((Node) this.child.clone());
+	}
+
+	public Node getChild() {
+	    return this.child;
+	}
+    }
+
+    /**
+     * Node representing a symbol.
+     */
+    private static class SymbolNode extends AbstractNode {
+
+	private static int pos = 1;
+	private int myPos;
+	private Object symbol;
+
+	public SymbolNode (Object symbol) {
+	    this.symbol = symbol;
+	    this.firstPos = Collections.singleton(this);
+	    this.lastPos = Collections.singleton(this);
+	    this.nullable = false;
+	    this.myPos = pos++;
+	}
+
+	public void accept(NodeVisitor visitor) { 
+	    visitor.visitSymbolNode(this);
+	}
+
+	public Object clone() {
+	    return new SymbolNode(this.symbol);
+	}
+
+	public int getMyPos() {
+	    return this.myPos;
+	}
+
+	public Object getSymbol() {
+	    return this.symbol;
+	}
+    }
+
+
+    /*
+    private static String snSetToString(Set set) {
+	StringBuffer sb = new StringBuffer();
+	sb.append("{ ");
+	Iterator i2 = set.iterator();
+	while (i2.hasNext()) {
+	    SymbolNode sn2 = (SymbolNode) i2.next();
+	    sb.append(sn2.getMyPos());
+	    sb.append(" ");
+	}
+	sb.append("}");
+	return sb.toString();
+
+    }
+    */
+}
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DFAState.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DFAState.java
new file mode 100644
index 0000000..18d8453
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DFAState.java
@@ -0,0 +1,113 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+
+/**
+ * Represents a state in a deterministic finite automaton (DFA). A DFA
+ * can be thought of as a directed graph, where each arc in the graph
+ * represents a transition on an input symbol to a new state (i.e. a
+ * node in the graph.) The DFA has a start state and one or more
+ * accepting states. The DFA represents a grammar. If a sequence of
+ * input symbols drives the DFA from the start state to one of the
+ * accepting states, the sequence is a valid sentence in the grammar
+ * represented by the DFA.
+ *
+ * <p>Within VEX, we use a DFA to validate the sequence of children of
+ * a given element. A DFA is constructed for each element declaration
+ * in the DTD.</p>
+ */
+public class DFAState implements Serializable {
+
+    private boolean accepting = false;
+    private Map transitions = new HashMap();
+
+    /**
+     * Class constructor.
+     */
+    public DFAState() {
+    }
+
+    /**
+     * Return the state obtained by traversing the given list of symbols.
+     * Returns null if the given sequence does not lead to a state in the DFA.
+     *
+     * @param sequence Sequence of symbols to use.
+     */
+    public DFAState getState(List sequence) {
+	DFAState state = this;
+	Iterator iter = sequence.iterator();
+	while (iter.hasNext()) {
+	    state = state.getNextState(iter.next());
+	    if (state == null) {
+		break;
+	    }
+	}
+	return state;
+    }
+
+    /**
+     * Adds an outgoing transition to the state.
+     *
+     * @param symbol Symbol that initiates the transition.
+     * @param target State to which the transition leads.
+     */
+    public void addTransition(Object symbol, DFAState target) {
+	this.transitions.put(symbol, target);
+    }
+
+    /**
+     * Returns the set of symbols that are valid for this state.
+     */
+    public Set getValidSymbols() {
+	return this.transitions.keySet();
+    }
+
+    /**
+     * Returns true if this is an accepting state of the DFA.
+     */
+    public boolean isAccepting() {
+	return this.accepting;
+    }
+
+    /**
+     * Returns the next state given the given input symbol, or
+     * null if there are no outgoing transitions corresponding to
+     * the given symbol.
+     *
+     * @param symbol input symbol
+     */
+    public DFAState getNextState(Object symbol) {
+	return (DFAState) this.transitions.get(symbol);
+    }
+
+    /**
+     * Sets the value of the accepting property.
+     *
+     * @param accepting true if this an accepting state of the DFA.
+     */
+    public void setAccepting(boolean accepting) {
+	this.accepting = accepting;
+    }
+
+
+
+    //========================================================= PRIVATE
+
+}
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DTDValidator.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DTDValidator.java
new file mode 100644
index 0000000..a0d4901
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DTDValidator.java
@@ -0,0 +1,301 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import com.wutka.dtd.DTD;
+import com.wutka.dtd.DTDAny;
+import com.wutka.dtd.DTDAttribute;
+import com.wutka.dtd.DTDCardinal;
+import com.wutka.dtd.DTDChoice;
+import com.wutka.dtd.DTDContainer;
+import com.wutka.dtd.DTDDecl;
+import com.wutka.dtd.DTDElement;
+import com.wutka.dtd.DTDEmpty;
+import com.wutka.dtd.DTDEnumeration;
+import com.wutka.dtd.DTDItem;
+import com.wutka.dtd.DTDMixed;
+import com.wutka.dtd.DTDName;
+import com.wutka.dtd.DTDNotationList;
+import com.wutka.dtd.DTDPCData;
+import com.wutka.dtd.DTDParser;
+import com.wutka.dtd.DTDSequence;
+
+/**
+ * A validator driven by a DTD.
+ */
+public class DTDValidator extends AbstractValidator {
+
+    // DFA representing an EMPTY element; just a single non-accepting state
+    // with no transitions.
+    private static final DFAState emptyDFA = new DFAState();
+
+    // map element names to DFAs
+    private Map elementDFAs = new HashMap();
+
+    // list of all element names plus PCDATA
+    private Set anySet;
+    
+    // map element names to arrays of AttributeDefinition objects
+    private Map attributeArrays = new HashMap();
+    
+    // map element names to maps of attribute name to attribute def
+    private Map attributeMaps = new HashMap();
+
+    /**
+     * Creates a instance of DtdValidator given a URL.
+     *
+     * @param url URL of the DTD file to use.
+     */
+    public static DTDValidator create(URL url) throws IOException {
+
+	// Compute the DFAs for each element in the DTD
+
+	DTDParser parser = new DTDParser(url);
+	DTD dtd = parser.parse();
+
+	DTDValidator validator = new DTDValidator();
+	Iterator iter = dtd.elements.values().iterator();
+	while (iter.hasNext()) {
+	    DTDElement element = (DTDElement) iter.next();
+	    DFAState dfa;
+	    if (element.getContent() instanceof DTDEmpty) {
+		dfa = emptyDFA;
+	    } else if (element.getContent() instanceof DTDAny) {
+		dfa = null;
+	    } else {
+		DFABuilder.Node node = createDFANode(element.getContent());
+		dfa = DFABuilder.createDFA(node);
+	    }
+	    validator.elementDFAs.put(element.getName(), dfa);
+
+	    Map defMap = new HashMap();
+            AttributeDefinition[] defArray = new AttributeDefinition[element.attributes.size()];
+            int i = 0;        
+            Iterator iter2 = element.attributes.values().iterator();
+            while (iter2.hasNext()) {
+                DTDAttribute attr = (DTDAttribute) iter2.next();
+                AttributeDefinition.Type type;
+                String[] values = null;
+                if (attr.getType() instanceof DTDEnumeration) {
+                    type = AttributeDefinition.Type.ENUMERATION;
+                    values = ((DTDEnumeration)attr.getType()).getItems();
+                } else if (attr.getType() instanceof DTDNotationList) {
+                    type = AttributeDefinition.Type.ENUMERATION;
+                    values = ((DTDNotationList)attr.getType()).getItems();
+                } else if (attr.getType() instanceof String) {
+                    type = AttributeDefinition.Type.get((String) attr.getType());
+                } else {
+                    throw new RuntimeException("Unrecognized attribute type for element "
+                        + element.getName() + " attribute " + attr.getName()
+                        + " type " + attr.getType().getClass().getName());
+                }
+                
+                AttributeDefinition ad = new AttributeDefinition(attr.getName(),
+                    type,
+                    attr.getDefaultValue(),
+                    values,
+                    attr.getDecl() == DTDDecl.REQUIRED,
+                    attr.getDecl() == DTDDecl.FIXED);
+
+                defMap.put(attr.getName(), ad);
+                defArray[i] = ad;
+                
+                i++;
+            }
+            validator.attributeMaps.put(element.getName(), defMap);
+            Arrays.sort(defArray);
+            validator.attributeArrays.put(element.getName(), defArray);
+	}
+
+	// Calculate anySet
+
+	validator.anySet = new HashSet();
+	validator.anySet.addAll(validator.elementDFAs.keySet());
+	validator.anySet.add(Validator.PCDATA);
+
+	return validator;
+    }
+
+    public AttributeDefinition getAttributeDefinition(String element, String attribute) {
+        Map attrMap = (Map) this.attributeMaps.get(element);
+        return attrMap == null ? null : (AttributeDefinition) attrMap.get(attribute);
+    }
+
+    public AttributeDefinition[] getAttributeDefinitions(String element) {
+        if (this.attributeArrays.containsKey(element)) {
+            return (AttributeDefinition[]) this.attributeArrays.get(element);
+        } else {
+            return new AttributeDefinition[0];
+        }
+    }
+    
+    public Set getValidRootElements() {
+        return this.elementDFAs.keySet();
+    }
+    
+    /** @see Validator#getValidItems */
+    public Set getValidItems(String element, String[] prefix, String[] suffix) {
+
+	// First, get a set of candidates. We'll later test to see if each is
+	// valid to insert here.
+
+	Set candidates = null;
+	DFAState dfa = (DFAState) elementDFAs.get(element);
+	if (dfa == null) {
+	    // Anything goes!
+	    return this.anySet;
+	}
+	    
+        DFAState target = dfa.getState(Arrays.asList(prefix));
+        if (target == null) {
+            return Collections.EMPTY_SET;
+        } else {
+            // If the last transition was due to PCDATA, adding more PCDATA
+            // is also valid
+            if (prefix.length > 0
+                && prefix[prefix.length - 1].equals(Validator.PCDATA)) {
+                candidates = new HashSet();
+                candidates.addAll(target.getValidSymbols());
+                candidates.add(Validator.PCDATA);
+            } else {
+                candidates = target.getValidSymbols();
+            }
+        }
+
+	// Now, see if each candidate can be inserted at the given
+	// offset. This second test is necessary in some simple
+	// cases. Consider a <section> with an optional <title>; if
+	// we're at the first offset of the <section> and a <title>
+	// already exists, we should not allow another <title>.
+
+	Set results = new HashSet();
+        String[] middle = new String[1];
+	for (Iterator iter = candidates.iterator(); iter.hasNext(); ) {
+	    middle[0] = (String) iter.next();
+	    if (this.isValidSequence(element, prefix, middle, suffix, true)) {
+		results.add(middle[0]);
+	    }
+	}
+
+	return Collections.unmodifiableSet(results);
+    }
+
+    /**
+     * @see Validator#isValidSequence
+     */
+    public boolean isValidSequence(
+        String element,
+        String[] nodes,
+        boolean partial) {
+            
+        DFAState dfa = (DFAState) this.elementDFAs.get(element);
+        if (dfa == null) {
+            // Unrecognized element. Give the user the benefit of the doubt.
+            return true;
+        }
+        
+        DFAState target = dfa.getState(Arrays.asList(nodes));
+
+        return target != null && (partial || target.isAccepting());
+    }
+    
+    //==================================================== PRIVATE
+
+    /**
+     * Homeys must call create()
+     */
+    private DTDValidator() {
+    }
+
+    /**
+     * Create a DFABuilder.Node corresponding to the given DTDItem.
+     */
+    private static DFABuilder.Node createDFANode(DTDItem item) {
+	DFABuilder.Node node = null;
+
+	if (item instanceof DTDName) {
+	    String name = ((DTDName) item).getValue();
+	    node = DFABuilder.createSymbolNode(name);
+
+	} else if (item instanceof DTDPCData) {
+	    node = DFABuilder.createSymbolNode(Validator.PCDATA);
+
+	} else if (item instanceof DTDChoice) {
+	    Iterator iter = ((DTDContainer)item).getItemsVec().iterator();
+	    while (iter.hasNext()) {
+		DTDItem child = (DTDItem) iter.next();
+		DFABuilder.Node newNode = createDFANode(child);
+		if (node == null) {
+		    node = newNode;
+		} else {
+		    node = DFABuilder.createChoiceNode(node, newNode);
+		}
+	    }
+
+	} else if (item instanceof DTDMixed) {
+	    Iterator iter = ((DTDContainer)item).getItemsVec().iterator();
+	    while (iter.hasNext()) {
+		DTDItem child = (DTDItem) iter.next();
+		DFABuilder.Node newNode = createDFANode(child);
+		if (node == null) {
+		    node = newNode;
+		} else {
+		    node = DFABuilder.createChoiceNode(node, newNode);
+		}
+	    }
+	    DFABuilder.Node pcdata = 
+		DFABuilder.createSymbolNode(Validator.PCDATA);
+	    node = DFABuilder.createChoiceNode(node, pcdata);
+					       
+	} else if (item instanceof DTDSequence) {
+	    Iterator iter = ((DTDContainer)item).getItemsVec().iterator();
+	    while (iter.hasNext()) {
+		DTDItem child = (DTDItem) iter.next();
+		DFABuilder.Node newNode = createDFANode(child);
+		if (node == null) {
+		    node = newNode;
+		} else {
+		    node = DFABuilder.createSequenceNode(node, newNode);
+		}
+	    }
+	} else {
+	    throw new RuntimeException("Unexpected DTDItem subclass: " +
+				       item.getClass().getName());
+	}
+
+	// Cardinality is moot if it's a null node
+	if (node == null) {
+	    return node;
+	}
+
+	if (item.cardinal == DTDCardinal.OPTIONAL) {
+	    node = DFABuilder.createOptionalNode(node);
+	} else if (item.cardinal == DTDCardinal.ZEROMANY) {
+	    node = DFABuilder.createRepeatingNode(node, 0);
+	} else if (item.cardinal == DTDCardinal.ONEMANY) {
+	    node = DFABuilder.createRepeatingNode(node, 1);
+	} 
+
+	return node;
+    }
+
+}
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/Document.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/Document.java
new file mode 100644
index 0000000..016d3b1
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/Document.java
@@ -0,0 +1,966 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.wst.xml.vex.core.internal.core.ListenerList;
+import org.eclipse.wst.xml.vex.core.internal.undo.CannotRedoException;
+import org.eclipse.wst.xml.vex.core.internal.undo.CannotUndoException;
+import org.eclipse.wst.xml.vex.core.internal.undo.IUndoableEdit;
+
+
+/**
+ * Represents an XML document.
+ */
+public class Document {
+
+    private Content content;
+    private RootElement rootElement;
+    private ListenerList listeners = new ListenerList(DocumentListener.class, DocumentEvent.class);
+    private boolean undoEnabled = true;
+    
+    private String publicID;
+    private String systemID;
+    private String encoding;
+    private Validator validator;
+
+    /**
+     * Class constructor.
+     * @param rootElement root element of the document. The
+     * document property of this RootElement is set by this
+     * constructor.
+     */
+    public Document(RootElement rootElement) {
+	this.content = new GapContent(100);
+	this.rootElement = rootElement;
+        rootElement.setDocument(this);
+	this.content.insertString(0, "\0\0");
+	rootElement.setContent(this.content, 0, 1);
+    }
+
+    /**
+     * Class constructor. This constructor is used by the document builder
+     * and assumes that the content and root element have bee properly set up.
+     * 
+     * @param content Content object used to store the document's content.
+     * @param rootElement RootElement of the document.
+     */
+    public Document(Content content, RootElement rootElement) {
+        this.content = content;
+        this.rootElement = rootElement;
+    }
+    
+    /**
+     * Adds a document listener to the list of listeners to be notified of
+     * document changes.
+     *
+     * @param listener <code>DocumentListener</code> to add.
+     */
+    public void addDocumentListener(DocumentListener listener) {
+        this.listeners.add(listener);
+    }
+
+    /**
+     * Returns true if the given document fragment can be inserted at the
+     * given offset. 
+     * 
+     * @param offset offset where the insertion is to occur
+     * @param fragment fragment to be inserted
+     */
+    public boolean canInsertFragment(int offset, DocumentFragment fragment) {
+        
+        if (this.validator == null) {
+            return true;
+        }
+        
+        Element element = this.getElementAt(offset);
+        String[] seq1 = this.getNodeNames(element.getStartOffset() + 1, offset);
+        String[] seq2 = fragment.getNodeNames();
+        String[] seq3 = this.getNodeNames(offset, element.getEndOffset());
+        return this.validator.isValidSequence(
+            element.getName(),
+            seq1, seq2, seq3, true);
+    }
+    
+    /**
+     * Returns true if text can be inserted at the
+     * given offset. 
+     * 
+     * @param offset offset where the insertion is to occur
+     */
+    public boolean canInsertText(int offset) {
+        
+        if (this.validator == null) {
+            return true;
+        }
+        
+        Element element = this.getElementAt(offset);
+        String[] seq1 = this.getNodeNames(element.getStartOffset() + 1, offset);
+        String[] seq2 = new String[] { Validator.PCDATA };
+        String[] seq3 = this.getNodeNames(offset, element.getEndOffset());
+        
+        return this.validator.isValidSequence(
+            element.getName(), seq1, seq2, seq3, true);        
+    }
+    
+    /**
+     * Creates a <code>Position</code> object at the given character offset.
+     *
+     * @param offset initial character offset of the position
+     */
+    public Position createPosition(int offset) {
+	return this.content.createPosition(offset);
+    }
+
+    /**
+     * Deletes a portion of the document. No element may straddle the
+     * deletion span.
+     *
+     * @param startOffset start of the range to delete
+     * @param endOffset end of the range to delete
+     * @throws DocumentValidationException if the change would result
+     * in an invalid document.
+     */
+    public void delete(int startOffset, int endOffset)
+	throws DocumentValidationException {
+
+        Element e1 = this.getElementAt(startOffset);
+        Element e2 = this.getElementAt(endOffset);
+        if (e1 != e2) {
+            throw new IllegalArgumentException("Deletion from " + startOffset + " to " + endOffset + " is unbalanced");
+        }
+
+	Validator validator = this.getValidator();
+	if (validator != null) {
+            String[] seq1 = this.getNodeNames(e1.getStartOffset() + 1, startOffset);
+            String[] seq2 = this.getNodeNames(endOffset, e1.getEndOffset());
+	    if (!validator.isValidSequence(
+                    e1.getName(), seq1, seq2, null, true)) {
+		throw new DocumentValidationException("Unable to delete from " + startOffset + " to " + endOffset);
+	    }
+	}
+    
+        // Grab the fragment for the undoable edit while it's still here
+        DocumentFragment frag = getFragment(startOffset, endOffset);
+
+        this.fireBeforeContentDeleted( 
+            new DocumentEvent(this, e1, startOffset, endOffset - startOffset, null));
+
+	Iterator iter = e1.getChildIterator();
+	while (iter.hasNext()) {
+	    Element child = (Element) iter.next();
+	    if (startOffset <= child.getStartOffset() &&
+		child.getEndOffset() < endOffset) {
+		iter.remove();
+	    }
+	}
+
+	this.content.remove(startOffset, endOffset - startOffset);
+
+        IUndoableEdit edit = this.undoEnabled ? 
+                new DeleteEdit(startOffset, endOffset, frag) : null;
+    
+        this.fireContentDeleted( 
+            new DocumentEvent(this, e1, startOffset, endOffset - startOffset, edit));
+    }
+
+    /**
+     * Finds the lowest element that contains both of the given offsets.
+     *
+     * @param offset1 the first offset
+     * @param offset2 the second offset
+     */
+    public Element findCommonElement(int offset1, int offset2) {
+	Element element = this.rootElement;
+	for (;;) {
+	    boolean tryAgain = false;
+	    Element[] children = element.getChildElements();
+	    for (int i = 0; i < children.length; i++) {
+		if (offset1 > children[i].getStartOffset() &&
+		    offset2 > children[i].getStartOffset() &&
+		    offset1 <= children[i].getEndOffset() &&
+		    offset2 <= children[i].getEndOffset()) {
+		    
+		    element = children[i];
+		    tryAgain = true;
+		    break;
+		}
+	    }
+	    if (!tryAgain) {
+		break;
+	    }
+	}
+	return element;
+    }
+
+    /**
+     * Returns the character at the given offset.
+     */
+    public char getCharacterAt(int offset) {
+	return this.content.getString(offset, 1).charAt(0);
+    }
+
+    /**
+     * Returns the element at the given offset. The given offset must be
+     * greater or equal to 1 and less than the current document length.
+     */
+    public Element getElementAt(int offset) {
+	if (offset < 1 || offset >= this.getLength()) {
+	    throw new IllegalArgumentException("Illegal offset: " + offset + ". Must be between 1 and n-1");
+	}
+	Element element = this.rootElement;
+	for (;;) {
+	    boolean tryAgain = false;
+	    Element[] children = element.getChildElements();
+	    for (int i = 0; i < children.length; i++) {
+		Element child = children[i];
+		if (offset <= child.getStartOffset()) {
+		    return element;
+		} else if (offset <= child.getEndOffset()) {
+		    element = child;
+		    tryAgain = true;
+		    break;
+		}
+	    }
+	    if (!tryAgain) {
+		break;
+	    }
+	}
+	return element;
+    }
+
+    /**
+     * Returns the encoding used for this document, or null if no
+     * encoding has been declared.
+     */
+    public String getEncoding() {
+	return this.encoding;
+    }
+
+    /**
+     * Create a <code>DocumentFragment</code> representing the given
+     * range of offsets.
+     * 
+     * @return
+     */
+    public DocumentFragment getFragment(int startOffset, int endOffset) {
+    
+        assertOffset(startOffset, 0, this.content.getLength());
+        assertOffset(endOffset, 0, this.content.getLength());
+            
+        if (endOffset <= startOffset) {
+            throw new IllegalArgumentException(
+                "Invalid range (" + startOffset + ", " + endOffset + ")");
+        }
+        
+        Element e1 = this.getElementAt(startOffset);
+        Element e2 = this.getElementAt(endOffset);
+        if (e1 != e2) {
+            throw new IllegalArgumentException(
+                "Fragment from "
+                    + startOffset
+                    + " to "
+                    + endOffset
+                    + " is unbalanced");
+        }
+
+        Element[] children = e1.getChildElements();
+        
+        Content newContent = new GapContent(endOffset - startOffset);
+        String s = this.content.getString(startOffset, endOffset - startOffset);
+        newContent.insertString(0, s);
+        List newChildren = new ArrayList();
+        for (int i = 0; i < children.length; i++) {
+            Element child = children[i];
+            if (child.getEndOffset() <= startOffset) {
+                continue;
+            } else if (child.getStartOffset() >= endOffset) {
+                break;
+            } else {
+                newChildren.add(
+                    this.cloneElement(child, newContent, -startOffset, null));
+            }
+        }
+        
+        Element[] elementArray =
+            (Element[]) newChildren.toArray(new Element[newChildren.size()]);
+        return new DocumentFragment(newContent, elementArray);
+    }
+    
+    /**
+     * Returns the length of the document in characters, including the null
+     * characters that delimit each element.
+     */
+    public int getLength() {
+	return this.content.getLength();
+    }
+
+    /**
+     * Returns an array of element names and Validator.PCDATA representing
+     * the content between the given offsets. The given offsets must both
+     * be directly in the same element.
+     * 
+     * @param startOffset the offset at which the sequence begins
+     * @param endOffset the offset at which the sequence ends
+     */
+    public String[] getNodeNames(int startOffset, int endOffset) {
+
+        Node[] nodes = this.getNodes(startOffset, endOffset);
+        String[] names = new String[nodes.length];
+        
+        for (int i = 0; i < nodes.length; i++) {
+            Node node = nodes[i];
+            if (node instanceof Element) {
+                names[i] = ((Element)node).getName();
+            } else {
+                names[i] = Validator.PCDATA;
+            }
+        }
+
+        return names;
+    }
+    
+    /**
+     * Returns an array of Nodes representing the selected range. The given offsets must both
+     * be directly in the same element.
+     * 
+     * @param startOffset the offset at which the sequence begins
+     * @param endOffset the offset at which the sequence ends
+     */
+    public Node[] getNodes(int startOffset, int endOffset) {
+
+        Element element = this.getElementAt(startOffset);
+        if (element != this.getElementAt(endOffset)) {
+            throw new IllegalArgumentException(
+                "Offsets are unbalanced: " + 
+                startOffset + " is in " + element.getName() + ", " + 
+                endOffset + " is in " + this.getElementAt(endOffset).getName());
+        }
+
+        List list = new ArrayList();
+        Node[] nodes = element.getChildNodes();
+        for (int i = 0; i < nodes.length; i++) {
+            Node node = nodes[i];
+            if (node.getEndOffset() <= startOffset) {
+                continue;
+            } else if (node.getStartOffset() >= endOffset) {
+                break;
+            } else {
+                if (node instanceof Element) {
+                    list.add(node);
+                } else {
+                    Text text = (Text) node;
+                    if (text.getStartOffset() < startOffset) {
+                        text.setContent(text.getContent(), startOffset, text.getEndOffset());
+                    } else if (text.getEndOffset() > endOffset) {
+                        text.setContent(text.getContent(), text.getStartOffset(), endOffset);
+                    }
+                    list.add(text);
+                }
+            }
+        }
+
+        return (Node[]) list.toArray(new Node[list.size()]);
+    }
+    
+    /**
+     * Creates an array of nodes for a given run of content. The returned array includes the given child
+     * elements and <code>Text</code> objects where text appears between elements.
+     * 
+     * @param content Content object containing the content
+     * @param startOffset start offset of the run
+     * @param endOffset end offset of the run
+     * @param elements child elements that are within the run
+     */
+    static Node[] createNodeArray(Content content, int startOffset, int endOffset, Element[] elements) {
+
+        List nodes = new ArrayList();
+        int offset = startOffset;
+        for (int i = 0; i < elements.length; i++) {
+            int start = elements[i].getStartOffset();
+            if (offset < start) {
+                nodes.add(new Text(content, offset, start));
+            }
+            nodes.add(elements[i]);
+            offset = elements[i].getEndOffset() + 1;
+        }
+
+        if (offset < endOffset) {
+            nodes.add(new Text(content, offset, endOffset));
+        }
+
+        return (Node[]) nodes.toArray(new Node[nodes.size()]);
+    }
+
+    /**
+     * Returns the public ID of the document type.
+     */
+    public String getPublicID() {
+	return this.publicID;
+    }
+
+    /**
+     * Returns the text between the two given offsets. Unlike getText,
+     * sentinel characters are not removed.
+     *
+     * @param startOffset character offset of the start of the text
+     * @param endOffset character offset of the end of the text
+     */
+    public String getRawText(int startOffset, int endOffset) {
+	return this.content.getString(startOffset, 
+				      endOffset - startOffset);
+    }
+
+    /**
+     * Returns the root element of this document.
+     */
+    public Element getRootElement() {
+        return this.rootElement;
+    }
+
+    /**
+     * Returns the system ID of the document type.
+     */
+    public String getSystemID() {
+	return this.systemID;
+    }
+
+    /**
+     * Returns the text between the two given offsets. Sentinal characters
+     * are removed.
+     *
+     * @param startOffset character offset of the start of the text
+     * @param endOffset character offset of the end of the text
+     */
+    public String getText(int startOffset, int endOffset) {
+	String raw = this.content.getString(startOffset, 
+					    endOffset - startOffset);
+	StringBuffer sb = new StringBuffer(raw.length());
+	for (int i = 0; i < raw.length(); i++) {
+	    char c = raw.charAt(i);
+	    if (c != '\0') {
+		sb.append(c);
+	    }
+	}
+	return sb.toString();
+    }
+
+    /**
+     * Returns the validator used to validate the document, or null if
+     * a validator has not been set. Note that the DocumentFactory
+     * does not automatically create a validator.
+     */
+    public Validator getValidator() {
+        return this.validator;
+    }
+
+    /**
+     * Inserts an element at the given position.
+     *
+     * @param offset character offset at which the element is to be inserted.
+     * Must be greater or equal to 1 and less than the current length of the
+     * document, i.e. it must be within the range of the root element.
+     * @param element element to insert
+     * @throws DocumentValidationException if the change would result
+     * in an invalid document.
+     */
+    public void insertElement(int offset, Element element) 
+	throws DocumentValidationException {
+
+	if (offset < 1 || offset >= this.getLength()) {
+	    throw new IllegalArgumentException("Error inserting element <" + element.getName() + ">: offset is " + offset + ", but it must be between 1 and " + (this.getLength() - 1));
+	}
+
+	Validator validator = this.getValidator();
+	if (validator != null) {
+            Element parent = this.getElementAt(offset);
+            String[] seq1 = this.getNodeNames(parent.getStartOffset() + 1, offset);
+            String[] seq2 = new String[] { element.getName() };
+            String[] seq3 = this.getNodeNames(offset, parent.getEndOffset());
+            if (!validator.isValidSequence(
+                    parent.getName(), seq1, seq2, seq3, true)) {
+		throw new DocumentValidationException("Cannot insert element " + element.getName() + " at offset " + offset);
+	    }		
+	}
+
+	// find the parent, and the index into its children at which
+	// this element should be inserted
+	Element parent = this.rootElement;
+	int childIndex = -1;
+	while (childIndex == -1) {
+	    boolean tryAgain = false;
+	    Element[] children = parent.getChildElements();
+	    for (int i = 0; i < children.length; i++) {
+		Element child = children[i];
+		if (offset <= child.getStartOffset()) {
+		    childIndex = i;
+		    break;
+		} else if (offset <= child.getEndOffset()) {
+		    parent = child;
+		    tryAgain = true;
+		    break;
+		}
+	    }
+	    if (!tryAgain && childIndex == -1) {
+		childIndex = children.length;
+		break;
+	    }
+	}
+
+        this.fireBeforeContentInserted(new DocumentEvent(this, parent, offset, 2, null));
+        
+	this.content.insertString(offset, "\0\0");
+
+	element.setContent(this.content, offset, offset + 1);
+	element.setParent(parent);
+	parent.insertChild(childIndex, element);
+
+        IUndoableEdit edit = this.undoEnabled ? 
+                new InsertElementEdit(offset, element) : null;
+    
+	this.fireContentInserted(new DocumentEvent(this, parent, offset, 2, edit));
+    }
+
+
+    /**
+     * Inserts a document fragment at the given position.
+     *
+     * @param offset character offset at which the element is to be inserted.
+     * Must be greater or equal to 1 and less than the current length of the
+     * document, i.e. it must be within the range of the root element.
+     * @param fragment fragment to insert
+     * @throws DocumentValidationException if the change would result
+     * in an invalid document.
+     */
+    public void insertFragment(int offset, DocumentFragment fragment)
+        throws DocumentValidationException {
+         
+        if (offset < 1 || offset >= this.getLength()) {
+            throw new IllegalArgumentException("Error inserting document fragment");
+        }
+
+        Element parent = this.getElementAt(offset);
+
+        if (this.validator != null) {
+            String[] seq1 = this.getNodeNames(parent.getStartOffset() + 1, offset);
+            String[] seq2 = fragment.getNodeNames();
+            String[] seq3 = this.getNodeNames(offset, parent.getEndOffset());
+            if (!validator.isValidSequence(
+                    parent.getName(), seq1, seq2, seq3, true)) {
+             
+                throw new DocumentValidationException("Cannot insert document fragment");
+            }
+        }
+
+        this.fireBeforeContentInserted(new DocumentEvent(this, parent, offset, 2, null));
+        
+        Content c = fragment.getContent();
+        String s = c.getString(0, c.getLength());
+        this.content.insertString(offset, s);
+        
+        Element[] children = parent.getChildElements();
+        int index = 0;
+        while (index < children.length 
+            && children[index].getEndOffset() < offset) {
+            index++;
+        }
+        
+        Element[] elements = fragment.getElements();
+        for (int i = 0; i < elements.length; i++) {
+            Element newElement = this.cloneElement(elements[i], this.content, offset, parent);
+            parent.insertChild(index, newElement);
+            index++;
+        }
+        
+        IUndoableEdit edit = this.undoEnabled ?
+                new InsertFragmentEdit(offset, fragment) : null;
+                
+        this.fireContentInserted(
+            new DocumentEvent(this, parent, offset, fragment.getContent().getLength(), edit));
+    }
+    
+    /**
+     * Inserts text at the given position.
+     *
+     * @param offset character offset at which the text is to be inserted.
+     * Must be greater or equal to 1 and less than the current length of the
+     * document, i.e. it must be within the range of the root element.
+     * @param text text to insert
+     * @return UndoableEdit that can be used to undo the deletion
+     * @throws DocumentValidationException if the change would result
+     * in an invalid document.
+     */
+    public void insertText(int offset, String text)
+	throws DocumentValidationException {
+
+	if (offset < 1 || offset >= this.getLength()) {
+	    throw new IllegalArgumentException("Offset must be between 1 and n-1");
+	}
+
+        Element parent = this.getElementAt(offset);
+
+	boolean isValid = false;
+	if (this.getCharacterAt(offset-1) != '\0') {
+	    isValid = true;
+	} else if (this.getCharacterAt(offset) != '\0') {
+	    isValid = true;
+	} else {
+	    Validator validator = this.getValidator();
+	    if (validator != null) {
+                String[] seq1 = this.getNodeNames(parent.getStartOffset() + 1, offset);
+                String[] seq2 = new String[] { Validator.PCDATA };
+                String[] seq3 = this.getNodeNames(offset, parent.getEndOffset());
+                isValid = validator.isValidSequence(
+                    parent.getName(), seq1, seq2, seq3, true);
+	    } else {
+		isValid = true;
+	    }
+	}
+
+	if (!isValid) {
+	    throw new DocumentValidationException("Cannot insert text '" + text + "' at offset " + offset);
+	}
+    
+	// Convert control chars to spaces
+	StringBuffer sb = new StringBuffer(text);
+	for (int i = 0; i < sb.length(); i++) {
+	    if (Character.isISOControl(sb.charAt(i)) && sb.charAt(i) != '\n') {
+	        sb.setCharAt(i, ' ');
+	    }
+	}
+    
+	String s = sb.toString();
+
+        this.fireBeforeContentInserted(new DocumentEvent(this, parent, offset, 2, null));
+        
+	this.content.insertString(offset, s);
+
+	IUndoableEdit edit = this.undoEnabled ?
+            new InsertTextEdit(offset, s) : null;
+            
+	this.fireContentInserted(
+            new DocumentEvent(this, parent, offset, s.length(), edit));
+    }
+
+    /**
+     * Returns true if undo is enabled, that is, undoable edit events are fired
+     * to registered listeners.
+     */
+    public boolean isUndoEnabled() {
+        return this.undoEnabled;
+    }
+    
+    /**
+     * Removes a document listener from the list of listeners so that
+     * it is no longer notified of document changes.
+     *
+     * @param listener <code>DocumentListener</code> to remove.
+     */
+    public void removeDocumentListener(DocumentListener listener) {
+        this.listeners.remove(listener);
+    }
+
+    /**
+     * Sets the public ID for the document's document type.
+     *
+     * @param publicID New value for the public ID.
+     */
+    public void setPublicID(String publicID) {
+	this.publicID = publicID;
+    }
+
+    /**
+     * Sets the system ID for the document's document type.
+     *
+     * @param systemID New value for the system ID.
+     */
+    public void setSystemID(String systemID) {
+	this.systemID = systemID;
+    }
+
+    /**
+     * Sets whether undo events are enabled. Typically, undo events are
+     * disabled while an edit is being undone or redone.
+     * 
+     * @param undoEnabled If true, undoable edit events are fired to 
+     * registered listeners.
+     */
+    public void setUndoEnabled(boolean undoEnabled) {
+        this.undoEnabled = undoEnabled;
+    }
+    
+    /**
+     * Sets the validator to use for this document.
+     *
+     * @param validator Validator to use for this document.
+     */
+    public void setValidator(Validator validator) {
+	this.validator = validator;
+    }
+
+    //==================================================== PRIVATE
+    
+    /**
+     * Represents a deletion from a document that can be undone
+     * and redone.
+     */
+    private class DeleteEdit implements IUndoableEdit {
+
+        private int startOffset;
+        private int endOffset;
+        private DocumentFragment frag;
+    
+        public DeleteEdit(int startOffset, int endOffset, DocumentFragment frag) {
+            this.startOffset = startOffset;
+            this.endOffset = endOffset;
+            this.frag = frag; 
+        }
+        
+        public boolean combine(IUndoableEdit edit) {
+            return false;
+        }
+
+        public void undo() throws CannotUndoException {
+            try {
+                setUndoEnabled(false);
+                insertFragment(this.startOffset, this.frag);
+            } catch (DocumentValidationException ex) {
+                throw new CannotUndoException();
+            } finally {
+                setUndoEnabled(true);
+            }
+        }
+
+        public void redo() throws CannotRedoException {
+            try {
+                setUndoEnabled(false);
+                delete(this.startOffset, this.endOffset);
+            } catch (DocumentValidationException ex) {
+                throw new CannotUndoException();
+            } finally {
+                setUndoEnabled(true);
+            }
+        }
+
+    }
+
+
+    /**
+     * Represents an insertion of an element into the document.
+     */
+    private class InsertElementEdit implements IUndoableEdit {
+
+        private int offset;
+        private Element element;
+    
+        public InsertElementEdit(int offset, Element element) {
+            this.offset = offset;
+            this.element = element;
+        }
+
+        public boolean combine(IUndoableEdit edit) {
+            return false;
+        }
+
+        public void undo() throws CannotUndoException {
+            try {
+                setUndoEnabled(false);
+                delete(this.offset, this.offset + 2);
+            } catch (DocumentValidationException ex) {
+                throw new CannotUndoException();
+            } finally {
+                setUndoEnabled(true);
+            }
+        }
+
+        public void redo() throws CannotRedoException {
+            try {
+                setUndoEnabled(false);
+                insertElement(this.offset, this.element);
+            } catch (DocumentValidationException ex) {
+                throw new CannotUndoException();
+            } finally {
+                setUndoEnabled(true);
+            }
+        }
+
+    }
+    
+     
+    /**
+     * Represents an insertion of a fragment into the document.
+     */
+    private class InsertFragmentEdit implements IUndoableEdit {
+
+        private int offset;
+        private DocumentFragment frag;
+    
+        public InsertFragmentEdit(int offset, DocumentFragment frag) {
+            this.offset = offset;
+            this.frag = frag;
+        }
+
+        public boolean combine(IUndoableEdit edit) {
+            return false;
+        }
+
+        public void undo() throws CannotUndoException {
+            try {
+                setUndoEnabled(false);
+                int length = this.frag.getContent().getLength();
+                delete(this.offset, this.offset + length);
+            } catch (DocumentValidationException ex) {
+                throw new CannotUndoException();
+            } finally {
+                setUndoEnabled(true);
+            }
+        }
+
+        public void redo() throws CannotRedoException {
+            try {
+                setUndoEnabled(false);
+                insertFragment(this.offset, this.frag);
+            } catch (DocumentValidationException ex) {
+                throw new CannotUndoException();
+            } finally {
+                setUndoEnabled(true);
+            }
+        }
+
+    }
+    
+   
+    /**
+     * Represents an insertion of text into the document.
+     */
+    private class InsertTextEdit implements IUndoableEdit {
+
+        private int offset;
+        private String text;
+    
+        public InsertTextEdit(int offset, String text) {
+            this.offset = offset;
+            this.text = text;
+        }
+
+        public boolean combine(IUndoableEdit edit) {
+            if (edit instanceof InsertTextEdit) {
+                InsertTextEdit ite = (InsertTextEdit) edit;
+                if (ite.offset == this.offset + this.text.length()) {
+                    this.text = this.text + ite.text;
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        public void undo() throws CannotUndoException {
+            try {
+                setUndoEnabled(false);
+                delete(this.offset, this.offset + this.text.length());
+            } catch (DocumentValidationException ex) {
+                throw new CannotUndoException();
+            } finally {
+                setUndoEnabled(true);
+            }
+        }
+
+        public void redo() throws CannotRedoException {
+            try {
+                setUndoEnabled(false);
+                insertText(this.offset, this.text);
+            } catch (DocumentValidationException ex) {
+                throw new CannotUndoException();
+            } finally {
+                setUndoEnabled(true);
+            }
+        }
+
+    }
+    
+    /**
+     * Assert that the given offset is within the given range,
+     * throwing IllegalArgumentException if not.
+     */
+    private static void assertOffset(int offset, int min, int max) {
+        if (offset < min || offset > max) {
+            throw new IllegalArgumentException("Bad offset " + offset +
+                               "must be between " + min +
+                               " and " + max);
+        }   
+    }
+
+
+
+    /**
+     * Clone an element tree, pointing to a new Content object.
+     * 
+     * @param original Element to be cloned
+     * @param content new Content object to which the clone will point
+     * @param shift amount to shift offsets to be valid in the new Content.
+     * @param parent parent for the cloned Element
+     */
+    private Element cloneElement(
+        Element original,
+        Content content,
+        int shift,
+        Element parent) {
+            
+        Element clone = new Element(original.getName());
+        clone.setContent(
+            content,
+            original.getStartOffset() + shift,
+            original.getEndOffset() + shift);
+        String[] attrNames = original.getAttributeNames();
+        for (int i = 0; i < attrNames.length; i++) {
+            try {
+                clone.setAttribute(attrNames[i], original.getAttribute(attrNames[i]));
+            } catch (DocumentValidationException ex) {
+                throw new RuntimeException("Unexpected exception: " + ex);
+            }
+        }
+        clone.setParent(parent);
+
+        Element[] children = original.getChildElements();
+        for (int i = 0; i < children.length; i++) {
+            Element cloneChild =
+                this.cloneElement(children[i], content, shift, clone);
+            clone.insertChild(i, cloneChild);
+        }
+
+        return clone;
+    }
+    
+    void fireAttributeChanged(DocumentEvent e) {
+        this.listeners.fireEvent("attributeChanged", e);
+    }
+    
+    private void fireBeforeContentDeleted(DocumentEvent e) {
+        this.listeners.fireEvent("beforeContentDeleted", e);
+    }
+
+    private void fireBeforeContentInserted(DocumentEvent e) {
+        this.listeners.fireEvent("beforeContentInserted", e);
+    }
+
+    private void fireContentDeleted(DocumentEvent e) {
+        this.listeners.fireEvent("contentDeleted", e);
+    }
+
+    private void fireContentInserted(DocumentEvent e) {
+        this.listeners.fireEvent("contentInserted", e);
+    }
+
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentBuilder.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentBuilder.java
new file mode 100644
index 0000000..d222949
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentBuilder.java
@@ -0,0 +1,325 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+import java.util.LinkedList;
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.ext.LexicalHandler;
+
+/**
+ * A SAX handler that builds a Vex document. This builder collapses whitespace
+ * as it goes, according to the following rules.
+ * 
+ * <ul>
+ *   <li>Elements with style white-space: pre are left alone.</li>
+ *   <li>Runs of whitespace are replaced with a single space.</li>
+ *   <li>Space just inside the start and end of elements is removed.</li>
+ *   <li>Space just outside the start and end of block-formatted elements
+ *       is removed.</li>
+ * </ul>
+ */
+public class DocumentBuilder implements ContentHandler, LexicalHandler {
+
+    /**
+     * Class constructor.
+     * @param policyFactory Used to determine the WhitespacePolicy to use
+     * for a given document type.
+     */
+    public DocumentBuilder(IWhitespacePolicyFactory policyFactory) {
+        this.policyFactory = policyFactory;
+    }
+    
+    /**
+     * Returns the newly built <code>Document</code> object.
+     */
+    public Document getDocument() {
+        return this.doc;
+    }
+
+    //============================================= ContentHandler methods
+
+    public void characters(char[] ch, int start, int length) 
+            throws SAXException {
+
+        // Convert nuls to spaces, since we use nulls for element delimiters
+        char[] chars = new char[length];
+        System.arraycopy(ch, start, chars, 0, length);
+        for (int i = 0; i < chars.length; i++) {
+            if (Character.isISOControl(chars[i]) && chars[i] != '\n' && chars[i] != '\r') {
+                chars[i] = ' ';
+            }
+        }
+        this.pendingChars.append(chars);
+    }
+
+    public void endDocument() {
+        this.doc = new Document(this.content, this.rootElement);
+        this.doc.setPublicID(this.dtdPublicID);
+        this.doc.setSystemID(this.dtdSystemID);
+        this.rootElement.setDocument(this.doc);
+    }
+
+    public void endElement(String namespaceURI, 
+               String localName, 
+               String qName) {
+        
+        this.appendChars(true);
+        
+        StackEntry entry = (StackEntry) this.stack.removeLast();
+        
+        // we must insert the trailing sentinel first, else the insertion
+        // pushes the end position of the element to after the sentinel
+        this.content.insertString(content.getLength(), "\0");
+        entry.element.setContent(this.content, entry.offset, content.getLength() - 1);
+
+        if (this.isBlock(entry.element)) {
+            this.trimLeading = true;
+        }
+    }
+
+    public void endPrefixMapping(java.lang.String prefix) {
+    }
+
+    public void ignorableWhitespace(char[] ch, int start, int length) {
+    }
+
+    public void processingInstruction(String target, String data) {
+    }
+
+    public void setDocumentLocator(Locator locator) {
+        this.locator = locator;
+    }
+
+    public void skippedEntity(java.lang.String name) {
+    }
+
+    public void startDocument() {
+    }
+
+    public void startElement(String namespaceURI, 
+                 String localName, 
+                 String qName, 
+                 Attributes attrs) 
+
+    throws SAXException {
+
+        try {
+            Element element;
+            if (stack.size() == 0) {
+                this.rootElement = new RootElement(qName);
+                element = this.rootElement;
+                if (this.policyFactory != null) {
+                    this.policy = this.policyFactory.getPolicy(this.dtdPublicID);
+                }
+            } else {
+                element = new Element(qName);
+                Element parent = ((StackEntry) stack.getLast()).element;
+                parent.addChild(element);
+            }
+            
+            int n = attrs.getLength();
+            for (int i = 0; i < n; i++) {
+                element.setAttribute(attrs.getQName(i), attrs.getValue(i));
+            }
+            
+            this.appendChars(this.isBlock(element));
+            
+            stack.add(new StackEntry(element, content.getLength(), this.isPre(element)));
+            content.insertString(content.getLength(), "\0");
+            
+            this.trimLeading = true;
+            
+        } catch (DocumentValidationException ex) {
+            throw new SAXParseException("DocumentValidationException",
+                    this.locator, ex);
+        }
+        
+    }
+
+    public void startPrefixMapping(String prefix, String uri) {
+    }
+
+    //============================================== LexicalHandler methods 
+
+    public void comment(char[] ch, int start, int length) {
+    }
+
+    public void endCDATA() {
+    }
+
+    public void endDTD() {
+    }
+
+    public void endEntity(String name) {
+    }
+
+    public void startCDATA() {
+    }
+
+    public void startDTD(String name, String publicId, String systemId) {
+        this.dtdPublicID = publicId;
+        this.dtdSystemID = systemId;
+    }
+
+    public void startEntity(java.lang.String name) {
+    }
+    
+    //======================================================== PRIVATE
+
+    private IWhitespacePolicyFactory policyFactory;
+    private IWhitespacePolicy policy;
+    
+    // Holds pending characters until we see another element boundary.
+    // This is (a) so we can collapse spaces in multiple adjacent character
+    // blocks, and (b) so we can trim trailing whitespace, if necessary.
+    private StringBuffer pendingChars = new StringBuffer();
+    
+    // If true, trim the leading whitespace from the next received block of
+    // text.
+    private boolean trimLeading = false;
+
+    // Content object to hold document content
+    private Content content = new GapContent(100);
+    
+    // Stack of StackElement objects
+    private LinkedList stack = new LinkedList();
+    
+    private RootElement rootElement;
+
+    private String dtdPublicID;
+    private String dtdSystemID;
+    private Document doc;
+    private Locator locator;
+
+    // Append any pending characters to the content
+    private void appendChars(boolean trimTrailing) {
+        
+        StringBuffer sb;
+        
+        StackEntry entry = this.stack.size() > 0 ? (StackEntry) this.stack.getLast() : null;
+        
+        if (entry != null && entry.pre) {
+            
+            sb = this.pendingChars;
+            
+        } else{
+            
+            // collapse the space in the pending characters
+            sb = new StringBuffer(this.pendingChars.length());
+            boolean ws = false; // true if we're in a run of whitespace
+            for (int i = 0; i < this.pendingChars.length(); i++) {
+                char c = this.pendingChars.charAt(i);
+                if (Character.isWhitespace(c)) {
+                    ws = true;
+                } else {
+                    if (ws) {
+                        sb.append(' ');
+                        ws = false;
+                    }
+                    sb.append(c);
+                }
+            }
+            if (ws) {
+                sb.append(' ');
+            }
+            // trim leading and trailing space, if necessary
+            if (this.trimLeading && sb.length() > 0 && sb.charAt(0) == ' ') {
+                sb.deleteCharAt(0);
+            }
+            if (trimTrailing && sb.length() > 0
+                    && sb.charAt(sb.length() - 1) == ' ') {
+                sb.setLength(sb.length() - 1);
+            }
+        }
+        
+        this.normalizeNewlines(sb);
+        
+        this.content.insertString(this.content.getLength(), sb.toString());
+
+        this.pendingChars.setLength(0);
+        this.trimLeading = false;
+    }
+    
+    private boolean isBlock(Element element) {
+        return this.policy != null && this.policy.isBlock(element);
+    }
+    
+    private boolean isPre(Element element) {
+        return this.policy != null && this.policy.isPre(element);
+    }
+
+    /**
+     * Convert lines that end in CR and CRLFs to plain newlines.
+     * 
+     * @param sb StringBuffer to be normalized.
+     */
+    private void normalizeNewlines(StringBuffer sb) {
+        
+        // State machine states
+        final int START = 0;
+        final int SEEN_CR = 1;
+        
+        int state = START;
+        int i = 0;
+        while (i < sb.length()) { 
+            // No simple 'for' here, since we may delete chars
+
+            char c = sb.charAt(i);
+            
+            switch (state) {
+            case START:
+                if (c == '\r') {
+                    state = SEEN_CR;
+                }
+                i++;
+                break;
+                
+            case SEEN_CR:
+                if (c == '\n') {
+                    // CR-LF, just delete the previous CR
+                    sb.deleteCharAt(i - 1);
+                    state = START; 
+                    // no need to advance i, since it's done implicitly
+                } else if (c == '\r') {
+                    // CR line ending followed by another
+                    // Replace the first with a newline...
+                    sb.setCharAt(i - 1, '\n');
+                    i++;
+                    // ...and stay in the SEEN_CR state
+                } else {
+                    // CR line ending, replace it with a newline
+                    sb.setCharAt(i - 1, '\n');
+                    i++;
+                    state = START;
+                }
+            }
+        }
+        
+        if (state == SEEN_CR) {
+            // CR line ending, replace it with a newline
+        }
+    }
+    
+    private static class StackEntry {
+        public Element element;
+        public int offset;
+        public boolean pre;
+        public StackEntry(Element element, int offset, boolean pre) {
+            this.element = element;
+            this.offset = offset;
+            this.pre = pre;
+        }
+    }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentEvent.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentEvent.java
new file mode 100644
index 0000000..37345ae
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentEvent.java
@@ -0,0 +1,142 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+import java.util.EventObject;
+
+import org.eclipse.wst.xml.vex.core.internal.undo.IUndoableEdit;
+
+
+/**
+ * Encapsulation of the details of a document change
+ */
+public class DocumentEvent extends EventObject {
+
+    private Document document;
+    private Element parentElement;
+    private int offset;
+    private int length;
+    private String attributeName;
+    private String oldAttributeValue;
+    private String newAttributeValue;
+    private IUndoableEdit undoableEdit;
+
+    /**
+     * Class constructor.
+     *
+     * @param document Document that changed.
+     * @param parentElement Element containing the change.
+     * @param offset offset at which the change occurred.
+     * @param length length of the change.
+     * @param undoableEdit IUndoableEdit that can be used to undo the change.
+     */
+    public DocumentEvent(Document document,
+                         Element parentElement,
+			 int offset,
+			 int length,
+                         IUndoableEdit undoableEdit) {
+
+        super(document);
+        this.document = document;
+	this.parentElement = parentElement;
+	this.offset = offset;
+	this.length = length;
+	this.undoableEdit = undoableEdit;
+    }
+
+
+    /**
+     * Class constructor used when firing an attributeChanged event.
+     * 
+     * @param document Document that changed.
+     * @param parentElement element containing the attribute that
+     * changed
+     * @param attributeName name of the attribute that changed
+     * @param oldAttributeValue value of the attribute before the
+     * change.
+     * @param newAttributeValue value of the attribute after the change.
+     * @param undoableEdit IUndoableEdit that can be used to undo the change.
+     */
+    public DocumentEvent(Document document,
+                         Element parentElement,
+                         String attributeName,
+                         String oldAttributeValue,
+                         String newAttributeValue,
+                         IUndoableEdit undoableEdit) {
+    
+                             super(document);
+        this.document = document;
+        this.parentElement = parentElement;
+        this.attributeName = attributeName;
+        this.oldAttributeValue = oldAttributeValue;
+        this.newAttributeValue = newAttributeValue;                         
+        this.undoableEdit = undoableEdit;
+    }
+    
+    /**
+     * Returns the length of the change.
+     */
+    public int getLength() {
+	return this.length;
+    }
+
+    /**
+     * Returns the offset at which the change occurred.
+     */
+    public int getOffset() {
+	return this.offset;
+    }
+
+    /**
+     * Returns the element containing the change.
+     */
+    public Element getParentElement() {
+	return this.parentElement;
+    }
+    /**
+     * @return the value of the attribute before the change.
+     * If null, indicates that the attribute was removed.
+     */
+    public String getNewAttributeValue() {
+        return newAttributeValue;
+    }
+
+    /**
+     * @return the value of the attribute after the change.
+     * If null, indicates the attribute did not exist before
+     * the change.
+     */
+    public String getOldAttributeValue() {
+        return oldAttributeValue;
+    }
+
+    /**
+     * @return the name of the attribute that was changed.
+     */
+    public String getAttributeName() {
+        return attributeName;
+    }
+
+    /**
+     * @return the document for which this event was generated
+     */
+    public Document getDocument() {
+        return document;
+    }
+
+    /**
+     * Returns the undoable edit that can be used to undo the action.
+     * May be null, in which case the action cannot be undone.
+     */
+    public IUndoableEdit getUndoableEdit() {
+        return undoableEdit;
+    }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentFragment.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentFragment.java
new file mode 100644
index 0000000..0a72a9c
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentFragment.java
@@ -0,0 +1,173 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+
+/**
+ * Represents a fragment of an XML document.
+ */
+public class DocumentFragment implements Serializable {
+
+    /** 
+     * Mime type representing document fragments: 
+     * "text/x-vex-document-fragment" 
+     */
+    public static final String MIME_TYPE = "application/x-vex-document-fragment";
+    
+    private Content content;
+    private Element[] elements;
+
+    /**
+     * Class constructor.
+     *
+     * @param content Content holding the fragment's content.
+     * @param elements Elements that make up this fragment.
+     */
+    public DocumentFragment(Content content, Element[] elements) {
+        this.content = content;
+	this.elements = elements;
+    }
+
+    /**
+     * Returns the Content object holding this fragment's content.
+     */
+    public Content getContent() {
+        return this.content;
+    }
+    
+    /**
+     * Returns the number of characters, including sentinels, represented
+     * by the fragment.
+     */
+    public int getLength() {
+        return this.content.getLength();
+    }
+    
+    /**
+     * Returns the elements that make up this fragment.
+     */
+    public Element[] getElements() {
+        return this.elements;
+    }
+    
+    /**
+     * Returns an array of element names and Validator.PCDATA representing
+     * the content of the fragment.
+     */
+    public String[] getNodeNames() {
+        
+        Node[] nodes = this.getNodes();
+        String[] names = new String[nodes.length];
+        for (int i = 0; i < nodes.length; i++) {
+            if (nodes[i] instanceof Text) {
+                names[i] = Validator.PCDATA;
+            } else {
+                names[i] = ((Element) nodes[i]).getName();
+            }
+        }
+        
+        return names;
+    }
+
+    /**
+     * Returns the nodes that make up this fragment, including 
+     * elements and <code>Text</code> objects.
+     */
+    public Node[] getNodes() {
+        return Document.createNodeArray(
+            this.getContent(),
+            0,
+            this.getContent().getLength(),
+            this.getElements());
+    }
+ 
+ 
+    //======================================================= PRIVATE
+    
+    /*
+     * Custom Serialization Methods
+     */
+
+    private void writeObject(ObjectOutputStream out) throws IOException {
+        out.writeUTF(this.content.getString(0, this.content.getLength()));
+        out.writeInt(this.elements.length);
+        for (int i = 0; i < this.elements.length; i++) {
+            this.writeElement(this.elements[i], out);
+        }
+    }
+    
+    private void writeElement(Element element, ObjectOutputStream out) 
+        throws IOException {
+            
+        out.writeObject(element.getName());
+        out.writeInt(element.getStartOffset());
+        out.writeInt(element.getEndOffset());
+        String[] attrNames = element.getAttributeNames();
+        out.writeInt(attrNames.length);
+        for (int i = 0; i < attrNames.length; i++) {
+            out.writeObject(attrNames[i]);
+            out.writeObject(element.getAttribute(attrNames[i]));
+        }
+        Element[] children = element.getChildElements();
+        out.writeInt(children.length);
+        for (int i = 0; i < children.length; i++) {
+            this.writeElement(children[i], out);
+        }
+    }
+    
+    private void readObject(ObjectInputStream in)
+        throws IOException, ClassNotFoundException {
+            
+        String s = in.readUTF();
+        this.content = new GapContent(s.length());
+        content.insertString(0, s);
+        int n = in.readInt();
+        this.elements = new Element[n];
+        for (int i = 0; i < n; i++) {
+            this.elements[i] = this.readElement(in);
+        }
+    }
+    
+    private Element readElement(ObjectInputStream in) 
+        throws IOException, ClassNotFoundException {
+            
+        String name = (String) in.readObject();
+        int startOffset = in.readInt();
+        int endOffset = in.readInt();
+        Element element = new Element(name);
+        element.setContent(this.content, startOffset, endOffset);
+        
+        int attrCount = in.readInt();
+        for (int i = 0; i < attrCount; i++) {
+            String key = (String) in.readObject();
+            String value = (String) in.readObject();
+            try {
+                element.setAttribute(key, value);
+            } catch (DocumentValidationException e) {
+                // Should never happen; there ain't no document
+                e.printStackTrace();
+            }
+        }
+        
+        int childCount = in.readInt();
+        for (int i = 0; i < childCount; i++) {
+            Element child = this.readElement(in);
+            child.setParent(element);
+            element.insertChild(i, child);
+        }
+        
+        return element;
+    }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentListener.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentListener.java
new file mode 100644
index 0000000..8d771be
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentListener.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+/**
+ * Receives notifications of document changes.
+ */
+public interface DocumentListener extends java.util.EventListener {
+
+    /**
+     * Called when an attribute is changed in one of the document's
+     * elements.
+     * 
+     * @param e the document event.
+     */
+    public void attributeChanged(DocumentEvent e);
+    
+    /**
+     * Called before content is deleted from a document.
+     * 
+     * @param e the document event
+     */
+    public void beforeContentDeleted(DocumentEvent e);
+    
+    /**
+     * Called before content is inserted into a document.
+     * 
+     * @param e the document event
+     */
+    public void beforeContentInserted(DocumentEvent e);
+    
+    /**
+     * Called when content is deleted from a document.
+     *
+     * @param e the document event
+     */
+    public void contentDeleted(DocumentEvent e);
+
+    /**
+     * Called when content is inserted into a document.
+     *
+     * @param e the document event
+     */
+    public void contentInserted(DocumentEvent e);
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentReader.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentReader.java
new file mode 100644
index 0000000..330b56a
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentReader.java
@@ -0,0 +1,161 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+import java.io.CharArrayReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.net.URL;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParserFactory;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.ext.LexicalHandler;
+
+
+/**
+ * Class for creating documents given a URL.
+ */
+public class DocumentReader {
+
+    
+    /**
+     * Returns the debugging flag.
+     */
+    public boolean isDebugging() {
+        return debugging;
+    }
+
+    /**
+     * Returns the entity resolver for this reader.
+     */
+    public EntityResolver getEntityResolver() {
+        return entityResolver;
+    }
+    
+    /**
+     * Returns the whitespace policy factory for this reader.
+     */
+    public IWhitespacePolicyFactory getWhitespacePolicyFactory() {
+        return whitespacePolicyFactory;
+    }
+    
+    /**
+     * Reads a document given a URL.
+     *
+     * @param url URL from which to load the document.
+     */
+    public Document read(URL url)
+        throws IOException, ParserConfigurationException, SAXException {
+
+        return read(new InputSource(url.toString()));
+    }
+
+    /**
+     * Reads a document from a string. This is mainly used for short documents
+     * in unit tests.
+     * @param s String containing the document to be read.
+     */
+    public Document read(String s) 
+            throws IOException, ParserConfigurationException, SAXException {
+
+        Reader reader = new CharArrayReader(s.toCharArray());
+        return this.read(new InputSource(reader));
+    }
+    
+    /**
+     * Reads a document given a SAX InputSource.
+     *
+     * @param is SAX InputSource from which to load the document.
+     */
+    public Document read(InputSource is)
+	throws IOException, ParserConfigurationException, SAXException {
+
+	SAXParserFactory factory = SAXParserFactory.newInstance();
+        factory.setValidating(false); // TODO: experimental--SWT implementation
+	XMLReader xmlReader = factory.newSAXParser().getXMLReader();
+	//xmlReader.setFeature("http://xml.org/sax/features/validation", false);
+	final org.eclipse.wst.xml.vex.core.internal.dom.DocumentBuilder builder = 
+	    new org.eclipse.wst.xml.vex.core.internal.dom.DocumentBuilder(this.getWhitespacePolicyFactory());
+
+	ContentHandler contentHandler = builder;
+	LexicalHandler lexicalHandler = builder;
+    
+	if (this.isDebugging()) {
+	    Object proxy = Proxy.newProxyInstance(
+                this.getClass().getClassLoader(),
+                new Class[] { ContentHandler.class, LexicalHandler.class },
+                new InvocationHandler() {
+                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+                        try {
+                            return method.invoke(builder, args);
+                        } catch (InvocationTargetException ex) {
+                            ex.getCause().printStackTrace();
+                            throw ex.getCause();
+                        }
+                    }
+	        });
+        
+	    contentHandler = (ContentHandler) proxy;
+	    lexicalHandler = (LexicalHandler) proxy;
+	}
+    
+	xmlReader.setContentHandler(contentHandler);
+	xmlReader.setProperty("http://xml.org/sax/properties/lexical-handler",
+			      lexicalHandler);
+	if (this.getEntityResolver() != null) {
+	    xmlReader.setEntityResolver(this.getEntityResolver());
+	}
+	xmlReader.parse(is);
+	return builder.getDocument();
+    }
+    
+    /**
+     * Sets the debugging flag.
+     * @param debugging true if the component should log debugging info to stdout.
+     */
+    public void setDebugging(boolean debugging) {
+        this.debugging = debugging;
+    }
+
+    /**
+     * Sets the entity resolver for this reader.
+     * @param entityResolver The entityResolver to set.
+     */
+    public void setEntityResolver(EntityResolver entityResolver) {
+        this.entityResolver = entityResolver;
+    }
+    
+    /**
+     * Sets the whitespace policy factory for this reader. This factory is used
+     * to obtain a whitespace policy once the public ID of the document is
+     * known.
+     * @param whitespacePolicyFactory The whitespacePolicyFactory to set.
+     */
+    public void setWhitespacePolicyFactory(
+            IWhitespacePolicyFactory whitespacePolicyFactory) {
+        this.whitespacePolicyFactory = whitespacePolicyFactory;
+    }
+
+    //======================================================= PRIVATE
+    
+    private boolean debugging;
+    private EntityResolver entityResolver;
+    private IWhitespacePolicyFactory whitespacePolicyFactory;
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentValidationException.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentValidationException.java
new file mode 100644
index 0000000..c3a008e
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentValidationException.java
@@ -0,0 +1,30 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+
+/**
+ * Exception thrown when an change would have compromised document validity.
+ */
+public class DocumentValidationException extends RuntimeException {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Class constructor.
+     *
+     * @param message Message indicating the nature of the exception.
+     */
+    public DocumentValidationException(String message) {
+        super(message);
+    }
+}
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentWriter.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentWriter.java
new file mode 100644
index 0000000..45965fe
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentWriter.java
@@ -0,0 +1,348 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.util.Arrays;
+
+/**
+ * Writes a document to an output stream, using a stylesheet to provide 
+ * formatting hints.
+ *
+ * <ul>
+ * <li>Children of an element are indented by a configurable amount.</li>
+ * <li>Text is wrapped to fit within a configurable width.<li>
+ * </ul>
+ *
+ * <p>Documents are currently saved UTF-8 encoding, with no encoding
+ * specified in the XML declaration.</p>
+ */
+public class DocumentWriter {
+
+    private IWhitespacePolicy whitespacePolicy;
+    private String indent;
+    private int wrapColumn;
+
+    /**
+     * Class constructor.
+     */
+    public DocumentWriter() {
+	this.indent = "  ";
+	this.wrapColumn = 72;
+    }
+
+    /**
+     * Escapes special XML characters. Changes '<', '>', and '&' to
+     * '&lt;', '&gt;' and '&amp;', respectively.
+     *
+     * @param s the string to be escaped.
+     * @return the escaped string
+     */
+    public static String escape(String s) {
+	StringBuffer sb = new StringBuffer(s.length());
+	
+	for (int i = 0; i < s.length(); i++) {
+	    char c = s.charAt(i);
+	    if (c == '<') {
+		sb.append("&lt;");
+	    } else if (c == '>') {
+		sb.append("&gt;");
+	    } else if (c == '&') {
+		sb.append("&amp;");
+        } else if (c == '"') {
+            sb.append("&quot;");
+        } else if (c == '\'') {
+            sb.append("&apos;");
+	    } else {
+		sb.append(c);
+	    }
+	}
+	return sb.toString();
+    }
+
+    /**
+     * Returns the indent string. By default this is two spaces.
+     */
+    public String getIndent() {
+	return this.indent;
+    }
+
+    /**
+     * Returns the whitespace policy used by this writer.
+     */
+    public IWhitespacePolicy getWhitespacePolicy() {
+        return whitespacePolicy;
+    }
+    
+    /**
+     * Returns the column at which text should be wrapped. By default this
+     * is 72.
+     */
+    public int getWrapColumn() {
+	return this.wrapColumn;
+    }
+
+    /**
+     * Sets the value of the indent string.
+     *
+     * @param indent new value for the indent string.
+     */
+    public void setIndent(String indent) {
+	this.indent = indent;
+    }
+
+    /**
+     * Sets the whitespace policy for this writer. The whitespace policy tells
+     * the writer which elements are block-formatted and which are pre-formatted.
+     * 
+     * @param whitespacePolicy The whitespacePolicy to set.
+     */
+    public void setWhitespacePolicy(IWhitespacePolicy whitespacePolicy) {
+        this.whitespacePolicy = whitespacePolicy;
+    }
+
+    /**
+     * Sets the value of the wrap column.
+     *
+     * @param wrapColumn new value for the wrap column.
+     */
+    public void setWrapColumn(int wrapColumn) {
+	this.wrapColumn = wrapColumn;
+    }
+
+    public void write(Document doc, OutputStream os)
+	throws IOException {
+
+	OutputStreamWriter osw = new OutputStreamWriter(os, "UTF-8");
+	PrintWriter pw = new PrintWriter(osw);
+	pw.println("<?xml version='1.0'?>");
+	if (doc.getSystemID() != null) {
+	    StringBuffer sb = new StringBuffer();
+            sb.append("<!DOCTYPE ");
+            sb.append(doc.getRootElement().getName());
+            if (doc.getPublicID() != null) {
+                sb.append(" PUBLIC");
+                sb.append(" \"");
+                sb.append(doc.getPublicID());
+                sb.append("\"");
+            } else {
+                sb.append(" SYSTEM");
+            } 
+            sb.append(" \"");
+            sb.append(doc.getSystemID());
+            sb.append("\">");
+            pw.println(sb.toString());
+	}
+	this.writeNode(doc.getRootElement(), pw, "");
+	pw.flush();
+    }
+
+
+    //====================================================== PRIVATE
+
+
+    private void writeNode(Node node, 
+			   PrintWriter pw,
+			   String indent) {
+
+	if (node instanceof Text) {
+	    TextWrapper wrapper = new TextWrapper();
+	    wrapper.add(escape(node.getText()));
+
+	    String[] lines = wrapper.wrap(this.wrapColumn - indent.length());
+
+	    for (int i = 0; i < lines.length; i++) {
+		pw.print(indent);
+		pw.println(lines[i]);
+	    }
+
+	} else {
+
+	    Element element = (Element) node;
+
+	    if (this.whitespacePolicy != null && this.whitespacePolicy.isPre(element)) {
+		pw.print(indent);
+		writeNodeNoWrap(node, pw);
+		pw.println();
+		return;
+	    }
+
+	    boolean hasBlockChild = false;
+	    Element[] children = element.getChildElements();
+	    for (int i = 0; i < children.length; i++) {
+                if (this.whitespacePolicy != null && this.whitespacePolicy.isBlock(children[i])) {
+		    hasBlockChild = true;
+		    break;
+		}
+	    }
+
+	    if (hasBlockChild) {
+		pw.print(indent);
+		pw.print("<");
+		pw.print(element.getName());
+
+		TextWrapper wrapper = new TextWrapper();
+		wrapper.addNoSplit(this.getAttributeString(element));
+		int outdent = indent.length() + 1 + element.getName().length();
+		String[] lines = wrapper.wrap(this.wrapColumn - outdent);
+		char[] bigIndent = new char[outdent];
+		Arrays.fill(bigIndent, ' ');
+		for (int i = 0; i < lines.length; i++) {
+		    if (i > 0) {
+			pw.print(bigIndent);
+		    }
+		    pw.print(lines[i]);
+		    if (i < lines.length - 1) {
+			pw.println();
+		    }
+		}
+		pw.println(">");
+
+		String childIndent = indent + this.indent;
+		Node[] content = element.getChildNodes();
+		for (int i = 0; i < content.length; i++) {
+		    this.writeNode(content[i], pw, childIndent);
+		}
+		pw.print(indent);
+		pw.print("</");
+		pw.print(element.getName());
+		pw.println(">");
+	    } else {
+		TextWrapper wrapper = new TextWrapper();
+		this.addNode(element, wrapper);
+		String[] lines = wrapper.wrap(this.wrapColumn-indent.length());
+		for (int i = 0; i < lines.length; i++) {
+		    pw.print(indent);
+		    pw.println(lines[i]);
+		}
+	    }
+		
+	}
+    }
+   
+    private void writeNodeNoWrap(Node node, PrintWriter pw) {
+
+	if (node instanceof Text) {
+	    pw.print(escape(node.getText()));
+	} else {
+
+	    Element element = (Element) node;
+
+	    pw.print("<");
+	    pw.print(element.getName());
+	    pw.print(this.getAttributeString(element));
+	    pw.print(">");
+
+	    Node[] content = element.getChildNodes();
+	    for (int i = 0; i < content.length; i++) {
+		this.writeNodeNoWrap(content[i], pw);
+	    }
+
+	    pw.print("</");
+	    pw.print(element.getName());
+	    pw.print(">");
+	}
+    }
+   
+
+    private String attrToString(String name, String value) {
+	StringBuffer sb = new StringBuffer();
+	sb.append(" ");
+	sb.append(name);
+	sb.append("=\"");
+	sb.append(escape(value));
+	sb.append("\"");
+	return sb.toString();
+    }
+
+    private void addNode(Node node, TextWrapper wrapper) {
+	if (node instanceof Text) {
+	    wrapper.add(escape(node.getText()));
+	} else {
+	    Element element = (Element)node;
+	    Node[] content = element.getChildNodes();
+	    String[] attrs = element.getAttributeNames();
+	    Arrays.sort(attrs);
+
+	    if (attrs.length == 0) {
+		if (content.length == 0) {
+		    wrapper.add("<" + element.getName() + " />");
+		} else {
+		    wrapper.add("<" + element.getName() + ">");
+		}
+	    } else {
+	        Validator validator = element.getDocument().getValidator();
+		StringBuffer sb = new StringBuffer();
+		for (int i = 0; i < attrs.length; i++) {
+		    sb.setLength(0);
+		    if (i == 0) {
+			sb.append("<" + element.getName());
+		    }
+		    if (!attrHasDefaultValue(validator, element, attrs[i])) {
+		        sb.append(attrToString(attrs[i], element.getAttribute(attrs[i])));
+		    }
+		    if (i == attrs.length - 1) {
+			if (content.length == 0) {
+			    sb.append("/>");
+			} else {
+			    sb.append(">");
+			}
+		    }
+		    wrapper.addNoSplit(sb.toString());
+		}
+	    }
+
+	    for (int i = 0; i < content.length; i++) {
+		addNode(content[i], wrapper);
+	    }
+
+	    if (content.length > 0) {
+		wrapper.add("</" + element.getName() + ">");
+	    }
+	}
+    }
+
+    private String getAttributeString(Element element) {
+        
+        Validator validator = element.getDocument().getValidator();
+        
+	String[] attrs = element.getAttributeNames();
+	Arrays.sort(attrs);
+	StringBuffer sb = new StringBuffer();
+	for (int i = 0; i < attrs.length; i++) {
+	    if (attrHasDefaultValue(validator, element, attrs[i])) {
+	        continue;
+	    }
+	    sb.append(" ");
+	    sb.append(attrs[i]);
+	    sb.append("=\"");
+	    sb.append(escape(element.getAttribute(attrs[i])));
+	    sb.append("\"");
+	}
+	return sb.toString();
+    }
+
+    private static boolean attrHasDefaultValue(Validator validator, Element element, String attribute) {
+        if (validator != null) {
+            AttributeDefinition ad = validator.getAttributeDefinition(element.getName(), attribute);
+            if (ad != null) {
+                String value = element.getAttribute(attribute);
+                String defaultValue = ad.getDefaultValue();
+                return value != null && value.equals(defaultValue);
+            }
+        }
+        return false;
+    }
+}
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/Element.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/Element.java
new file mode 100644
index 0000000..ee45709
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/Element.java
@@ -0,0 +1,324 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.wst.xml.vex.core.internal.undo.CannotRedoException;
+import org.eclipse.wst.xml.vex.core.internal.undo.CannotUndoException;
+import org.eclipse.wst.xml.vex.core.internal.undo.IUndoableEdit;
+
+
+/**
+ * <code>Element</code> represents a tag in an XML document. Methods
+ * are available for managing the element's attributes and children.
+ */
+public class Element extends Node implements Cloneable {
+
+    private String name;
+    private Element parent = null;
+    private List children = new ArrayList();
+    private Map attributes = new HashMap();
+
+
+
+    /**
+     * Class constructor.
+     * @param name element name
+     */
+    public Element(String name) {
+	this.name = name;
+    }
+
+    /**
+     * Adds the given child to the end of the child list.
+     * Sets the parent attribute of the given element to this element.
+     */
+    public void addChild(Element child) {
+        this.children.add(child);
+        child.parent = this;
+    }
+    
+
+    /**
+     * Clones the element and its attributes. The returned element has
+     * no parent or children.
+     */
+    public Object clone() {
+        try {
+            Element element = new Element(this.getName());
+            for (Iterator it = this.attributes.keySet().iterator(); it
+                    .hasNext();) {
+                String attrName = (String) it.next();
+                element.setAttribute(attrName, (String) this.attributes
+                        .get(attrName));
+            }
+            return element;
+            
+        } catch (DocumentValidationException ex) {
+            ex.printStackTrace();
+            return null;
+        }
+    }
+    
+    /**
+     * Returns the value of an attribute given its name. If no such
+     * attribute exists, returns null.
+     *
+     * @param name Name of the attribute.
+     */
+    public String getAttribute(String name) {
+	return (String) attributes.get(name);
+    }
+
+    /**
+     * Returns an array of names of the attributes in the element.
+     */
+    public String[] getAttributeNames() {
+	Collection names = this.attributes.keySet();
+	return (String[]) names.toArray(new String[names.size()]);
+    }
+
+    /**
+     * Returns an iterator over the children. Used by
+     * <code>Document.delete</code> to safely delete children.
+     */
+    public Iterator getChildIterator() {
+	return this.children.iterator();
+    }
+
+    /**
+     * Returns an array of the elements children.
+     */
+    public Element[] getChildElements() {
+	int size = this.children.size();
+	return (Element[]) this.children.toArray(new Element[size]);
+    }
+
+    /**
+     * Returns an array of nodes representing the content of this element. 
+     * The array includes child elements and runs of text returned as 
+     * <code>Text</code> objects.
+     */
+    public Node[] getChildNodes() {
+        return Document.createNodeArray(
+            this.getContent(),
+            this.getStartOffset() + 1,
+            this.getEndOffset(),
+            this.getChildElements());
+    }
+
+    /**
+     * @return The document to which this element belongs.
+     * Returns null if this element is part of a document
+     * fragment.
+     */
+    public Document getDocument() {
+        Element root = this;
+        while (root.getParent() != null) {
+            root = root.getParent(); 
+        }
+        if (root instanceof RootElement) {
+            return ((RootElement) root).getDocument();
+        } else {
+            return null;
+        }
+    }
+    
+    /**
+     * Returns the name of the element.
+     */
+    public String getName() {
+	return this.name;
+    }
+
+    /**
+     * Returns the parent of this element, or null if this is the root element.
+     */
+    public Element getParent() {
+	return this.parent;
+    }
+
+    
+    public String getText() {
+        String s = super.getText();
+        StringBuffer sb = new StringBuffer(s.length());
+        for (int i = 0; i < s.length(); i++) {
+            char c = s.charAt(i);
+            if (c != 0) {
+                sb.append(c);
+            }
+        }
+        return sb.toString();
+    }
+    
+    /**
+     * Inserts the given element as a child at the given child index.
+     * Sets the parent attribute of the given element to this element.
+     */
+    void insertChild(int index, Element child) {
+	this.children.add(index, child);
+	child.parent = this;
+    }
+    
+    /**
+     * Returns true if the element has no content.
+     */
+    public boolean isEmpty() {
+        return this.getStartOffset() + 1 == this.getEndOffset();
+    }
+
+    /**
+     * Removes the given attribute from the array.
+     *
+     * @param name name of the attribute to remove.
+     */
+    public void removeAttribute(String name) 
+        throws DocumentValidationException {
+
+        String oldValue = this.getAttribute(name);
+        String newValue = null;            
+	if (oldValue != null) {
+	    this.attributes.remove(name);
+	}
+        Document doc = this.getDocument();
+        if (doc != null) { // doc may be null, e.g. when we're cloning an element
+                           // to produce a document fragment
+            
+            IUndoableEdit edit = doc.isUndoEnabled() ?
+                    new AttributeChangeEdit(name, oldValue, newValue) : null;
+                    
+            doc.fireAttributeChanged(new DocumentEvent(
+                    doc, this, name, oldValue, newValue, edit));
+        }
+    }
+
+    /**
+     * Sets the value of an attribute for this element.
+     *
+     * @param name Name of the attribute to be set.
+     * @param value New value for the attribute. If null, this call
+     * has the same effect as removeAttribute(name).
+     */
+    public void setAttribute(String name, String value) 
+        throws DocumentValidationException {
+            
+        String oldValue = this.getAttribute(name);
+        
+        if (value == null && oldValue == null) {
+            return;
+        } else if (value == null) {
+            this.removeAttribute(name);
+        } else if (value.equals(oldValue)) {
+            return;
+        } else {
+            this.attributes.put(name, value);
+            Document doc = this.getDocument();
+            if (doc != null) { // doc may be null, e.g. when we're cloning an element
+                               // to produce a document fragment
+                
+                IUndoableEdit edit = doc.isUndoEnabled() ?
+                        new AttributeChangeEdit(name, oldValue, value) : null;
+                        
+                doc.fireAttributeChanged(new DocumentEvent(
+                        doc, this, name, oldValue, value, edit));
+            }
+        }
+        
+
+    }
+
+    /**
+     * Sets the parent of this element.
+     *
+     * @param parent Parent element.
+     */
+    public void setParent(Element parent) {
+	this.parent = parent;
+    }
+
+    public String toString() {
+        
+	StringBuffer sb = new StringBuffer();
+	sb.append("<");
+	sb.append(this.getName());
+	String[] attrs = this.getAttributeNames();
+	
+	for (int i = 0; i < attrs.length; i++) {
+	    if (i > 0) {
+	    	sb.append(",");
+	    }
+	    sb.append(" ");
+	    sb.append(attrs[i]);
+	    sb.append("=\"");
+	    sb.append(this.getAttribute(attrs[i]));
+	    sb.append("\"");
+	}
+    
+	sb.append("> (");
+	sb.append(this.getStartPosition());
+	sb.append(",");
+	sb.append(this.getEndPosition());
+	sb.append(")");
+    
+	return sb.toString();
+    }
+
+    //========================================================= PRIVATE
+
+    private class AttributeChangeEdit implements IUndoableEdit {
+
+        private String name;
+        private String oldValue;
+        private String newValue;
+    
+        public AttributeChangeEdit(String name, String oldValue, String newValue) {
+            this.name = name;
+            this.oldValue = oldValue;
+            this.newValue = newValue;
+        }
+
+        public boolean combine(IUndoableEdit edit) {
+            return false;
+        }
+
+        public void undo() throws CannotUndoException {
+            Document doc = getDocument();
+            try {
+                doc.setUndoEnabled(false);
+                setAttribute(name, oldValue);
+            } catch (DocumentValidationException ex) {
+                throw new CannotUndoException();
+            } finally {
+                doc.setUndoEnabled(true);
+            }
+        }
+
+        public void redo() throws CannotRedoException {
+            Document doc = getDocument();
+            try {
+                doc.setUndoEnabled(false);
+                setAttribute(name, newValue);
+            } catch (DocumentValidationException ex) {
+                throw new CannotUndoException();
+            } finally {
+                doc.setUndoEnabled(true);
+            }
+        }
+    }
+
+}
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/GapContent.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/GapContent.java
new file mode 100644
index 0000000..835d897
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/GapContent.java
@@ -0,0 +1,270 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Implementation of the <code>Content</code> interface that manages
+ * changes efficiently. Implements a buffer that keeps its free space
+ * (the "gap") at the location of the last change. Insertions at the
+ * start of the gap require no other chars to be moved so long as the
+ * insertion is smaller than the gap. Deletions that end of the gap
+ * are also very efficent. Furthermore, changes near the gap require
+ * relatively few characters to be moved.
+ */
+public class GapContent implements Content {
+
+    private char[] content;
+    private int gapStart;
+    private int gapEnd;
+    private final Map positions = new HashMap();
+
+    /**
+     * Class constructor.
+     *
+     * @param initialCapacity initial capacity of the content.
+     */
+    public GapContent(int initialCapacity) {
+
+	assertPositive(initialCapacity);
+
+	this.content = new char[initialCapacity];
+	this.gapStart = 0;
+	this.gapEnd = initialCapacity;
+    }
+
+    /**
+     * Creates a new Position object at the given initial offset.
+     *
+     * @param offset initial offset of the position
+     */
+    public Position createPosition(int offset) {
+
+	assertOffset(offset, 0, this.getLength());
+
+	Position pos = new GapContentPosition(offset);
+	this.positions.put(pos, pos);
+	
+	return pos;
+    }
+
+    /**
+     * Insert a string into the content.
+     *
+     * @param offset Offset at which to insert the string.
+     * @param s String to insert.
+     */
+    public void insertString(int offset, String s) {
+
+	assertOffset(offset, 0, this.getLength());
+
+	if (s.length() > (this.gapEnd - this.gapStart)) {
+	    this.expandContent(this.getLength() + s.length());
+	}
+    
+	//
+	// Optimization: no need to update positions if we're inserting
+	// after existing content (offset == this.getLength()) and if
+	// we don't have to move the gap to do it (offset == gapStart).
+	//
+	// This significantly improves document load speed.
+	//
+	boolean atEnd = (offset == this.getLength() && offset == gapStart);
+    
+	this.moveGap(offset);
+	s.getChars(0, s.length(), this.content, offset);
+	this.gapStart += s.length();
+
+	if (!atEnd) {
+	    //
+	    // Update positions
+	    //
+	    for (Iterator i = this.positions.keySet().iterator(); i.hasNext(); ) {
+	        GapContentPosition pos = (GapContentPosition) i.next();
+	        if (pos.getOffset() >= offset) {
+	            pos.setOffset(pos.getOffset() + s.length());
+	        }
+	    }
+	}
+    }
+	
+
+    /**
+     * Deletes the given range of characters.
+     *
+     * @param offset Offset from which characters should be deleted.
+     * @param length Number of characters to delete.
+     */
+    public void remove(int offset, int length) {
+
+	assertOffset(offset, 0, this.getLength() - length);
+	assertPositive(length);
+	
+	this.moveGap(offset + length);
+	this.gapStart -= length;
+
+	for (Iterator i = this.positions.keySet().iterator(); i.hasNext(); ) {
+	    GapContentPosition pos = (GapContentPosition) i.next();
+	    if (pos.getOffset() >= offset + length) {
+	        pos.setOffset(pos.getOffset() - length);
+	    } else if (pos.getOffset() >= offset) {
+	        pos.setOffset(offset);
+	    }
+	}
+    }
+
+    /**
+     * Gets a substring of the content.
+     *
+     * @param offset Offset at which the string begins.
+     * @param length Number of characters to return.
+     */
+    public String getString(int offset, int length) {
+
+	assertOffset(offset, 0, this.getLength() - length);
+	assertPositive(length);
+
+	if (offset + length <= this.gapStart) {
+	    return new String(this.content,
+			      offset,
+			      length);
+	} else if (offset >= this.gapStart) {
+	    return new String(this.content,
+			      offset - this.gapStart + this.gapEnd,
+			      length);
+	} else {
+	    StringBuffer sb = new StringBuffer(length);
+	    sb.append(this.content, 
+		      offset, 
+		      this.gapStart - offset);
+	    sb.append(this.content,
+		      this.gapEnd,
+		      offset + length - this.gapStart);
+	    return sb.toString();
+	}
+    }
+
+    /**
+     * Return the length of the content.
+     */
+    public int getLength() {
+	return this.content.length - (this.gapEnd - this.gapStart);
+    }
+
+    //====================================================== PRIVATE
+
+    private static final int GROWTH_SLOWDOWN_SIZE = 100000;
+    private static final int GROWTH_RATE_FAST = 2;
+    private static final float GROWTH_RATE_SLOW = 1.1f;
+    
+    /**
+     * Implementation of the Position interface.
+     */
+    private static class GapContentPosition implements Position {
+
+	private int offset;
+
+	public GapContentPosition(int offset) {
+	    this.offset = offset;
+	}
+
+	public int getOffset() {
+	    return this.offset;
+	}
+
+	public void setOffset(int offset) {
+	    this.offset = offset;
+	}
+    
+	public String toString() {
+	    return Integer.toString(this.offset);
+	}
+    }
+
+    /**
+     * Assert that the given offset is within the given range,
+     * throwing IllegalArgumentException if not.
+     */
+    private static void assertOffset(int offset, int min, int max) {
+	if (offset < min || offset > max) {
+	    throw new IllegalArgumentException("Bad offset " + offset +
+					       "must be between " + min +
+					       " and " + max);
+	}
+    }
+
+    /**
+     * Assert that the given value is zero or positive.
+     * throwing IllegalArgumentException if not.
+     */
+    private static void assertPositive(int value) {
+	if (value < 0) {
+	    throw new IllegalArgumentException("Value should be zero or positive, but it was " + value);
+	}
+    }
+
+    /**
+     * Expand the content array to fit at least the given length.
+     */
+    private void expandContent(int newLength) {
+
+        // grow quickly when small, slower when large  	
+    	
+	int newCapacity;
+	
+	if (newLength < GROWTH_SLOWDOWN_SIZE) {
+	    newCapacity = Math.max((int) (newLength * GROWTH_RATE_FAST), 32);
+	} else {
+	    newCapacity = (int)(newLength * GROWTH_RATE_SLOW);
+	}
+	
+	char[] newContent = new char[newCapacity];
+
+	System.arraycopy(this.content, 0,
+			 newContent, 0,
+			 this.gapStart);
+
+	int tailLength = this.content.length - this.gapEnd;
+	System.arraycopy(this.content, this.gapEnd,
+			 newContent, newCapacity - tailLength,
+			 tailLength);
+
+	this.content = newContent;
+	this.gapEnd = newCapacity - tailLength;
+    }
+
+    /**
+     * Move the gap to the given offset.
+     */
+    private void moveGap(int offset) {
+
+	assertOffset(offset, 0, this.getLength());
+
+	if (offset <= this.gapStart) {
+	    int length = this.gapStart - offset;
+	    System.arraycopy(this.content, offset,
+			     this.content, this.gapEnd - length,
+			     length);
+	    this.gapStart -= length;
+	    this.gapEnd -= length;
+	} else {
+	    int length = offset - this.gapStart;
+	    System.arraycopy(this.content, this.gapEnd,
+			     this.content, this.gapStart,
+			     length);
+	    this.gapStart += length;
+	    this.gapEnd += length;
+	}
+    }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/IWhitespacePolicy.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/IWhitespacePolicy.java
new file mode 100644
index 0000000..aceeae3
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/IWhitespacePolicy.java
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+
+/**
+ * Determines whitespace policy for document elements. For example, a CSS
+ * stylesheet implements a whitespace policy via its display and white-space
+ * properties.
+ */
+public interface IWhitespacePolicy {
+    
+    /**
+     * Returns true if the given element is normally block-formatted.
+     * @param element Element to test.
+     */
+    public boolean isBlock(Element element);
+    
+    /**
+     * Returns true if the given element is pre-formatted, that is, all of 
+     * its contained whitespace should be preserved.
+     * @param element Element to test.
+     */
+    public boolean isPre(Element element);
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/IWhitespacePolicyFactory.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/IWhitespacePolicyFactory.java
new file mode 100644
index 0000000..f4772f5
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/IWhitespacePolicyFactory.java
@@ -0,0 +1,27 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+
+/**
+ * Factory for returning a WhitespacePolicy object given a document type 
+ * public ID. This is required by DocumentBuilder, since we don't know what
+ * WhitespacePolicy we need before we begin parsing the document.
+ */
+public interface IWhitespacePolicyFactory {
+    
+    /**
+     * Return a WhitespacePolicy for documents with the given public ID.
+     * @param publicId Public ID of the document type associated with
+     * the document.
+     */
+    public IWhitespacePolicy getPolicy(String publicId);
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/Node.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/Node.java
new file mode 100644
index 0000000..7496ea7
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/Node.java
@@ -0,0 +1,95 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+/**
+ * <code>Node</code> represents a component of an XML document. .
+ */
+public class Node {
+
+    private Content content = null;
+    private Position start = null;
+    private Position end = null;
+
+    /**
+     * Class constructor.
+     */
+    public Node() {
+    }
+
+
+    /**
+     * Returns the document associated with this node. Null if the node
+     * has not yet been inserted into a document.
+     */
+    public Content getContent() {
+	return this.content;
+    }
+    
+    /**
+     * Returns the character offset corresponding to the end of the
+     * node.
+     */
+    public int getEndOffset() {
+	return this.end.getOffset();
+    }
+
+    /**
+     * Returns the <code>Position</code> corresponding to the end of
+     * the node.
+     */
+    public Position getEndPosition() {
+	return this.end;
+    }
+
+    /**
+     * Returns the character offset corresponding to the start of the
+     * node.
+     */
+    public int getStartOffset() {
+	return this.start.getOffset();
+    }
+
+    /**
+     * Returns the <code>Position</code> corresponding to the start of
+     * the node.
+     */
+    public Position getStartPosition() {
+	return this.start;
+    }
+
+    /**
+     * Returns the text contained by this node. If this node is an element,
+     * the text in all child nodes is included.
+     */
+    public String getText() {
+	return this.content.getString(this.getStartOffset(),
+				      this.getEndOffset() - this.getStartOffset());
+    }
+
+    /**
+     * Sets the content of the node
+     * 
+     * @param content Content object holding the node's content
+     * @param startOffset offset at which the node's content starts
+     * @param endOffset offset at which the node's content ends
+     */
+    void setContent(Content content, 
+		    int startOffset, 
+		    int endOffset) {
+
+	this.content = content;
+	this.start = content.createPosition(startOffset);
+	this.end = content.createPosition(endOffset);
+    }
+
+}
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/Position.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/Position.java
new file mode 100644
index 0000000..34937e0
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/Position.java
@@ -0,0 +1,24 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+/**
+ * Represents a logical location in a document. As the document is
+ * modified, existing <code>Position</code> objects are updated to
+ * reflect the appropriate character offset in the document.
+ */
+public interface Position {
+
+    /**
+     * Returns the character offset corresponding to the position.
+     */
+    public int getOffset();
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/RootElement.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/RootElement.java
new file mode 100644
index 0000000..ec66d27
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/RootElement.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+/**
+ * The root element of a document. Keeps track of the document to which
+ * it is associated. Any element can find the document to which it is
+ * associated by following its parents to this root. This would be done,
+ * for example, to notify document listeners that the document has changed
+ * when the element changes.
+ */
+public class RootElement extends Element {
+
+    private Document document;
+    
+    /**
+     * Class constructor
+     * @param name Name of the element.
+     */
+    public RootElement(String name) {
+        super(name);
+    }
+
+    /**
+     * @return The document associated with this element.
+     */
+    public Document getDocument() {
+        return document;
+    }
+
+    /**
+     * Sets the document to which this element is associated.
+     * This is called by the document constructor, so it need not
+     * be called by client code.
+     * @param document Document to which this root element is
+     * associated.
+     */
+    public void setDocument(Document document) {
+        this.document = document;
+    }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/Text.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/Text.java
new file mode 100644
index 0000000..9a483ad
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/Text.java
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+/**
+ * <code>Text</code> represents a run of text in a document. Text
+ * objects are not used in the internal document structure; they are
+ * only returned as needed by the <code>Element.getContent</code>
+ * method.
+ */
+public class Text extends Node {
+
+    /**
+     * Class constructor.
+     * 
+     * @param content Content object containing the text
+     * @param startOffset character offset of the start of the run
+     * @param endOffset character offset of the end of the run
+     */
+    public Text(Content content, int startOffset, int endOffset) {
+	this.setContent(content, startOffset, endOffset);
+    }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/TextWrapper.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/TextWrapper.java
new file mode 100644
index 0000000..29f0aae
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/TextWrapper.java
@@ -0,0 +1,125 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Wraps text to a given width.
+ */
+public class TextWrapper {
+
+    private List parts = new ArrayList();
+
+    private boolean lastIsWhite = true;
+    
+    /**
+     * Class constructor.
+     */
+    public TextWrapper() {
+    }
+
+    /**
+     * Adds text to the list of things to be wrapped.
+     *
+     * @param s Text to be added.
+     */
+    public void add(String s) {
+	int i = 0;
+	int j = 0;
+	boolean thisIsWhite = true;
+	while (j < s.length()) {
+
+	    // skip non-whitespace
+	    while (j < s.length() && !Character.isWhitespace(s.charAt(j))) {
+		j++;
+		thisIsWhite = false;
+	    }
+
+	    // skip whitespace
+	    while (j < s.length() && Character.isWhitespace(s.charAt(j))) {
+		j++;
+		thisIsWhite = true;
+	    }
+	    
+	    if (lastIsWhite)
+	    	this.parts.add(s.substring(i, j));
+	    else
+	    	this.parts.add(((String)this.parts.remove(this.parts.size()-1)) +
+	    			s.substring(i, j));
+	    i = j;
+	    lastIsWhite = thisIsWhite;
+	}
+    }
+
+    /**
+     * Adds text to the list of things to be wrapped. The given text
+     * will be treated as a single unit and will not be split across
+     * lines.
+     *
+     * @param s Text to be added.
+     */
+    public void addNoSplit(String s) {
+	this.parts.add(s);
+    }
+
+    /**
+     * Clears any added text.
+     */
+    public void clear() {
+	this.parts.clear();
+    }
+
+    /**
+     * Wraps the text into the given width. The text is only
+     * broken at spaces, meaning the returned lines will not
+     * necessarily fit within width.
+     *
+     * @param width
+     */
+    public String[] wrap(int width) {
+	List lines = new ArrayList();
+	StringBuffer line = new StringBuffer();
+
+	Iterator iter = this.parts.iterator();
+	while (iter.hasNext()) {
+	    String s = (String) iter.next();
+	    if (line.length() > 0 &&
+		line.length() + s.length() > width) {
+		// part won't fit on the current line
+		lines.add(line.toString());
+		line.setLength(0);
+
+		if (s.length() > width) {
+		    lines.add(s);
+		} else {
+		    line.append(s);
+		}
+	    } else {
+		line.append(s);
+	    }
+	}
+
+	if (line.length() > 0) {
+	    lines.add(line.toString());
+	}
+
+	return (String[]) lines.toArray(new String[lines.size()]);
+    }
+								
+
+    //====================================================== PRIVATE
+
+
+}
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/Validator.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/Validator.java
new file mode 100644
index 0000000..ec6446a
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/Validator.java
@@ -0,0 +1,93 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.dom;
+
+import java.io.Serializable;
+import java.util.Set;
+
+/**
+ * Represents an object that can validate the structure of a document.
+ * Validators must be serializable.
+ */
+public interface Validator extends Serializable {
+
+    /**
+     * String indicating that character data is allowed at the given
+     * point in the document.
+     */
+    public static final String PCDATA = "#PCDATA";
+
+    /**
+     * Returns the AttributeDefinition for a particular attribute.
+     * @param element Name of the element.
+     * @param attribute Name of the attribute.
+     */
+    public AttributeDefinition getAttributeDefinition(String element, String attribute);
+
+    /**
+     * Returns the attribute definitions that apply to the given element.
+     * @param element Name of the element to check.
+     */
+    public AttributeDefinition[] getAttributeDefinitions(String element);
+
+    /**
+     * Returns a set of Strings representing valid root elements for the
+     * given document type.
+     */
+    public Set getValidRootElements();
+    
+    /**
+     * Returns a set of Strings representing items that are valid at
+     * point in the child nodes of a given element. Each string is either
+     * an element name or Validator.PCDATA.
+     *
+     * @param element Name of the parent element.
+     * @param prefix Array of strings representing nodes coming before the 
+     * insertion point
+     * @param suffix Array of strings representing nodes coming after the
+     * insertion point
+     */
+    public Set getValidItems(String element, String[] prefix, String[] suffix);
+
+    /**
+     * Returns true if the given sequence is valid for the given element.
+     * Accepts three sequences, which will be concatenated before doing
+     * the check. 
+     * 
+     * @param element Name of the element being tested.
+     * @param nodes Array of element names and Validator.PCDATA.
+     * @param partial If true, an valid but incomplete sequence is acceptable.
+     */
+    public boolean isValidSequence(
+        String element,
+        String[] nodes,
+        boolean partial);
+                                 
+    /**
+     * Returns true if the given sequence is valid for the given element.
+     * Accepts three sequences, which will be concatenated before doing
+     * the check. 
+     * 
+     * @param element Name of the element being tested.
+     * @param seq1 Array of element names and Validator.PCDATA.
+     * @param seq2 Array of element names and Validator.PCDATA. May be null or empty.
+     * @param seq3 Array of element names and Validator.PCDATA. May be null or empty.
+     * @param partial If true, an valid but incomplete sequence is acceptable.
+     */
+    public boolean isValidSequence(
+        String element,
+        String[] seq1,
+        String[] seq2,
+        String[] seq3,
+        boolean partial);
+
+}
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/package.html b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/package.html
new file mode 100644
index 0000000..2eca216
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/dom/package.html
@@ -0,0 +1,22 @@
+<?xml version='1.0'?>
+<html>
+
+  <head>
+
+    <title>net.sf.vex.dom</title>
+
+  </head>
+
+  <body>
+
+    <p>Classes implementing an object model for XML documents. The 
+    classes in this package are designed to be similar to those in the 
+    JDOM library. However, this package is unique in that content is 
+    represented by one single string of characters for the entire 
+    document. Implementing the model in this way simplifies the editor 
+    implementation.</p>
+
+  </body>
+
+</html>
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/AbstractBlockBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/AbstractBlockBox.java
new file mode 100644
index 0000000..71c2b6d
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/AbstractBlockBox.java
@@ -0,0 +1,922 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.wst.xml.vex.core.internal.core.Caret;
+import org.eclipse.wst.xml.vex.core.internal.core.Color;
+import org.eclipse.wst.xml.vex.core.internal.core.ColorResource;
+import org.eclipse.wst.xml.vex.core.internal.core.FontMetrics;
+import org.eclipse.wst.xml.vex.core.internal.core.Graphics;
+import org.eclipse.wst.xml.vex.core.internal.core.Insets;
+import org.eclipse.wst.xml.vex.core.internal.core.IntRange;
+import org.eclipse.wst.xml.vex.core.internal.css.CSS;
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheet;
+import org.eclipse.wst.xml.vex.core.internal.css.Styles;
+import org.eclipse.wst.xml.vex.core.internal.dom.Document;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.dom.Position;
+
+
+
+/**
+ * Base class of block boxes that can contain other block boxes. This class
+ * implements the layout method and various navigation methods. Subclasses must
+ * implement the createChildren method.
+ * 
+ * Subclasses can be anonymous or non-anonymous (i.e. generated by an element).
+ * Since the vast majority of instances will be non-anonymous, this class can
+ * manage the element and top and bottom margins without too much inefficiency.
+ * 
+ * <p>Subclasses that can be anonymous must override the getStartPosition and
+ * getEndPosition classes to return the range covered by the box.</p>
+ */
+public abstract class AbstractBlockBox extends AbstractBox implements BlockBox {
+
+
+    /**
+     * Class constructor for non-anonymous boxes.
+     * 
+     * @param context LayoutContext being used.
+     * @param parent Parent box.
+     * @param element Element associated with this box.
+     * anonymous box.
+     */
+    public AbstractBlockBox(LayoutContext context, BlockBox parent, Element element) {
+        
+        this.parent = parent;
+        this.element = element;
+
+        Styles styles = context.getStyleSheet().getStyles(element);
+        int parentWidth = parent.getWidth();
+        this.marginTop = styles.getMarginTop().get(parentWidth);
+        this.marginBottom = styles.getMarginBottom().get(parentWidth);
+        
+    }
+
+    /**
+     * Class constructor for anonymous boxes.
+     * 
+     * @param context LayoutContext to use.
+     * @param parent Parent box.
+     * @param startOffset Start of the range covered by the box.
+     * @param endOffset End of the range covered by the box.
+     */
+    public AbstractBlockBox(LayoutContext context, BlockBox parent, int startOffset, int endOffset) {
+        this.parent = parent;
+        this.marginTop = 0;
+        this.marginBottom = 0;
+
+        Document doc = context.getDocument();
+        this.startPosition = doc.createPosition(startOffset);
+        this.endPosition = doc.createPosition(endOffset);
+    }
+
+    /**
+     * Walks the box tree and returns the nearest enclosing element.
+     */
+    protected Element findContainingElement() {
+        BlockBox box = this;
+        Element element = box.getElement();
+        while (element == null) {
+            box = box.getParent();
+            element = box.getElement();
+        }
+        return element;
+    }
+    
+    /**
+     * Returns this box's children as an array of BlockBoxes.
+     */
+    protected BlockBox[] getBlockChildren() {
+        return (BlockBox[]) this.getChildren();
+    }
+
+
+    public Caret getCaret(LayoutContext context, int offset) {
+        
+        // If we haven't yet laid out this block, estimate the caret.
+        if (this.getLayoutState() != LAYOUT_OK) {
+            int relative = offset - this.getStartOffset();
+            int size = this.getEndOffset() - this.getStartOffset();
+            int y = 0;
+            if (size > 0) {
+                y = this.getHeight() * relative / size;
+            }
+            return new HCaret(0, y, this.getHCaretWidth()); 
+        }
+    
+        int y;
+    
+        Box[] children = this.getContentChildren();
+        for (int i = 0; i < children.length; i++) {
+            
+            if (offset < children[i].getStartOffset()) {
+                if (i > 0) {
+                    y = (children[i-1].getY() + children[i-1].getHeight() + children[i].getY()) / 2;
+                } else {
+                    y = 0;
+                }
+                return new HCaret(0, y, this.getHCaretWidth());
+            }
+            
+            if (offset >= children[i].getStartOffset()
+                && offset <= children[i].getEndOffset()) {
+                
+                Caret caret = children[i].getCaret(context, offset);
+                caret.translate(children[i].getX(), children[i].getY());
+                return caret;
+            }
+        }
+        
+        if (this.hasChildren()) {
+            y = this.getHeight();
+        } else {
+            y = this.getHeight() / 2;
+        }
+        
+        return new HCaret(0, y, this.getHCaretWidth());
+    }
+
+
+    
+    public Box[] getChildren() {
+        return this.children;
+    }
+    
+    /**
+     * Return an array of children that contain content.
+     */
+    protected BlockBox[] getContentChildren() {
+        Box[] children = this.getChildren();
+        List contentChildren = new ArrayList(children.length);
+        for (int i = 0; i < children.length; i++) {
+            if (children[i].hasContent()) {
+                contentChildren.add(children[i]);
+            }
+        }
+        return (BlockBox[]) contentChildren.toArray(new BlockBox[contentChildren.size()]);
+    }
+
+    public Element getElement() {
+        return this.element;
+    }
+
+    public int getEndOffset() {
+        Element element = this.getElement();
+        if (element != null) {
+            return element.getEndOffset();
+        } else if (this.getEndPosition() != null) {
+            return this.getEndPosition().getOffset();
+        } else {
+            throw new IllegalStateException();
+        }
+    }
+    
+
+    /**
+     * Returns the estimated size of the box, based on the the current font
+     * size and the number of characters covered by the box. This is a utility
+     * method that can be used in implementation of setInitialSize. It assumes
+     * the width of the box has already been correctly set.
+     * 
+     * @param context LayoutContext to use.
+     */
+    protected int getEstimatedHeight(LayoutContext context) {
+    
+        Element element = this.findContainingElement();
+        Styles styles = context.getStyleSheet().getStyles(element);
+        int charCount = this.getEndOffset() - this.getStartOffset();
+    
+        float fontSize = styles.getFontSize();
+        float lineHeight = styles.getLineHeight();
+        float estHeight = lineHeight * fontSize * 0.6f * charCount / this.getWidth();
+        
+        return Math.round(Math.max(estHeight, lineHeight));
+    }
+
+    public LineBox getFirstLine() {
+        if (this.hasChildren()) {
+            BlockBox firstChild = (BlockBox) this.getChildren()[0];
+            return firstChild.getFirstLine();
+        } else {
+            return null;    
+        }
+    }
+
+    /**
+     * Returns the width of the horizontal caret. This is overridden
+     * by TableBox to return a caret that is the full width of the table.
+     */
+    protected int getHCaretWidth() {
+        return H_CARET_LENGTH;
+    }
+
+    public Insets getInsets(LayoutContext context, int containerWidth) {
+        
+        if (this.getElement() != null) {
+            Styles styles = context.getStyleSheet().getStyles(this.getElement());
+            
+            int top = this.marginTop
+            + styles.getBorderTopWidth()
+            + styles.getPaddingTop().get(containerWidth);
+            
+            int left = styles.getMarginLeft().get(containerWidth)
+            + styles.getBorderLeftWidth()
+            + styles.getPaddingLeft().get(containerWidth);
+            
+            int bottom = this.marginBottom
+            + styles.getBorderBottomWidth()
+            + styles.getPaddingBottom().get(containerWidth);
+            
+            int right = styles.getMarginRight().get(containerWidth)
+            + styles.getBorderRightWidth()
+            + styles.getPaddingRight().get(containerWidth);
+            
+            return new Insets(top, left, bottom, right);
+        } else {
+            return new Insets(this.marginTop, 0, this.marginBottom, 0);
+        }
+    }
+
+    public LineBox getLastLine() {
+        if (this.hasChildren()) {
+            BlockBox lastChild = (BlockBox) this.getChildren()[this.getChildren().length - 1]; 
+            return lastChild.getLastLine();
+        } else {
+            return null;    
+        }
+    }
+
+    /**
+     * Returns the layout state of this box.
+     */
+    protected byte getLayoutState() {
+        return this.layoutState;
+    }
+    
+    public int getLineEndOffset(int offset) {
+        BlockBox[] children = this.getContentChildren();
+        for (int i = 0; i < children.length; i++) {
+            if (children[i].containsOffset(offset)) {
+                return children[i].getLineEndOffset(offset);
+            }
+        }
+        return offset;
+    }
+    
+    public int getLineStartOffset(int offset) {
+        BlockBox[] children = this.getContentChildren();
+        for (int i = 0; i < children.length; i++) {
+            if (children[i].containsOffset(offset)) {
+                return children[i].getLineStartOffset(offset);
+            }
+        }
+        return offset;
+    }
+
+
+    public int getMarginBottom() {
+        return this.marginBottom;
+    }
+
+    public int getMarginTop() {
+        return this.marginTop;
+    }
+
+    public int getNextLineOffset(LayoutContext context, int offset, int x) {
+
+        //
+        // This algorithm works when this block owns the offsets between
+        // its children.
+        //
+
+        if (offset == this.getEndOffset()) {
+            return -1;
+        }
+        
+        BlockBox[] children = this.getContentChildren();
+
+        if (offset < this.getStartOffset()
+                && children.length > 0
+                && children[0].getStartOffset() > this.getStartOffset()) {
+            //
+            // If there's an offset before the first child, put the caret there.
+            //
+            return this.getStartOffset();
+        }
+        
+        for (int i = 0; i < children.length; i++) {
+            BlockBox child = children[i];
+            if (offset <= child.getEndOffset()) {
+                int newOffset = child.getNextLineOffset(context, offset, x - child.getX());
+                if (newOffset < 0 /* && i < children.length-1 */) {
+                    return child.getEndOffset() + 1;
+                } else {
+                    return newOffset;
+                }
+            }
+        }
+        
+        return this.getEndOffset();
+    }
+
+    public BlockBox getParent() {
+        return this.parent;
+    }
+
+    public int getPreviousLineOffset(LayoutContext context, int offset, int x) {
+        
+        if (offset == this.getStartOffset()) {
+            return -1;
+        }
+        
+        BlockBox[] children = this.getContentChildren();
+
+        if (offset > this.getEndOffset()
+                && children.length > 0
+                && children[children.length-1].getEndOffset() < this.getEndOffset()) {
+            //
+            // If there's an offset after the last child, put the caret there.
+            //
+            return this.getEndOffset();
+        }
+        
+        for (int i = children.length; i > 0; i--) {
+            BlockBox child = children[i-1];
+            if (offset >= child.getStartOffset()) {
+                int newOffset = child.getPreviousLineOffset(context, offset, x - child.getX());
+                if (newOffset < 0 && i > 0) {
+                    return child.getStartOffset() - 1;
+                } else {
+                    return newOffset;
+                }
+            }
+        }
+
+        return this.getStartOffset();
+    }
+
+    
+    public int getStartOffset() {
+        Element element = this.getElement();
+        if (element != null) {
+            return element.getStartOffset() + 1;
+        } else if (this.getStartPosition() != null) {
+            return this.getStartPosition().getOffset();
+        } else {
+            throw new IllegalStateException();
+        }
+    }
+    
+    public boolean hasContent() {
+        return true;
+    }
+
+    public void invalidate(boolean direct) {
+        
+        if (direct) {
+            this.layoutState = LAYOUT_REDO;
+        } else {
+            this.layoutState = LAYOUT_PROPAGATE;
+        }
+        
+        if (this.getParent() instanceof AbstractBlockBox) {
+            ((AbstractBlockBox) this.getParent()).invalidate(false);
+        }
+    }
+
+    public boolean isAnonymous() {
+        return this.getElement() == null;
+    }
+
+    /**
+     * Call the given callback for each child matching one of the given
+     * display styles. Any nodes that do not match one of the given display types
+     * cause the onRange callback to be called, with a range covering all such
+     * contiguous nodes.
+     * 
+     * @param styleSheet StyleSheet from which to determine display styles.
+     * @param displayStyles Display types to be explicitly recognized.
+     * @param callback DisplayStyleCallback through which the caller is notified
+     * of matching elements and non-matching ranges.
+     */
+    protected void iterateChildrenByDisplayStyle(StyleSheet styleSheet, Set displayStyles, ElementOrRangeCallback callback) {
+        LayoutUtils.iterateChildrenByDisplayStyle(styleSheet, displayStyles, this.findContainingElement(), this.getStartOffset(), this.getEndOffset(), callback);
+    }
+    
+    public void paint(LayoutContext context, int x, int y) {
+
+        if (this.skipPaint(context, x, y)) {
+            return;
+        }
+        
+        boolean drawBorders = !context.isElementSelected(this.getElement());
+        
+        this.drawBox(context, x, y, this.getParent().getWidth(), drawBorders);
+        
+        this.paintChildren(context, x, y);
+        
+        this.paintSelectionFrame(context, x, y, true);
+    }
+    
+    /**
+     * Default implementation. Width is calculated as the parent's width minus
+     * this box's insets. Height is calculated by getEstimatedHeight.
+     */
+    public void setInitialSize(LayoutContext context) {
+        int parentWidth = this.getParent().getWidth();
+        Insets insets = this.getInsets(context, parentWidth);
+        this.setWidth(Math.max(0, parentWidth - insets.getLeft() - insets.getRight()));
+        this.setHeight(this.getEstimatedHeight(context));
+    }
+
+    public int viewToModel(LayoutContext context, int x, int y) {
+
+        Box[] children = this.getChildren();
+        
+        if (children == null) {
+            int charCount = this.getEndOffset() - this.getStartOffset() - 1;
+            if (charCount == 0 || this.getHeight() == 0) {
+                return this.getEndOffset();
+            } else {
+                return this.getStartOffset() 
+                    + charCount * y / this.getHeight();
+            }
+        } else {
+            for (int i = 0; i < children.length; i++) {
+                Box child = children[i];
+                if (!child.hasContent()) {
+                    continue;
+                }
+                if (y < child.getY()) {
+                    return child.getStartOffset() - 1;
+                } else if (y < child.getY() + child.getHeight()) {
+                    return child.viewToModel(context, x - child.getX(), y
+                            - child.getY());
+                }
+            }
+        }
+
+        return this.getEndOffset();
+    }
+
+
+    //===================================================== PRIVATE
+    
+    private BlockBox parent;
+    private Box[] children;
+    
+    /**
+     * Paint a frame that indicates a block element box has been selected.
+     * 
+     * @param context LayoutContext to use.
+     * @param x x-coordinate at which to draw
+     * @param y y-coordinate at which to draw.
+     * @param selected
+     */
+    protected void paintSelectionFrame(LayoutContext context, int x, int y, boolean selected) {
+
+        Element element = this.getElement();
+        Element parent = element == null ? null : element.getParent();
+        
+        boolean paintFrame = context.isElementSelected(element)
+        && !context.isElementSelected(parent);
+        
+        if (!paintFrame) {
+            return;
+        }
+
+        Graphics g = context.getGraphics();
+        ColorResource foreground;
+        ColorResource background;
+        
+        if (selected) {
+            foreground = g.getSystemColor(ColorResource.SELECTION_FOREGROUND);
+            background = g.getSystemColor(ColorResource.SELECTION_BACKGROUND);
+        } else {
+            foreground = g.createColor(new Color(0, 0, 0));
+            background = g.createColor(new Color(0xcc, 0xcc, 0xcc));
+        }
+        
+        FontMetrics fm = g.getFontMetrics();
+        ColorResource oldColor = g.setColor(background);
+        g.setLineStyle(Graphics.LINE_SOLID);
+        g.setLineWidth(1);
+        int tabWidth = g.stringWidth(this.getElement().getName()) + fm.getLeading();
+        int tabHeight = fm.getHeight();
+        int tabX = x + this.getWidth() - tabWidth;
+        int tabY = y + this.getHeight() - tabHeight;
+        g.drawRect(x, y, this.getWidth(), this.getHeight());
+        g.fillRect(tabX, tabY, tabWidth, tabHeight);
+        g.setColor(foreground);
+        g.drawString(this.getElement().getName(), tabX + fm.getLeading() / 2, tabY);
+        
+        g.setColor(oldColor);
+        if (!selected) {
+            foreground.dispose();
+            background.dispose();
+        }
+    }
+
+
+
+    /** Layout is OK */
+    public static final byte LAYOUT_OK = 0;
+    
+    /** My layout is OK, but one of my children needs to be laid out */
+    public static final byte LAYOUT_PROPAGATE = 1;
+    
+    /** I need to be laid out */
+    public static final byte LAYOUT_REDO = 2;
+    
+    private byte layoutState = LAYOUT_REDO;
+
+    public IntRange layout(LayoutContext context, int top, int bottom) {
+    
+        int repaintStart = Integer.MAX_VALUE;
+        int repaintEnd = 0;
+        boolean repaintToBottom = false;
+        int originalHeight = this.getHeight();
+        
+        if (this.layoutState == LAYOUT_REDO) {
+
+//            System.out.println("Redo layout of " + this.getElement().getName());
+            
+            List childList = this.createChildren(context);
+            this.children = (BlockBox[]) childList.toArray(new BlockBox[childList.size()]);
+    
+            // Even though we position children after layout, we have to 
+            // do a preliminary positioning here so we now which ones 
+            // overlap our layout band
+            for (int i = 0; i < this.children.length; i++) {
+                BlockBox child = (BlockBox) this.children[i];
+                child.setInitialSize(context);
+            }
+            this.positionChildren(context);
+        
+            // repaint everything
+            repaintToBottom = true;
+            repaintStart = 0;
+        }
+    
+        Box[] children = this.getChildren();
+        for (int i = 0; i < children.length; i++) {
+            if (children[i] instanceof BlockBox) {
+                BlockBox child = (BlockBox) children[i];
+                if (top <= child.getY() + child.getHeight() &&
+                        bottom >= child.getY()) {
+                    
+                    IntRange repaintRange = child.layout(context, top - child.getY(), bottom - child.getY());
+                    if (repaintRange != null) {
+                        repaintStart = Math.min(repaintStart, repaintRange.getStart() + child.getY());
+                        repaintEnd = Math.max(repaintEnd, repaintRange.getEnd() + child.getY());
+                    }
+                }
+            }
+        }
+    
+        int childRepaintStart = this.positionChildren(context);
+        if (childRepaintStart != -1) {
+            repaintToBottom = true;
+            repaintStart = Math.min(repaintStart, childRepaintStart);
+        }
+        
+        this.layoutState = LAYOUT_OK;
+        
+        if (repaintToBottom) {
+            repaintEnd = Math.max(originalHeight, this.getHeight());
+        }
+        
+        if (repaintStart < repaintEnd) {
+            return new IntRange(repaintStart, repaintEnd);
+        } else {
+            return null;
+        }
+    }
+
+    protected abstract List createChildren(LayoutContext context);
+
+    /**
+     * Creates a list of block boxes for a given document range. beforeInlines
+     * and afterInlines are prepended/appended to the first/last block child, 
+     * and each may be null.
+     */
+    protected List createBlockBoxes(LayoutContext context, int startOffset, int endOffset, int width, List beforeInlines, List afterInlines) {
+        
+        List blockBoxes = new ArrayList();
+        List pendingInlines = new ArrayList();
+        
+        if (beforeInlines != null) {
+            pendingInlines.addAll(beforeInlines);
+        }
+        
+        Element element = context.getDocument().findCommonElement(startOffset, endOffset);
+        
+        if (startOffset == endOffset) {
+            int relOffset = startOffset - element.getStartOffset();
+            pendingInlines.add(new PlaceholderBox(context, element, relOffset));
+        } else {
+            
+            BlockInlineIterator iter = new BlockInlineIterator(context, element, startOffset, endOffset);
+            
+            while (true) {
+
+                Object next = iter.next();
+                
+                if (next == null) {
+                    break;
+                }
+                
+                if (next instanceof IntRange) {
+                    
+                    IntRange range = (IntRange) next;
+                    
+                    InlineElementBox.InlineBoxes inlineBoxes = 
+                        InlineElementBox.createInlineBoxes(context, element, range.getStart(), range.getEnd());
+                    pendingInlines.addAll(inlineBoxes.boxes);
+                    pendingInlines.add(new PlaceholderBox(context, element, range.getEnd() - element.getStartOffset()));
+                    
+                } else {
+
+                    if (pendingInlines.size() > 0) {
+                        blockBoxes.add(ParagraphBox.create(context, element, pendingInlines, width));
+                        pendingInlines.clear();
+                    }
+                    
+                    if (isTableChild(context, next)) {
+                        
+                        // Consume continguous table children and create an
+                        // anonymous table.
+                        
+                        int tableStartOffset = ((Element) next).getStartOffset();
+                        int tableEndOffset = -1; // dummy to hide warning
+                        while (isTableChild(context, next)) {
+                            tableEndOffset = ((Element) next).getEndOffset() + 1;
+                            next = iter.next();
+                        }
+                        
+                        // add anonymous table
+                        blockBoxes.add(new TableBox(context, this, tableStartOffset, tableEndOffset));
+                        
+                        if (next == null) {
+                            break;
+                        } else {
+                            iter.push(next);
+                        }
+                        
+                    } else { // next is a block box element
+                        Element blockElement = (Element) next;
+                        blockBoxes.add(context.getBoxFactory().createBox(context, blockElement, this, width));
+                    }
+                }
+            }
+        }
+        
+        if (afterInlines != null) {
+            pendingInlines.addAll(afterInlines);
+        }
+        
+        if (pendingInlines.size() > 0) {
+            blockBoxes.add(ParagraphBox.create(context, element, pendingInlines, width));
+            pendingInlines.clear();
+        }
+        
+        return blockBoxes;
+    }
+
+    private class BlockInlineIterator {
+        
+        public BlockInlineIterator(LayoutContext context, Element element, int startOffset, int endOffset) {
+            this.context = context;
+            this.element = element;
+            this.startOffset = startOffset;
+            this.endOffset = endOffset;
+        }
+
+        /**
+         * Returns the next block element or inline range, or null if we're at the end.
+         */
+        public Object next() {
+            if (this.pushStack.size() > 0) {
+                return this.pushStack.removeLast();
+            } else if (startOffset == endOffset) {
+                return null;
+            } else {
+                Element blockElement = findNextBlockElement(this.context, this.element, startOffset, endOffset);
+                if (blockElement == null) {
+                    if (startOffset < endOffset) {
+                        IntRange result = new IntRange(startOffset, endOffset);
+                        startOffset = endOffset;
+                        return result;
+                    } else {
+                        return null;
+                    }
+                } else if (blockElement.getStartOffset() > startOffset) {
+                    this.pushStack.addLast(blockElement);
+                    IntRange result = new IntRange(startOffset, blockElement.getStartOffset());
+                    startOffset = blockElement.getEndOffset() + 1;
+                    return result;
+                } else {
+                    startOffset = blockElement.getEndOffset() + 1;
+                    return blockElement;
+                }
+            }
+        }
+
+        public Object peek() {
+            if (this.pushStack.size() == 0) {
+                Object next = next();
+                if (next == null) {
+                    return null;
+                } else {
+                    push(next);
+                }
+            }
+            return pushStack.getLast();
+        }
+        public void push(Object pushed) {
+            this.pushStack.addLast(pushed);
+        }
+        
+        private LayoutContext context;
+        private Element element;
+        private int startOffset;
+        private int endOffset;
+        private LinkedList pushStack = new LinkedList();
+    }
+
+    protected boolean hasChildren() {
+        return this.getChildren() != null && this.getChildren().length > 0;
+    }
+
+    /**
+     * Positions the children of this box. Vertical margins are collapsed here.
+     * Returns the vertical offset of the top of the first child to move, 
+     * or -1 if not children were actually moved.
+     */
+    protected int positionChildren(LayoutContext context) {
+        
+        int childY = 0;
+        int prevMargin = 0;
+        BlockBox[] children = this.getBlockChildren();
+        int repaintStart = -1;
+        
+        Styles styles = null;
+        
+        if (!this.isAnonymous()) {
+            styles = context.getStyleSheet().getStyles(this.getElement());
+        }
+    
+        if (styles != null && children.length > 0) {
+            if (styles.getBorderTopWidth() + styles.getPaddingTop().get(this.getWidth()) == 0) {
+                // first child's top margin collapses into ours
+                this.marginTop = Math.max(this.marginTop, children[0].getMarginTop());
+                childY -= children[0].getMarginTop();
+            }
+        }
+        
+        
+        for (int i = 0; i < children.length; i++) {
+            
+            Insets insets = children[i].getInsets(context, this.getWidth());
+            
+            childY += insets.getTop();
+            
+            if (i > 0) {
+                childY -= Math.min(prevMargin, 
+                                   children[i].getMarginTop());
+            }
+            
+            if (repaintStart == -1 && children[i].getY() != childY) {
+                repaintStart = Math.min(children[i].getY(), childY);
+            }
+    
+            children[i].setX(insets.getLeft());
+            children[i].setY(childY);
+            
+            childY += children[i].getHeight() + insets.getBottom();
+            prevMargin = children[i].getMarginBottom();
+        }
+    
+        if (styles != null && children.length > 0) {
+            if (styles.getBorderBottomWidth() + styles.getPaddingBottom().get(this.getWidth()) == 0) {
+                // last child's bottom margin collapses into ours
+                this.marginBottom = Math.max(this.marginBottom, prevMargin);
+                childY -= prevMargin;
+            }
+        }
+        
+        this.setHeight(childY);
+        
+        return repaintStart;
+    }
+
+
+    /**
+     * Sets the layout state of the box.
+     * @param layoutState One of the LAYOUT_* constants
+     */
+    protected void setLayoutState(byte layoutState) {
+        this.layoutState = layoutState;
+    }
+    
+
+
+    //========================================================= PRIVATE
+    /** The length, in pixels, of the horizontal caret between block boxes */
+    private static final int H_CARET_LENGTH = 20;
+    
+
+    /**
+     * Element with which we are associated. For anonymous boxes, this is null.
+     */
+    private Element element;
+    
+    /*
+     * We cache the top and bottom margins, since they may be affected by
+     * our children.
+     */
+    private int marginTop;
+    private int marginBottom;
+
+    /**
+     * Start position of an anonymous box. For non-anonymous boxes, this is null.
+     */
+    private Position startPosition;
+
+    /**
+     * End position of an anonymous box. For non-anonymous boxes, this is null.
+     */
+    private Position endPosition;
+
+    /**
+     * Searches for the next block-formatted child.
+     * @param context LayoutContext to use.
+     * @param element Element within which to search.
+     * @param startOffset The offset at which to start the search.
+     * @param endOffset The offset at which to end the search.
+     */
+    private static Element findNextBlockElement(LayoutContext context, Element element, int startOffset, int endOffset) {
+        
+        Element[] children = element.getChildElements();
+        for (int i = 0; i < children.length; i++) {
+            Element child = children[i];
+            if (child.getEndOffset() < startOffset) {
+                continue;
+            } else if (child.getStartOffset() >= endOffset) {
+                break;
+            } else {
+                Styles styles = context.getStyleSheet().getStyles(child);
+                if (!styles.getDisplay().equals(CSS.INLINE)) { // TODO do proper block display determination
+                    return child;
+                } else {
+                    Element fromChild = findNextBlockElement(context, child, startOffset, endOffset);
+                    if (fromChild != null) {
+                        return fromChild;
+                    }
+                }
+            }
+        }
+        
+        return null;
+    }
+
+    /**
+     * Return the end position of an anonymous box. The default implementation
+     * returns null.
+     */
+    private Position getEndPosition() {
+        return this.endPosition;
+    }
+    
+    /**
+     * Return the start position of an anonymous box. The default implementation
+     * returns null.
+     */
+    private Position getStartPosition() {
+        return this.startPosition;
+    }
+
+    private boolean isTableChild(LayoutContext context, Object rangeOrElement) {
+        if (rangeOrElement != null && rangeOrElement instanceof Element) {
+            return LayoutUtils.isTableChild(context.getStyleSheet(), (Element) rangeOrElement);
+        } else {
+            return false;
+        }
+    }
+    
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/AbstractBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/AbstractBox.java
new file mode 100644
index 0000000..9040b70
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/AbstractBox.java
@@ -0,0 +1,415 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import org.eclipse.wst.xml.vex.core.internal.core.Caret;
+import org.eclipse.wst.xml.vex.core.internal.core.Color;
+import org.eclipse.wst.xml.vex.core.internal.core.ColorResource;
+import org.eclipse.wst.xml.vex.core.internal.core.Graphics;
+import org.eclipse.wst.xml.vex.core.internal.core.Insets;
+import org.eclipse.wst.xml.vex.core.internal.core.Rectangle;
+import org.eclipse.wst.xml.vex.core.internal.css.CSS;
+import org.eclipse.wst.xml.vex.core.internal.css.Styles;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+
+
+/**
+ * Base implementation of the <code>Box</code> interface, implementing
+ * some common methods.
+ */
+public abstract class AbstractBox implements Box {
+
+    private static final Box[] EMPTY_BOX_ARRAY = new Box[0];
+    
+    private int x;
+    private int y;
+    private int width = -1;
+    private int height = -1;
+
+    /**
+     * Class constructor.
+     */
+    public AbstractBox() {
+    }
+
+    /**
+     * Returns true if the given offset is between startOffset and 
+     * endOffset, inclusive.
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#containsOffset(int)
+     */
+    public boolean containsOffset(int offset) {
+        return offset >= this.getStartOffset() && offset <= this.getEndOffset();
+    }
+    
+    /**
+     * Throws <code>IllegalStateException</code>. Boxes with content must
+     * provide an implementation of this method.
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getCaret(org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext, int)
+     */
+    public Caret getCaret(LayoutContext context, int offset) {
+        throw new IllegalStateException();
+    }
+
+    /**
+     * Returns an empty array of children.
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getChildren()
+     */
+    public Box[] getChildren() {
+        return EMPTY_BOX_ARRAY;
+    }
+    
+    /**
+     * Returns null. Boxes associated with
+     * elements must provide an implementation of this method.
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getElement()
+     */
+    public Element getElement() {
+        return null;
+    }
+
+    /**
+     * Throws <code>IllegalStateException</code>. Boxes with content must
+     * provide an implementation of this method.
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getEndOffset()
+     */
+    public int getEndOffset() {
+        throw new IllegalStateException();
+    }
+
+    /**
+     * Returns the height set with <code>setHeight</code>.
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getHeight()
+     */
+    public int getHeight() {
+        return this.height;
+    }
+
+    /**
+     * Throws <code>IllegalStateException</code>. Boxes with content must
+     * provide an implementation of this method.
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getStartOffset()
+     */
+    public int getStartOffset() {
+        throw new IllegalStateException();
+    }
+
+    /**
+     * Returns the insets of this box, which is the sum of the margin, border,
+     * and padding on each side. If no element is associated with this box
+     * returns all zeros.
+     */
+    public Insets getInsets(LayoutContext context, int containerWidth) {
+        Element element = this.getElement();
+        if (element == null) {
+            return Insets.ZERO_INSETS;
+        } else {
+            return getInsets(context.getStyleSheet().getStyles(element), containerWidth);
+        }
+    }
+    
+    /**
+     * Returns false. Boxes with content must override this method and return
+     * true, and must provide implementations for the following methods.
+     * 
+     * <ul>
+     * <li>{@link Box#getCaretShapes}</li>
+     * <li>{@link Box#getStartOffset}</li>
+     * <li>{@link Box#getEndOffset}</li>
+     * <li>{@link Box#viewToModel}</li>
+     * </ul>
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#hasContent()
+     */
+    public boolean hasContent() {
+        return false;
+    }
+    
+    public boolean isAnonymous() {
+        return true;
+    }
+
+    /**
+     * Returns the width set with <code>setWidth</code>.
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getWidth()
+     */    
+    public int getWidth() {
+        return this.width;
+    }
+    
+    /**
+     * Returns the value set with <code>setX</code>.
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getX()
+     */
+    public int getX() {
+        return this.x;
+    }
+
+    /**
+     * Returns the value set with <code>setY</code>.
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getY()
+     */
+    public int getY() {
+        return this.y;
+    }
+
+    /**
+     * Paint all children of this box.
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#paint(org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext, int, int)
+     */
+    public void paint(LayoutContext context, int x, int y) {
+
+        if (this.skipPaint(context, x, y)) {
+            return;
+        }
+        
+        this.paintChildren(context, x, y);
+    }
+    
+    /**
+     * Paint the children of this box.
+     * @param context LayoutContext to use.
+     * @param x x-coordinate at which to paint
+     * @param y y-coordinate at which to paint
+     */
+    protected void paintChildren(LayoutContext context, int x, int y) {
+        Box[] children = this.getChildren();
+        for (int i = 0; children != null && i < children.length; i++) {
+            Box child = children[i];
+            child.paint(context, x + child.getX(), y + child.getY());
+        }
+    }
+
+
+    public void setHeight(int height) {
+        this.height = height;
+    }
+
+    public void setWidth(int width) {
+	this.width = width;
+    }
+
+    public void setX(int x) {
+        this.x = x;
+    }
+    
+    public void setY(int y) {
+        this.y = y;
+    }
+    
+    /**
+     * Returns true if this box is outside the clip
+     * region. Implementations of <code>paint</code> should use this
+     * to avoid unnecessary painting.
+     *
+     * @param context <code>LayoutContext</code> in effect.
+     * @param x the x-coordinate at which the box is being painted
+     * @param y the y-coordinate at which the box is being painted
+     */
+    protected boolean skipPaint(LayoutContext context, int x, int y) {
+	Rectangle clipBounds = context.getGraphics().getClipBounds();
+
+	return clipBounds.getY() + clipBounds.getHeight() <= y
+	    || clipBounds.getY() >= y + this.getHeight();
+
+    }
+
+    /**
+     * Throws <code>IllegalStateException</code>. Boxes with content must
+     * provide an implementation of this method.
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#viewToModel(org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext, int, int)
+     */
+    public int viewToModel(LayoutContext context, int x, int y) {
+        throw new IllegalStateException();
+    }
+
+    /**
+     * Draws the background and borders of a CSS-styled box.
+     * 
+     * @param context LayoutContext used for drawing.
+     * @param x x-coordinate of the left side of the box
+     * @param y y-coordinate of the top of the box
+     * @param containerWidth width of the containing client area. Used for calculating
+     * padding expressed as a percentage.
+     * @param drawBorders If true, the background is filled and the borders are drawn;
+     * otherwise, just the background is filled. This is handy when removing the 
+     * borders when drawing the selection frame.
+     */
+    protected void drawBox(LayoutContext context, int x, int y, int containerWidth, boolean drawBorders) {
+        this.drawBox(context, this.getElement(), x, y, containerWidth, drawBorders);
+    }
+    
+    /**
+     * Draws the background and borders of a CSS-styled box.
+     * 
+     * @param context LayoutContext used for drawing.
+     * @param element Element to use when determining styles. This is used by
+     * TableBodyBox to specify the corresponding table element.
+     * @param x x-coordinate of the left side of the box
+     * @param y y-coordinate of the top of the box
+     * @param containerWidth width of the containing client area. Used for calculating
+     * padding expressed as a percentage.
+     * @param drawBorders If true, the background is filled and the borders are drawn;
+     * otherwise, just the background is filled. This is handy when removing the 
+     * borders when drawing the selection frame.
+     */
+    protected void drawBox(LayoutContext context, Element element, int x, int y, int containerWidth, boolean drawBorders) {
+        
+        if (element == null) {
+            return;
+        }
+        
+        Graphics g = context.getGraphics();
+        Styles styles = context.getStyleSheet().getStyles(element);
+        
+        boolean hasLeft = true;
+        boolean hasRight = true;
+        int left = x - styles.getPaddingLeft().get(containerWidth) - styles.getBorderLeftWidth();
+        int top = y - styles.getPaddingTop().get(containerWidth) - styles.getBorderTopWidth();
+        int right = x + this.getWidth() + styles.getPaddingRight().get(containerWidth) + styles.getBorderRightWidth();
+        int bottom = y + this.getHeight() + styles.getPaddingBottom().get(containerWidth) + styles.getBorderBottomWidth();
+        
+        if (this instanceof InlineElementBox) {
+            // TODO fix boxes for inline elements
+            hasLeft = this.getStartOffset() == element.getStartOffset() + 1;
+            hasRight = this.getEndOffset() == element.getEndOffset();
+            if (hasLeft) {
+                //left += styles.getMarginLeft().get(0);
+            }
+            if (hasRight) {
+                //right -= styles.getMarginRight().get(0);
+            }
+            //top = y - styles.getPaddingTop().get(0) - styles.getBorderTopWidth();
+            //bottom = y + box.getHeight() + styles.getPaddingBottom().get(0) + styles.getBorderBottomWidth();
+        }
+        
+        Color backgroundColor = styles.getBackgroundColor();
+        
+        if (backgroundColor != null) {
+            ColorResource color = g.createColor(backgroundColor);
+            ColorResource oldColor = g.setColor(color);
+            g.fillRect(left, top, right - left, bottom - top); 
+            g.setColor(oldColor);
+            color.dispose();
+        }
+        
+        
+        if (drawBorders) {
+            //        Object oldAntiAlias =
+            //            g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
+            //
+            //        g.setRenderingHint(
+            //            RenderingHints.KEY_ANTIALIASING,
+            //            RenderingHints.VALUE_ANTIALIAS_OFF);
+            boolean oldAntiAlias = g.isAntiAliased();
+            g.setAntiAliased(false);
+
+            int bw2 = styles.getBorderBottomWidth() / 2;
+            int lw2 = styles.getBorderLeftWidth() / 2;
+            int rw2 = styles.getBorderRightWidth() / 2;
+            int tw2 = styles.getBorderTopWidth() / 2;
+
+            // Bottom border
+            if (styles.getBorderBottomWidth() > 0) {
+                ColorResource color = g.createColor(styles
+                        .getBorderBottomColor());
+                ColorResource oldColor = g.setColor(color);
+                g.setLineStyle(lineStyle(styles.getBorderBottomStyle()));
+                g.setLineWidth(styles.getBorderBottomWidth());
+                g.drawLine(left + bw2, bottom - bw2 - 1, right - bw2, bottom
+                        - bw2 - 1);
+                g.setColor(oldColor);
+                color.dispose();
+            }
+
+            // Left border
+            if (hasLeft && styles.getBorderLeftWidth() > 0) {
+                ColorResource color = g
+                        .createColor(styles.getBorderLeftColor());
+                ColorResource oldColor = g.setColor(color);
+                g.setLineStyle(lineStyle(styles.getBorderLeftStyle()));
+                g.setLineWidth(styles.getBorderLeftWidth());
+                g.drawLine(left + lw2, top + lw2, left + lw2, bottom - lw2 - 1);
+                g.setColor(oldColor);
+                color.dispose();
+            }
+
+            // Right border
+            if (hasRight && styles.getBorderRightWidth() > 0) {
+                ColorResource color = g.createColor(styles
+                        .getBorderRightColor());
+                ColorResource oldColor = g.setColor(color);
+                g.setLineStyle(lineStyle(styles.getBorderRightStyle()));
+                g.setLineWidth(styles.getBorderRightWidth());
+                g.drawLine(right - rw2 - 1, top + rw2, right - rw2 - 1, bottom
+                        - rw2 - 1);
+                g.setColor(oldColor);
+                color.dispose();
+            }
+
+            // Top border
+            if (styles.getBorderTopWidth() > 0) {
+                ColorResource color = g.createColor(styles.getBorderTopColor());
+                ColorResource oldColor = g.setColor(color);
+                g.setLineStyle(lineStyle(styles.getBorderTopStyle()));
+                g.setLineWidth(styles.getBorderTopWidth());
+                g.drawLine(left + tw2, top + tw2, right - tw2, top + tw2);
+                g.setColor(oldColor);
+                color.dispose();
+            }
+
+            //        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldAntiAlias);
+            g.setAntiAliased(oldAntiAlias);
+            
+        }
+    }
+
+    /**
+     * Convert a CSS line style string (e.g. "dotted") to the corresponding
+     * Graphics.LINE_XXX style.
+     */
+    private static int lineStyle(String style) {
+        if (style.equals(CSS.DOTTED)) {
+            return Graphics.LINE_DOT;
+        } else if (style.equals(CSS.DASHED)) {
+            return Graphics.LINE_DASH;
+        } else {
+            return Graphics.LINE_SOLID;
+        }
+        
+    }
+
+    /**
+     * Returns the insets for a CSS box with the given styles.
+     * @param styles Styles for the box.
+     * @param containerWidth Content area of the containing box.
+     */
+    public static Insets getInsets(Styles styles, int containerWidth) {
+        
+        int top = styles.getMarginTop().get(containerWidth)
+                + styles.getBorderTopWidth()
+                + styles.getPaddingTop().get(containerWidth);
+                
+        int left = styles.getMarginLeft().get(containerWidth)
+                 + styles.getBorderLeftWidth()
+                 + styles.getPaddingLeft().get(containerWidth);
+                
+        int bottom = styles.getMarginBottom().get(containerWidth)
+                   + styles.getBorderBottomWidth()
+                   + styles.getPaddingBottom().get(containerWidth);
+                
+        int right = styles.getMarginRight().get(containerWidth)
+                  + styles.getBorderRightWidth()
+                  + styles.getPaddingRight().get(containerWidth);
+                  
+        return new Insets(top, left, bottom, right);
+    }
+
+}
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/AnonymousBlockBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/AnonymousBlockBox.java
new file mode 100644
index 0000000..839bd42
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/AnonymousBlockBox.java
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import java.util.List;
+
+/**
+ * A block box that is not associated with a particular element.
+ */
+public class AnonymousBlockBox extends AbstractBlockBox {
+
+    public AnonymousBlockBox(
+            LayoutContext context, 
+            BlockBox parent,
+            int startOffset,
+            int endOffset) {
+        
+        super(context, parent, startOffset, endOffset);
+    }
+
+    protected List createChildren(LayoutContext context) {
+        return createBlockBoxes(context, this.getStartOffset(), this.getEndOffset(), this.getWidth(), null, null);
+    }
+
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/BlockBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/BlockBox.java
new file mode 100644
index 0000000..55c892a
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/BlockBox.java
@@ -0,0 +1,125 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import org.eclipse.wst.xml.vex.core.internal.core.IntRange;
+
+
+/**
+ * Represents a block box. Block boxes are stacked one on top of another.
+ */
+public interface BlockBox extends Box {
+    
+    /**
+     * Returns the first LineBox contained by this block, or null if the block
+     * contains no lines.
+     */
+    public LineBox getFirstLine();
+    
+    /**
+     * Returns the last LineBox contained by this block, or null if the block
+     * contains no lines.
+     */
+    public LineBox getLastLine();
+    
+    /**
+     * Returns the offset of the end of the line containing the given offset.
+     * @param offset offset identifying the current line.
+     */
+    public int getLineEndOffset(int offset);
+    
+    /**
+     * Returns the offset of the start of the line containing the given offset.
+     * @param offset offset identifying the current line.
+     */
+    public int getLineStartOffset(int offset);
+
+    /**
+     * Returns the bottom margin of this box.
+     */
+    public int getMarginBottom();
+    
+    /**
+     * Returns the top margin of this box.
+     */
+    public int getMarginTop();
+
+    /**
+     * Returns the offset on the next line that is closest to the given 
+     * x coordinate. The given offset may be before the start of this box
+     * in which case this method should return the offset of the first line
+     * in this box.
+     * @param context LayoutContext used for the layout
+     * @param offset the current offset
+     * @param x the x coordinate
+     */
+    public int getNextLineOffset(LayoutContext context, int offset, int x);
+    
+    /**
+     * Returns the offset on the previous line that is closest to the given 
+     * x coordinate. The given offset may be after the end of this box
+     * in which case this method should return the offset of the last line
+     * in this box.
+     * @param context LayoutContext used for the layout
+     * @param offset the current offset
+     * @param x the x coordinate
+     */
+    public int getPreviousLineOffset(LayoutContext context, int offset, int x);
+    
+    /**
+     * Returns the parent box of this box.
+     */
+    public BlockBox getParent();
+    
+    /**
+     * Informs this box that its layout has become invalid, and that it should
+     * re-do it the next time layout is called.
+     * @param direct If true, the box's content has changed and it must re-create
+     * it's children on the next call to layout. Otherwise, it should just 
+     * propagate the next layout call to its children.
+     */
+    public void invalidate(boolean direct);
+
+    /**
+     * Layout this box. This method is responsible for the following.
+     * 
+     * <ul>
+     * <li>Creating any child boxes.</li>
+     * <li>Calling layout on the child boxes.</li>
+     * <li>Positioning the child boxes (i.e. calling child.setX() and child.setY())</li>
+     * <li>Determining this box's height and width.</li>
+     * </ul>
+     * 
+     * <p>Boxes with no children should simply calculate their width and height
+     * here</p>
+     * 
+     * <p>This method is passed a vertical range to be layed out. Children
+     * falling outside this range need not be layed out.</p>
+     * 
+     * <p>This method returns an IntRange object representing the vertical
+     * range to re-draw due to layout change. Null may be returned if there
+     * were no changes that need to be re-drawn.</p>
+     * 
+     * @param context The layout context to be used.
+     * @param top Top of the range to lay out.
+     * @param bottom Bottom of the range to lay out.
+     */    
+    public IntRange layout(LayoutContext context, int top, int bottom);
+    
+    /**
+     * Sets the initial size of the box.
+     * 
+     * @param context LayoutContext to use.
+     */
+    public void setInitialSize(LayoutContext context);
+}
+
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/BlockElementBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/BlockElementBox.java
new file mode 100644
index 0000000..b4dab38
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/BlockElementBox.java
@@ -0,0 +1,385 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.wst.xml.vex.core.internal.VEXCorePlugin;
+import org.eclipse.wst.xml.vex.core.internal.core.Drawable;
+import org.eclipse.wst.xml.vex.core.internal.core.Graphics;
+import org.eclipse.wst.xml.vex.core.internal.core.Rectangle;
+import org.eclipse.wst.xml.vex.core.internal.css.CSS;
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheet;
+import org.eclipse.wst.xml.vex.core.internal.css.Styles;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+
+
+/**
+ * A block box corresponding to a DOM Element. Block boxes lay their
+ * children out stacked top to bottom. Block boxes correspond to the
+ * <code>display: block;</code> CSS property.
+ */
+public class BlockElementBox extends AbstractBlockBox {
+
+    /** hspace btn. list-item bullet and block, as fraction of font-size */
+    static final float BULLET_SPACE = 0.5f;
+    
+    /** vspace btn. list-item bullet and baseine, as fraction of font-size */
+    //private static final float BULLET_LIFT = 0.1f;
+    
+    /** number of boxes created since VM startup, for profiling */
+    private static int boxCount;
+
+    BlockBox beforeMarker;
+    
+    /**
+     * Class constructor. This box's children are not created here but in the first
+     * call to layout. Instead, we estimate the box's height here based on the given width.
+     * @param context LayoutContext used for this layout.
+     * @param parent This box's parent box.
+     * @param element Element to which this box corresponds.
+     */
+    public BlockElementBox(LayoutContext context, BlockBox parent, Element element) {
+        super(context, parent, element);
+    }
+
+    /**
+     * Returns the number of boxes created since VM startup. Used for profiling.
+     */
+    public static int getBoxCount() {
+        return boxCount;
+    }
+    
+    public int getEndOffset() {
+        return this.getElement().getEndOffset();
+    }
+
+    public int getStartOffset() {
+        return this.getElement().getStartOffset() + 1;
+    }
+
+    public boolean hasContent() {
+        return true;
+    }
+
+    public void paint(LayoutContext context, int x, int y) {
+        
+        super.paint(context, x, y);
+
+        if (this.beforeMarker != null) {
+            this.beforeMarker.paint(
+                context, 
+                x + this.beforeMarker.getX(),
+                y + this.beforeMarker.getY());
+        }
+    }
+
+    
+    protected int positionChildren(LayoutContext context) {
+
+        int repaintStart = super.positionChildren(context);
+
+        Styles styles = context.getStyleSheet().getStyles(this.getElement());
+        if (this.beforeMarker != null) {
+            int x = - this.beforeMarker.getWidth() - Math.round(BULLET_SPACE * styles.getFontSize()); 
+            int y = this.getFirstLineTop(context);
+            LineBox firstLine = this.getFirstLine();
+            if (firstLine != null) {
+                y += firstLine.getBaseline() - this.beforeMarker.getFirstLine().getBaseline();
+            }
+    
+            this.beforeMarker.setX(x);
+            this.beforeMarker.setY(y);
+        }
+    
+        return repaintStart;
+    }
+
+    public String toString() {
+	return "BlockElementBox: <" + this.getElement().getName() + ">"
+	    + "[x=" + this.getX()
+            + ",y=" + this.getY()
+            + ",width=" + this.getWidth()
+            + ",height=" + this.getHeight() + "]";
+    }
+
+
+    //===================================================== PRIVATE
+
+
+    /**
+     * Lays out the children as vertically stacked blocks. Runs of
+     * text and inline elements are wrapped in DummyBlockBox's. 
+     */
+    protected List createChildren(LayoutContext context) {
+        
+    	long start = 0;
+        if (VEXCorePlugin.getInstance().isDebugging()) {
+        	start = System.currentTimeMillis();
+        }
+        
+        Element element = this.getElement();
+        int width = this.getWidth();
+
+        List childList = new ArrayList();
+        
+        StyleSheet ss = context.getStyleSheet();
+        
+        // element and styles for generated boxes
+        Element genElement;
+        Styles genStyles;
+
+        // :before content
+        List beforeInlines = null;
+        genElement = context.getStyleSheet().getBeforeElement(this.getElement());
+        if (genElement != null) {
+            genStyles = ss.getStyles(genElement);
+            if (genStyles.getDisplay().equals(CSS.INLINE)) {
+                beforeInlines = new ArrayList();
+                beforeInlines.addAll(LayoutUtils.createGeneratedInlines(context, genElement));
+            } else {
+                childList.add(new BlockPseudoElementBox(context, genElement, this, width));
+            }
+        }
+        
+        // :after content
+        Box afterBlock = null;
+        List afterInlines = null;
+        genElement = context.getStyleSheet().getAfterElement(this.getElement());
+        if (genElement != null) {
+            genStyles = context.getStyleSheet().getStyles(genElement);
+            if (genStyles.getDisplay().equals(CSS.INLINE)) {
+                afterInlines = new ArrayList();
+                afterInlines.addAll(LayoutUtils.createGeneratedInlines(context, genElement));
+            } else {
+                afterBlock = new BlockPseudoElementBox(context, genElement, this, width);
+            }
+        }
+        
+        int startOffset = element.getStartOffset() + 1;
+        int endOffset = element.getEndOffset();
+        childList.addAll(createBlockBoxes(context, startOffset, endOffset, width, beforeInlines, afterInlines));
+
+        if (afterBlock != null) {
+            childList.add(afterBlock);
+        }
+        
+        Styles styles = context.getStyleSheet().getStyles(this.getElement());
+        if (styles.getDisplay().equals(CSS.LIST_ITEM)
+                && !styles.getListStyleType().equals(CSS.NONE)) {
+            this.createListMarker(context);
+        }
+        
+        if (VEXCorePlugin.getInstance().isDebugging()) {
+	        long end = System.currentTimeMillis();
+	        if (end - start > 10) {
+	            System.out.println("BEB.layout for " + this.getElement().getName() + " took " + (end-start) + "ms");
+	        }
+        }
+
+        return childList;
+    }
+
+
+    
+    
+    
+    /**
+     * Creates a marker box for this primary box and puts it in the
+     * beforeMarker field.
+     */
+    private void createListMarker(LayoutContext context) {
+        
+        Styles styles = context.getStyleSheet().getStyles(this.getElement());
+        
+        InlineBox markerInline;
+        String type =styles.getListStyleType(); 
+        if (type.equals(CSS.NONE)) {
+            return;
+        } else if (type.equals(CSS.CIRCLE)) {
+            markerInline = createCircleBullet(this.getElement(), styles);
+        } else if (type.equals(CSS.SQUARE)) {
+            markerInline = createSquareBullet(this.getElement(), styles);
+        } else if (isEnumeratedListStyleType(type)) {
+            String item = this.getItemNumberString(type);
+            markerInline = new StaticTextBox(context, this.getElement(), item + ".");
+        } else {
+            markerInline = createDiscBullet(this.getElement(), styles);
+        }
+                
+        this.beforeMarker = ParagraphBox.create(
+            context, this.getElement(), new InlineBox[] { markerInline }, Integer.MAX_VALUE);
+        
+    }
+    
+    /**
+     * Returns a Drawable that draws a circle-style list item bullet.
+     */
+    private static InlineBox createCircleBullet(Element element, Styles styles) {
+        final int size = Math.round(0.5f * styles.getFontSize());
+        final int lift = Math.round(0.1f * styles.getFontSize());
+        Drawable drawable = new Drawable() {
+            public void draw(Graphics g, int x, int y) {
+                g.setLineStyle(Graphics.LINE_SOLID);
+                g.setLineWidth(1);
+                g.drawOval(x, y - size - lift, size, size);
+            }
+            public Rectangle getBounds() {
+                return new Rectangle(0, -size - lift, size, size);
+            }
+        };
+        return new DrawableBox(drawable, element);
+    }
+    
+    /**
+     * Returns a Drawable that draws a disc-style list item bullet.
+     */
+    private static InlineBox createDiscBullet(Element element, Styles styles) {
+        final int size = Math.round(0.5f * styles.getFontSize());
+        final int lift = Math.round(0.1f * styles.getFontSize());
+        Drawable drawable = new Drawable() {
+            public void draw(Graphics g, int x, int y) {
+                g.fillOval(x, y - size - lift, size, size);
+            }
+            public Rectangle getBounds() {
+                return new Rectangle(0, -size - lift, size, size);
+            }
+        };
+        return new DrawableBox(drawable, element);
+    }
+    
+    
+    /**
+     * Returns a Drawable that draws a square-style list item bullet.
+     */
+    private static InlineBox createSquareBullet(Element element, Styles styles) {
+        final int size = Math.round(0.5f * styles.getFontSize());
+        final int lift = Math.round(0.1f * styles.getFontSize());
+        Drawable drawable = new Drawable() {
+            public void draw(Graphics g, int x, int y) {
+                g.setLineStyle(Graphics.LINE_SOLID);
+                g.setLineWidth(1);
+                g.drawRect(x, y - size - lift, size, size);
+            }
+            public Rectangle getBounds() {
+                return new Rectangle(0, -size - lift, size, size);
+            }
+        };
+        return new DrawableBox(drawable, element);
+    }
+    
+    /**
+     * Returns the vertical distance from the top of this box to the top
+     * of its first line.
+     */
+    int getFirstLineTop(LayoutContext context) {
+        Styles styles = context.getStyleSheet().getStyles(this.getElement());
+        int top = styles.getBorderTopWidth() + styles.getPaddingTop().get(0);
+        Box[] children = this.getChildren();
+        if (children != null &&
+                children.length > 0 && 
+                children[0] instanceof BlockElementBox) {
+            return top + ((BlockElementBox) children[0]).getFirstLineTop(context);
+        } else {
+            return top;
+        }
+    }
+    
+    /**
+     * Returns the item number of this box. The item number indicates the 
+     * ordinal number of the corresponding element amongst its siblings
+     * starting with 1.
+     */
+    private int getItemNumber() {
+        Element element = this.getElement();
+        Element parent = element.getParent();
+
+        if (parent == null) {
+            return 1;
+        }
+        
+        int item = 1;
+        Element[] children = parent.getChildElements();
+        for (int i = 0; i < children.length; i++) {
+            if (children[i] == element) {
+                return item;
+            }
+            if (children[i].getName().equals(element.getName())) {
+                item++;
+            }
+        }
+        
+        throw new IllegalStateException();
+    }
+    
+    private String getItemNumberString(String style) {
+        int item = getItemNumber();
+        if (style.equals(CSS.DECIMAL_LEADING_ZERO)) {
+            if (item < 10) {
+                return "0" + Integer.toString(item);
+            } else {
+                return Integer.toString(item);
+            }
+        } else if (style.equals(CSS.LOWER_ALPHA) || style.equals(CSS.LOWER_LATIN)) {
+            return this.getAlpha(item);            
+        } else if (style.equals(CSS.LOWER_ROMAN)) {
+            return this.getRoman(item);            
+        } else if (style.equals(CSS.UPPER_ALPHA) || style.equals(CSS.UPPER_LATIN)) {
+            return this.getAlpha(item).toUpperCase();            
+        } else if (style.equals(CSS.UPPER_ROMAN)) {
+            return this.getRoman(item).toUpperCase();            
+        } else {
+            return Integer.toString(item);
+        }
+    }
+    
+    private String getAlpha(int n) {
+        final String alpha = "abcdefghijklmnopqrstuvwxyz";
+        return String.valueOf(alpha.charAt((n-1) % 26));    
+    }
+    
+    private String getRoman(int n) {
+        final String[] ones = { "", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix" };
+        final String[] tens = { "", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc" };
+        final String[] hundreds = { "", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm" };
+        StringBuffer sb = new StringBuffer();
+        for (int i = 0; i < n/1000; i++) {
+            sb.append("m");
+        }
+        sb.append(hundreds[(n/100) % 10]);
+        sb.append(tens[(n/10) % 10]);
+        sb.append(ones[n % 10]);
+        return sb.toString();
+    }
+    
+    private static boolean isEnumeratedListStyleType(String s) {
+        return s.equals(CSS.ARMENIAN)
+            || s.equals(CSS.CJK_IDEOGRAPHIC)
+            || s.equals(CSS.DECIMAL)
+            || s.equals(CSS.DECIMAL_LEADING_ZERO)
+            || s.equals(CSS.GEORGIAN)
+            || s.equals(CSS.HEBREW)
+            || s.equals(CSS.HIRAGANA)
+            || s.equals(CSS.HIRAGANA_IROHA)
+            || s.equals(CSS.KATAKANA)
+            || s.equals(CSS.KATAKANA_IROHA)
+            || s.equals(CSS.LOWER_ALPHA)
+            || s.equals(CSS.LOWER_GREEK)
+            || s.equals(CSS.LOWER_LATIN)
+            || s.equals(CSS.LOWER_ROMAN)
+            || s.equals(CSS.UPPER_ALPHA)
+            || s.equals(CSS.UPPER_LATIN)
+            || s.equals(CSS.UPPER_ROMAN);
+    }
+    
+}
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/BlockPseudoElementBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/BlockPseudoElementBox.java
new file mode 100644
index 0000000..73ad4af
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/BlockPseudoElementBox.java
@@ -0,0 +1,157 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import java.util.List;
+
+import org.eclipse.wst.xml.vex.core.internal.core.IntRange;
+import org.eclipse.wst.xml.vex.core.internal.css.Styles;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+
+
+/**
+ * Implements a Block
+ */
+public class BlockPseudoElementBox extends AbstractBox implements BlockBox {
+
+    private Element pseudoElement;
+    private BlockBox parent;
+    private ParagraphBox para;
+    
+    private int marginTop;
+    private int marginBottom;
+    
+    public BlockPseudoElementBox(LayoutContext context, Element pseudoElement, BlockBox parent, int width) {
+        
+        this.pseudoElement = pseudoElement;
+        this.parent = parent;
+        
+        Styles styles = context.getStyleSheet().getStyles(pseudoElement);
+        
+        this.marginTop = styles.getMarginTop().get(width);
+        this.marginBottom = styles.getMarginBottom().get(width);
+
+        int leftInset = styles.getMarginLeft().get(width)
+            + styles.getBorderLeftWidth()
+            + styles.getPaddingLeft().get(width);
+        int rightInset = styles.getMarginRight().get(width)
+            + styles.getBorderRightWidth()
+            + styles.getPaddingRight().get(width);
+
+        int childWidth = width - leftInset - rightInset;
+        List inlines = LayoutUtils.createGeneratedInlines(context, pseudoElement);
+        this.para = ParagraphBox.create(context, pseudoElement, inlines, childWidth);
+
+        this.para.setX(0);
+        this.para.setY(0);
+        this.setWidth(width - leftInset - rightInset);
+        this.setHeight(this.para.getHeight());
+    }
+
+
+    /**
+     * Provide children for {@link AbstractBox#paint}.
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getChildren()
+     */
+    public Box[] getChildren() {
+        return new Box[] { this.para };    
+    }
+
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getElement()
+     */
+    public Element getElement() {
+        return this.pseudoElement;
+    }
+
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.BlockBox#getFirstLine()
+     */
+    public LineBox getFirstLine() {
+        throw new IllegalStateException();
+    }
+
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.BlockBox#getLastLine()
+     */
+    public LineBox getLastLine() {
+        throw new IllegalStateException();
+    }
+
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.BlockBox#getLineEndOffset(int)
+     */
+    public int getLineEndOffset(int offset) {
+        throw new IllegalStateException();
+    }
+
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.BlockBox#getLineStartOffset(int)
+     */
+    public int getLineStartOffset(int offset) {
+        throw new IllegalStateException();
+    }
+
+    public int getMarginBottom() {
+        return this.marginBottom;
+    }
+    
+    public int getMarginTop() {
+        return this.marginTop;
+    }
+    
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.BlockBox#getNextLineOffset(org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext, int, int)
+     */
+    public int getNextLineOffset(LayoutContext context, int offset, int x) {
+        throw new IllegalStateException();
+    }
+
+    /**
+     * Returns this box's parent.
+     */
+    public BlockBox getParent() {
+        return this.parent;
+    }
+    
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.BlockBox#getPreviousLineOffset(org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext, int, int)
+     */
+    public int getPreviousLineOffset(
+        LayoutContext context,
+        int offset,
+        int x) {
+            throw new IllegalStateException();
+    }
+
+    public IntRange layout(LayoutContext context, int top, int bottom) {
+        return null;
+    }
+    
+    public void invalidate(boolean direct) {
+        throw new IllegalStateException("invalidate called on a non-element BlockBox");
+    }
+
+
+    /**
+     * Draw boxes before painting our child.
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#paint(org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext, int, int)
+     */
+    public void paint(LayoutContext context, int x, int y) {
+        this.drawBox(context, x, y, this.getParent().getWidth(), true);
+        super.paint(context, x, y);
+    }
+
+    public void setInitialSize(LayoutContext context) {
+        // NOP - size calculated in the ctor
+    }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/Box.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/Box.java
new file mode 100644
index 0000000..caf8b81
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/Box.java
@@ -0,0 +1,167 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import org.eclipse.wst.xml.vex.core.internal.core.Caret;
+import org.eclipse.wst.xml.vex.core.internal.core.Insets;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+
+
+/**
+ * Represents a rectangular area in the layout. The height and width of the box
+ * are measured from the inner edges of the box's padding, as in CSS. 
+ * Similarly, the (x, y) position of the box are with
+ * respect to the inner top-left corner of the box's padding, and are relative to
+ * the parent's (x, y) position.
+ */
+public interface Box {
+    
+    
+    /**
+     * Returns true if this box contains the given offset.
+     * @param offset the offset to test
+     */
+    public boolean containsOffset(int offset);
+    
+    /**
+     * Returns a Caret object representing the given offset.
+     * 
+     * @param context LayoutContext to be used
+     * @param offset offset for which to retrieve the caret 
+     */
+    public Caret getCaret(LayoutContext context, int offset);
+    
+    /**
+     * Returns an array of this box's children.
+     */
+    public Box[] getChildren();
+    
+    /**
+     * Returns the Element with which this box is associated, or null if 
+     * there is no such box. The box may directly represent the Element,
+     * or simply use it for formatting information.
+     */
+    public Element getElement();
+
+    /**
+     * Returns the offset of the end of the content that the box covers.
+     */
+    public int getEndOffset();
+
+    /**
+     * Returns the height of the box. For boxes subject to the CSS box model,
+     * this is the inner height, exclusive of margins, borders, and padding.
+     */
+    public int getHeight();
+    
+    /**
+     * Return an Insets object representing the total width of margins,
+     * borders, and padding for this box.
+     * @param context LayoutContext being used for this layout.
+     * @param containerWidth Width of the containing box.
+     */
+    public Insets getInsets(LayoutContext context, int containerWidth);
+
+    /**
+     * Returns the offset of the start of the content that the box covers.
+     */
+    public int getStartOffset();
+
+    /**
+     * Returns the width of the box. For boxes subject to the CSS box model,
+     * this is the inner width, exclusive of margins, borders, and padding.
+     */
+    public int getWidth();
+    
+    /**
+     * Returns the x-coordinate of the box, relative to its parent.
+     * For boxes subject to the CSS box model, this is the left edge of
+     * the box's content area.
+     */
+    public int getX();
+
+    /**
+     * Returns the y-coordinate of the box, relative to its parent.
+     * For boxes subject to the CSS box model, this is the top edge of
+     * the box's content area.
+     */
+    public int getY();
+    
+    /**
+     * Returns true if this box represents a portion of the XML document's 
+     * content. If false is returned, the following methods are not supported
+     * by this box.
+     * 
+     * <ul>
+     * <li>getCaretShapes()</li>
+     * <li>getEndOffset()</li>
+     * <li>getStartOffset()</li>
+     * <li>viewToModel()</li>
+     * </ul>
+     */
+    public boolean hasContent();
+    
+    /**
+     * Returns true if the box is anonymous, that is, it is not directly
+     * associated with an element.
+     */
+    public boolean isAnonymous();
+    
+    /**
+     * Draws the box's content in the given Graphics context.
+     *
+     * @param context <code>LayoutContext</code> containing the
+     * <code>Graphics</code> object into which the box should be
+     * painted
+     * @param x the x-offset at which the box should be painted
+     * @param y the y-offset at which the box should be painted
+     */
+    public void paint(LayoutContext context, int x, int y);
+
+    /**
+     * Sets the height of this box.
+     *
+     * @param height new height of the box
+     */
+    public void setHeight(int height);
+
+    /**
+     * Sets the width of this box.
+     *
+     * @param width new width of the box
+     */
+    public void setWidth(int width);
+
+    /**
+     * Sets the x-coordinate of the top-left corner of the box.
+     * @param x the new x-coordinate
+     */
+    public void setX(int x);
+    
+    /**
+     * Sets the y-coordinate of the top-left corner of the box.
+     * @param y the new y-coordinate
+     */
+    public void setY(int y);
+    
+    /**
+     * Returns the offset in the content closest to the given view position.
+     *
+     * @param context <code>LayoutContext</code> for this box tree
+     * @param x x offset of the view position for which the model
+     * offset is to be determined.
+     * @param y y offset of the view position for which the model
+     * offset is to be determined.
+     */
+    public int viewToModel(LayoutContext context, int x, int y);
+
+}
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/BoxFactory.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/BoxFactory.java
new file mode 100644
index 0000000..5b6727f
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/BoxFactory.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import java.io.Serializable;
+
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+
+
+
+/**
+ * Interface to an object that creates boxes from elements. Implementations
+ * of this interface must be serializable.
+ */
+public interface BoxFactory extends Serializable {
+
+    /**
+     * Creates a box given an element. 
+     * @param context CSS styles for the new element
+     * @param element Element for which the box should be created.
+     * @param parent Parent box for the new box.
+     * @param containerWidth Width of the box to be created.
+     */
+    public Box createBox(LayoutContext context, Element element, BlockBox parent, int containerWidth);
+
+}
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/CompositeInlineBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/CompositeInlineBox.java
new file mode 100644
index 0000000..fd17191
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/CompositeInlineBox.java
@@ -0,0 +1,198 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.eclipse.wst.xml.vex.core.internal.core.Caret;
+import org.eclipse.wst.xml.vex.core.internal.core.FontMetrics;
+import org.eclipse.wst.xml.vex.core.internal.core.FontResource;
+import org.eclipse.wst.xml.vex.core.internal.core.Graphics;
+import org.eclipse.wst.xml.vex.core.internal.css.Styles;
+
+
+/**
+ * InlineBox consisting of several children. This is the parent class 
+ * of InlineElementBox and LineBox, and implements the split method.
+ */
+public abstract class CompositeInlineBox extends AbstractBox implements InlineBox {
+
+    /**
+     * Returns true if any of the children have content.
+     */
+    public boolean hasContent() {
+        Box[] children = this.getChildren();
+        for (int i = 0; i < children.length; i++) {
+            if (children[i].hasContent()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public boolean isEOL() {
+        Box[] children = this.getChildren();
+        return children.length > 0 && ((InlineBox) children[children.length-1]).isEOL();
+    }
+
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getCaret(org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext, int)
+     */
+    public Caret getCaret(LayoutContext context, int offset) {
+
+        int x = 0;
+        Box[] children = this.getChildren();
+        
+        // we want the caret to be to the right of any leading static boxes... 
+        int start = 0; 
+        while (start < children.length && !children[start].hasContent()) {
+            x += children[start].getWidth();
+            start++;
+        }
+        
+        // ...and to the left of any trailing static boxes
+        int end = children.length;
+        while (end < 0 && !children[end - 1].hasContent()) {
+            end--;
+        }
+        
+        for (int i = start; i < end; i++) {
+            Box child = children[i];
+            if (child.hasContent()) {
+                if (offset < child.getStartOffset()) {
+                    break;
+                } else if (offset <= child.getEndOffset()) {
+                    Caret caret = child.getCaret(context, offset);
+                    caret.translate(child.getX(), child.getY());
+                    return caret;
+                }                
+            }
+            x += child.getWidth();
+        }
+        
+        Graphics g = context.getGraphics();
+        Styles styles = context.getStyleSheet().getStyles(this.getElement());
+        
+        FontResource font = g.createFont(styles.getFont());
+        FontResource oldFont = g.setFont(font);
+        FontMetrics fm = g.getFontMetrics();
+        int height = fm.getAscent() + fm.getDescent();
+        g.setFont(oldFont);
+        font.dispose();
+        
+        int lineHeight = styles.getLineHeight();
+        int y = (lineHeight - height) / 2;
+        return new TextCaret(x, y, height);
+    }
+
+
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.InlineBox#split(org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext, int, boolean)
+     */
+    public Pair split(LayoutContext context, int maxWidth, boolean force) {
+        
+        // list of children that have yet to be added to the left side
+        LinkedList rights = new LinkedList(Arrays.asList(this.getChildren()));
+
+        // pending is a list of inlines we are trying to add to the left side
+        // but which cannot end at a split
+        List pending = new ArrayList();
+        
+        // list of inlines that make up the left side
+        List lefts = new ArrayList();
+        
+        int remaining = maxWidth;
+        boolean eol = false;
+        
+        while (!rights.isEmpty() && remaining >= 0) {
+            InlineBox inline = (InlineBox) rights.removeFirst();
+            InlineBox.Pair pair = inline.split(context, remaining, force && lefts.isEmpty());
+
+            if (pair.getLeft() != null) {
+                lefts.addAll(pending);
+                pending.clear();
+                lefts.add(pair.getLeft());
+                remaining -= pair.getLeft().getWidth();
+            }
+            
+            if (pair.getRight() != null) {
+                pending.add(pair.getRight());
+                remaining -= pair.getRight().getWidth();
+            }
+            
+            if (pair.getLeft() != null && pair.getLeft().isEOL()) { 
+                eol = true;
+                break;
+            }
+            
+        }
+        
+        if (((force && lefts.isEmpty()) || remaining >= 0) && !eol) {
+            lefts.addAll(pending);
+        } else {
+            rights.addAll(0, pending);
+        }
+        
+        InlineBox[] leftKids = (InlineBox[]) lefts.toArray(new InlineBox[lefts.size()]);
+        InlineBox[] rightKids = (InlineBox[]) rights.toArray(new InlineBox[rights.size()]);
+        
+        return this.split(context, leftKids, rightKids);
+    }
+
+    
+    /**
+     * Creates a Pair of InlineBoxes, each with its own set of children. 
+     * @param context LayoutContext used for this layout.
+     * @param lefts Child boxes to be given to the left box.
+     * @param rights Child boxes to be given to the right box.
+     * @return
+     */
+    protected abstract Pair split(LayoutContext context, InlineBox[] lefts, InlineBox[] rights);
+
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#viewToModel(org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext, int, int)
+     */
+    public int viewToModel(LayoutContext context, int x, int y) {
+        
+        if (!this.hasContent()) {
+            throw new RuntimeException("Oops. Calling viewToModel on a line with no content");
+        }
+        
+        Box closestContentChild = null;
+        int delta = Integer.MAX_VALUE;
+        Box[] children = this.getChildren();
+        for (int i = 0; i < children.length; i++) {
+            Box child = children[i];
+            if (child.hasContent()) {
+                int newDelta = 0;
+                if (x < child.getX()) {
+                    newDelta = child.getX() - x;
+                } else if (x > child.getX() + child.getWidth()) {
+                    newDelta = x - (child.getX() + child.getWidth());
+                }
+                if (newDelta < delta) {
+                    delta = newDelta;
+                    closestContentChild = child;
+                }
+            }
+        }
+        
+        return closestContentChild.viewToModel(
+            context, 
+            x - closestContentChild.getX(),
+            y - closestContentChild.getY());
+    }
+
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/CssBoxFactory.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/CssBoxFactory.java
new file mode 100644
index 0000000..ae87e42
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/CssBoxFactory.java
@@ -0,0 +1,45 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import org.eclipse.wst.xml.vex.core.internal.css.CSS;
+import org.eclipse.wst.xml.vex.core.internal.css.Styles;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+
+
+
+/**
+ * Implementation of the BoxFactory interface that returns boxes that
+ * represent CSS semantics.
+ */
+public class CssBoxFactory implements BoxFactory {
+
+    private static final long serialVersionUID = -6882526795866485074L;
+
+    /**
+     * Class constructor.
+     */
+    public CssBoxFactory() {
+    }
+
+    public Box createBox(LayoutContext context, Element element, BlockBox parent, int containerWidth) {
+        Styles styles = context.getStyleSheet().getStyles(element);
+        if (styles.getDisplay().equals(CSS.TABLE)) {
+            return new TableBox(context, parent, element);
+        } else if (styles.isBlock()) {
+            return new BlockElementBox(context, parent, element);
+        } else {
+            throw new RuntimeException("Unexpected display property: " + styles.getDisplay());
+        }
+    }
+
+}
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/DocumentTextBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/DocumentTextBox.java
new file mode 100644
index 0000000..4af9251
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/DocumentTextBox.java
@@ -0,0 +1,245 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 John Krasnay 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:
+ *     John Krasnay - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.core.internal.layout;
+
+import org.eclipse.wst.xml.vex.core.internal.core.ColorResource;
+import org.eclipse.wst.xml.vex.core.internal.core.FontResource;
+import org.eclipse.wst.xml.vex.core.internal.core.Graphics;
+import org.eclipse.wst.xml.vex.core.internal.css.Styles;
+import org.eclipse.wst.xml.vex.core.internal.dom.Document;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.dom.Text;
+
+
+/**
+ * A TextBox that gets its text from the document.
+ * Represents text which is editable within the VexWidget. 
+ */
+public class DocumentTextBox extends TextBox {
+
+    private int startRelative;
+    private int endRelative;
+
+    /**
+     * Class constructor, accepting a Text object.
+     * @param context LayoutContext in use
+     * @param element Element being used 
+     * @param text
+     */
+    public DocumentTextBox(LayoutContext context, Element element, Text text) {
+        this(context, element, text.getStartOffset(), text.getEndOffset());
+    }
+    
+    /**
+     * Class constructor.
+     * 
+     * @param context LayoutContext used to calculate the box's size.
+     * @param element Element that directly contains the text.
+     * @param startOffset start offset of the text
+     * @param endOffset end offset of the text
+     */
+    public DocumentTextBox(LayoutContext context, Element element, int startOffset, int endOffset) {
+        super(element);
+        
+        if (startOffset >= endOffset) {
+            throw new IllegalStateException("DocumentTextBox: startOffset (" + startOffset + ") >= endOffset (" + endOffset + ")");
+        }
+        
+        this.startRelative = startOffset - element.getStartOffset();
+        this.endRelative = endOffset - element.getStartOffset();
+        this.calculateSize(context);
+        
+        if (this.getText().length() < (endOffset - startOffset)) {
+            throw new IllegalStateException();
+        }
+    }
+    
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getEndOffset()
+     */
+    public int getEndOffset() {
+        if (this.endRelative == -1) {
+            return -1;
+        } else {
+            return this.getElement().getStartOffset() + this.endRelative - 1;
+        }
+    }
+
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getStartOffset()
+     */
+    public int getStartOffset() {
+        if (this.startRelative == -1) {
+            return -1;
+        } else {
+