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 {
+            return this.getElement().getStartOffset() + this.startRelative;
+        }
+    }
+    
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.TextBox#getText()
+     */
+    public String getText() {
+        Document doc = this.getElement().getDocument(); 
+        return doc.getText(this.getStartOffset(), this.getEndOffset() + 1);
+    }
+
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#hasContent()
+     */
+    public boolean hasContent() {
+        return true;
+    }
+
+
+    /**
+     * @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) {
+        
+        Styles styles = context.getStyleSheet().getStyles(this.getElement());
+        Graphics g = context.getGraphics();
+        
+        FontResource font = g.createFont(styles.getFont());
+        FontResource oldFont = g.setFont(font);
+        ColorResource foreground = g.createColor(styles.getColor());
+        ColorResource oldForeground = g.setColor(foreground);
+//        ColorResource background = g.createColor(styles.getBackgroundColor());
+//        ColorResource oldBackground = g.setBackgroundColor(background);
+        
+        char[] chars = this.getText().toCharArray();
+        
+        if (chars.length < this.getEndOffset() - this.getStartOffset()) {
+            throw new IllegalStateException();
+        }
+        
+        if (chars.length == 0) {
+            throw new IllegalStateException();
+        }
+        
+        int start = 0;
+        int end = chars.length;
+        if (chars[end - 1] == NEWLINE_CHAR) {
+            end--;
+        }
+        int selStart = context.getSelectionStart() - this.getStartOffset();
+        selStart = Math.min(Math.max(selStart, start), end);
+        int selEnd = context.getSelectionEnd() - this.getStartOffset();
+        selEnd = Math.min(Math.max(selEnd, start), end);
+        
+        // text before selection
+        if (start < selStart) {
+            g.drawChars(chars, start, selStart - start, x, y);
+            String s = new String(chars, start, selStart - start);
+            paintTextDecoration(context, styles, s, x, y);
+        }
+        
+        // text after selection
+        if (selEnd < end) {
+            int x1 = x + g.charsWidth(chars, 0, selEnd);
+            g.drawChars(chars, selEnd, end - selEnd, x1, y);
+            String s = new String(chars, selEnd, end - selEnd);
+            paintTextDecoration(context, styles, s, x1, y);
+        }
+        
+        // text within selection
+        if (selStart < selEnd) {
+            String s = new String(chars, selStart, selEnd - selStart);
+            int x1 = x + g.charsWidth(chars, 0, selStart);
+            this.paintSelectedText(context, s, x1, y);
+            paintTextDecoration(context, styles, s, x1, y);
+        }
+
+        g.setFont(oldFont);
+        g.setColor(oldForeground);
+//        g.setBackgroundColor(oldBackground);
+        font.dispose();
+        foreground.dispose();
+//        background.dispose();
+    }
+    
+    
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.TextBox#splitAt(int)
+     */
+    public Pair splitAt(LayoutContext context, int offset) {
+
+        if (offset < 0 || offset > (this.endRelative - this.startRelative)) {
+            throw new IllegalStateException();
+        }
+        
+        int split = this.getStartOffset() + offset;
+                
+        DocumentTextBox left;
+        if (offset == 0) {
+            left = null; 
+        } else {
+            left = new DocumentTextBox(context, this.getElement(), this.getStartOffset(), split);
+        }
+        
+        InlineBox right;
+        if (split == this.getEndOffset() + 1) {
+            right = null; 
+        } else {
+            right = new DocumentTextBox(context, this.getElement(), split, this.getEndOffset() + 1);
+        }
+        return new Pair(left, right);
+    }
+
+    /**
+     * @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) {
+        
+        Graphics g = context.getGraphics();
+        Styles styles = context.getStyleSheet().getStyles(this.getElement());
+        FontResource font = g.createFont(styles.getFont());
+        FontResource oldFont = g.setFont(font);
+        char[] chars = this.getText().toCharArray();
+        
+        if (this.getWidth() <= 0) {
+            return this.getStartOffset();
+        }
+        
+        // first, get an estimate based on x / width
+        int offset = (x / this.getWidth()) * chars.length;
+        offset = Math.max(0, offset);
+        offset = Math.min(chars.length, offset);
+        
+        int delta = Math.abs(x - g.charsWidth(chars, 0, offset));
+
+        // Search backwards
+        while (offset > 0) {
+            int newDelta = Math.abs(x - g.charsWidth(chars, 0, offset-1));
+            if (newDelta > delta) {
+                break;
+            }
+            delta = newDelta;
+            offset--;
+        }
+        
+        // Search forwards
+        while (offset < chars.length-1) {
+            int newDelta = Math.abs(x - g.charsWidth(chars, 0, offset+1));
+            if (newDelta > delta) {
+                break;
+            }
+            delta = newDelta;
+            offset++;
+        }
+
+        g.setFont(oldFont);
+        font.dispose();
+        return this.getStartOffset() + offset;
+    }
+
+    
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/DrawableBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/DrawableBox.java
new file mode 100644
index 0000000..0853576
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/DrawableBox.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.layout;
+
+import org.eclipse.wst.xml.vex.core.internal.core.ColorResource;
+import org.eclipse.wst.xml.vex.core.internal.core.Drawable;
+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.core.Rectangle;
+import org.eclipse.wst.xml.vex.core.internal.css.Styles;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+
+
+/**
+ * An inline box that draws a Drawable object. The drawable is drawn relative
+ * to the text baseline, therefore it should draw using mostly negative y-coordinates.
+ */
+public class DrawableBox extends AbstractBox implements InlineBox {
+
+    public static final byte NO_MARKER = 0;
+    public static final byte START_MARKER = 1;
+    public static final byte END_MARKER = 2;
+    
+    private Drawable drawable;
+    private Element element;
+    private byte marker;
+    
+    /**
+     * Class constructor.
+     * @param drawable Drawable to draw.
+     * @param element Element whose styles determine the color of the drawable.
+     */
+    public DrawableBox(Drawable drawable, Element element) {
+        this(drawable, element, NO_MARKER);
+    }
+    
+    /**
+     * Class constructor. This constructor is called when creating a 
+     * DrawableBox that represents the start or end marker of an
+     * inline element.
+     * @param drawable Drawable to draw.
+     * @param element Element whose styles determine the color of the drawable.
+     * @param marker which marker should be drawn. Must be one of NO_MARKER,
+     * START_MARKER, or END_MARKER.
+     */
+    public DrawableBox(Drawable drawable, Element element, byte marker) {
+        this.drawable = drawable;
+        this.element = element;
+        this.marker = marker;
+        Rectangle bounds = drawable.getBounds();
+        this.setWidth(bounds.getWidth());
+        this.setHeight(bounds.getHeight());
+    }
+    
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.InlineBox#getBaseline()
+     */
+    public int getBaseline() {
+        return 0;
+    }
+
+    /**
+     * Returns the element that controls the styling for this text element.
+     */
+    public Element getElement() {
+        return this.element;
+    }
+    
+    public boolean isEOL() {
+        return false;
+    }
+    
+    /**
+     * @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) {
+        return new Pair(null, this);
+    }
+
+    /**
+     * Draw the drawable. The foreground color of the context's Graphics is
+     * set before calling the drawable's draw method.
+     */
+    public void paint(LayoutContext context, int x, int y) {
+        
+        Graphics g = context.getGraphics();
+        Styles styles = context.getStyleSheet().getStyles(this.element);
+        
+        boolean drawSelected = false;
+        if (this.marker == START_MARKER) {
+            drawSelected = 
+                this.getElement().getStartOffset() >= context.getSelectionStart()
+                && this.getElement().getStartOffset() + 1 <= context.getSelectionEnd();
+        } else if (this.marker == END_MARKER) {
+            drawSelected = 
+                this.getElement().getEndOffset() >= context.getSelectionStart()
+                && this.getElement().getEndOffset() + 1 <= context.getSelectionEnd();
+        }
+        
+        FontResource font = g.createFont(styles.getFont());
+        ColorResource color = g.createColor(styles.getColor());
+        
+        FontResource oldFont = g.setFont(font);
+        ColorResource oldColor = g.setColor(color);
+        
+        FontMetrics fm = g.getFontMetrics();
+        
+        if (drawSelected) {
+            Rectangle bounds = this.drawable.getBounds();
+            g.setColor(g.getSystemColor(ColorResource.SELECTION_BACKGROUND));
+            g.fillRect(x + bounds.getX(), y - fm.getAscent(), bounds.getWidth(), styles.getLineHeight());
+            g.setColor(g.getSystemColor(ColorResource.SELECTION_FOREGROUND));
+        }
+        
+        this.drawable.draw(g, x, y);
+        
+        g.setFont(oldFont);
+        g.setColor(oldColor);
+        font.dispose();
+        color.dispose();
+    }
+
+    /**
+     * @see java.lang.Object#toString()
+     */
+    public String toString() {
+        return "[shape]";
+    }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/ElementOrRangeCallback.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/ElementOrRangeCallback.java
new file mode 100644
index 0000000..d950b5b
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/ElementOrRangeCallback.java
@@ -0,0 +1,18 @@
+/*******************************************************************************
+ * 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.dom.Element;
+
+public interface ElementOrRangeCallback {
+    public void onElement(Element child, String displayStyle);
+    public void onRange(Element parent, int startOffset, int endOffset);
+}
\ 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/layout/HCaret.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/HCaret.java
new file mode 100644
index 0000000..2dc2df0
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/HCaret.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.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.Rectangle;
+
+/**
+ * A horizontal caret representing the insertion point between two block boxes.
+ */
+public class HCaret extends Caret {
+
+    private static final int LINE_WIDTH = 2;
+    
+    /**
+     * 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
+     * @param length Horizontal length of the caret.
+     */
+    public HCaret(int x, int y, int length) {
+        super(x, y);
+        this.length = length;
+    }
+    
+    public void draw(Graphics g, Color color) {
+        ColorResource newColor = g.createColor(color);
+        ColorResource oldColor = g.setColor(newColor);
+        g.fillRect(this.getX(), this.getY(), this.length, LINE_WIDTH);
+        g.setColor(oldColor);
+        newColor.dispose();
+    }    
+
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.core.Caret#getBounds()
+     */
+    public Rectangle getBounds() {
+        return new Rectangle(this.getX(), this.getY(), this.length, LINE_WIDTH);
+    }
+    
+    
+    //====================================================== PRIVATE
+    
+    private int length;
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/InlineBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/InlineBox.java
new file mode 100644
index 0000000..3e336a9
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/InlineBox.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.layout;
+
+/**
+ * Represents an inline box. Inline boxes are the children of line boxes.
+ */
+public interface InlineBox extends Box {
+
+    /**
+     * Represents a pair of inline boxes as returned by the <code>split</code>
+     * method.
+     */
+    public class Pair {
+        private InlineBox left;
+        private InlineBox right;
+
+        /**
+         * Class constructor.
+         * @param left box to the left of the split
+         * @param right box to the right of the split
+         */
+        public Pair(InlineBox left, InlineBox right) {
+            this.left = left;
+            this.right = right;
+        }
+        
+        /**
+         * Returns the box to the left of the split.
+         */
+        public InlineBox getLeft() {
+            return this.left;
+        }
+        
+        /**
+         * Returns the box to the right of the split.
+         */
+        public InlineBox getRight() {
+            return this.right;
+        }
+    }
+    
+    /**
+     * Returns the distance from the top of the inline box to the baseline.
+     */
+    public int getBaseline();
+    
+    /**
+     * Returns true if this inline box must be the last box on the current line.
+     */
+    public boolean isEOL();
+    
+    /**
+     * Splits this inline box into two. If <code>force</code> is false, this
+     * method should find a natural split point (e.g. after a space) and
+     * return two boxes representing a split at that point. The width of the
+     * last box must not exceed <code>maxWidth</code>. If no such natural split
+     * exists, null should be returned as the left box and <code>this</code>
+     * returned as the right box.
+     * 
+     * <p>If <code>force</code> is true, it means we are adding the first inline
+     * box to a line, therefore we must return something as the left box.
+     * In some cases, we may find a suboptimal split (e.g. between characters)
+     * that satisfies this. In other cases, <code>this</code> should be returned
+     * as the left box even though it exceeds maxWidth.</p>
+     * 
+     * <p>If the entire box fits within <code>maxWidth</code>, it should only 
+     * be returned as the left box if it can end a line; otherwise, it should
+     * be returned as the right box. Most implementations <i>cannot</i> end a line
+     * (one notable exception being a text box ending in whitespace) and should
+     * therefore return themselves as the right box.</p>
+     *
+     * @param context the layout context to be used.
+     * @param maxWidth Maximum width of the left part of the box.
+     * @param force if true, force a suboptimal split
+     */
+    public Pair split(LayoutContext context, int maxWidth, boolean force);
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/InlineElementBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/InlineElementBox.java
new file mode 100644
index 0000000..6a5346d
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/InlineElementBox.java
@@ -0,0 +1,369 @@
+/*******************************************************************************
+ * 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.core.Drawable;
+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.core.Rectangle;
+import org.eclipse.wst.xml.vex.core.internal.css.Styles;
+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.dom.Text;
+
+
+/**
+ * An inline box that represents an inline element. This box is responsible
+ * for creating and laying out its child boxes.
+ */
+public class InlineElementBox extends CompositeInlineBox {
+    
+    private Element element;
+    private InlineBox[] children;
+    private InlineBox firstContentChild = null;
+    private InlineBox lastContentChild = null;
+    private int baseline;
+    private int halfLeading;
+    
+    
+
+    /**
+     * Class constructor, called by the createInlineBoxes static factory method.
+     * @param context LayoutContext to use.
+     * @param element Element that generated this box
+     * @param startOffset Start offset of the range being rendered, which may be arbitrarily
+     * before or inside the element.
+     * @param endOffset End offset of the range being rendered, which may be arbitrarily
+     * after or inside the element.
+     */
+    private InlineElementBox(LayoutContext context, Element element, int startOffset, int endOffset) {
+        
+        this.element = element;
+
+        List childList = new ArrayList();
+
+        Styles styles = context.getStyleSheet().getStyles(element);
+        
+        if (startOffset <= element.getStartOffset()) {
+
+            // space for the left margin/border/padding
+            int space = styles.getMarginLeft().get(0) 
+                + styles.getBorderLeftWidth() 
+                + styles.getPaddingLeft().get(0);
+            
+            if (space > 0) {
+                childList.add(new SpaceBox(space, 1));
+            }
+            
+            // :before content
+            Element beforeElement = context.getStyleSheet().getBeforeElement(element);
+            if (beforeElement != null) {
+                childList.addAll(LayoutUtils.createGeneratedInlines(context, beforeElement));
+            }
+            
+            // left marker
+            childList.add(createLeftMarker(element, styles));
+        }
+        
+        InlineBoxes inlines = createInlineBoxes(context, element, startOffset, endOffset);
+        childList.addAll(inlines.boxes);
+        this.firstContentChild = inlines.firstContentBox;
+        this.lastContentChild = inlines.lastContentBox;
+
+        if (endOffset > element.getEndOffset()) {
+            
+            childList.add(new PlaceholderBox(context, element, element.getEndOffset() - element.getStartOffset()));
+            
+            // trailing marker        
+            childList.add(createRightMarker(element, styles));
+            
+            // :after content
+            Element afterElement = context.getStyleSheet().getAfterElement(element);
+            if (afterElement != null) {
+                childList.addAll(LayoutUtils.createGeneratedInlines(context, afterElement));
+            }
+            
+            // space for the right margin/border/padding
+            int space = styles.getMarginRight().get(0) 
+                    + styles.getBorderRightWidth() 
+                    + styles.getPaddingRight().get(0);
+
+            if (space > 0) {
+                childList.add(new SpaceBox(space, 1));
+            }
+        }
+        
+        this.children = (InlineBox[]) childList.toArray(new InlineBox[childList.size()]);
+        this.layout(context);
+    }
+    
+
+    
+    
+    
+    /**
+     * Class constructor. This constructor is called by the split method.
+     * @param context LayoutContext used for the layout.
+     * @param element Element to which this box applies.
+     * @param children Child boxes.
+     */
+    private InlineElementBox(LayoutContext context, Element element, InlineBox[] children) {
+        this.element = element;
+        this.children = children;
+        this.layout(context);
+        for (int i = 0; i < children.length; i++) {
+            InlineBox child = children[i];        
+            if (child.hasContent()) {
+                if (this.firstContentChild == null) {
+                    this.firstContentChild = child;           
+                }
+                this.lastContentChild = child;
+            }
+        }
+    }
+    
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.InlineBox#getBaseline()
+     */
+    public int getBaseline() {
+        return this.baseline;
+    }
+
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getChildren()
+     */
+    public Box[] getChildren() {
+        return this.children;
+    }
+    
+    /**
+     * Returns the element associated with this box.
+     */
+    public Element getElement() {
+        return this.element;
+    }
+    
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getEndOffset()
+     */
+    public int getEndOffset() {
+        if (this.lastContentChild == null) {
+            return this.getElement().getEndOffset();
+        } else {
+            return this.lastContentChild.getEndOffset();
+        }
+    }
+
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getStartOffset()
+     */
+    public int getStartOffset() {
+        if (this.firstContentChild == null) {
+            return this.getElement().getStartOffset();
+        } else {
+            return this.firstContentChild.getStartOffset();
+        }
+    }
+    
+    /**
+     * Override to paint background and borders.
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.AbstractBox#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, 0, true); // TODO CSS violation
+        super.paint(context, x, y);
+    }
+    
+
+
+    public Pair split(LayoutContext context, InlineBox[] lefts, InlineBox[] rights) {
+        
+        InlineElementBox left = null;
+        InlineElementBox right = null;
+        
+        if (lefts.length > 0 || rights.length == 0) {
+            left = new InlineElementBox(context, this.getElement(), lefts);
+        }
+        
+        if (rights.length > 0) {
+            right = new InlineElementBox(context, this.getElement(), rights);
+        }
+        
+        return new Pair(left, right);
+    }
+
+    public String toString() {
+        StringBuffer sb = new StringBuffer();
+        if (this.getStartOffset() == this.getElement().getStartOffset() + 1) {
+            sb.append("<");
+            sb.append(this.getElement().getName());
+            sb.append(">");    
+        }
+        Box[] children = this.getChildren();
+        for (int i = 0; i < children.length; i++) {
+            sb.append(children[i]);
+        }
+        if (this.getEndOffset() == this.getElement().getEndOffset()) {
+            sb.append("</");
+            sb.append(this.getElement().getName());
+            sb.append(">");    
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Holds the results of the createInlineBoxes method. 
+     */
+    static class InlineBoxes {
+
+        /** List of generated boxes */
+        public List boxes = new ArrayList();
+        
+        /** First generated box that has content */
+        public InlineBox firstContentBox;
+        
+        /** Last generated box that has content */
+        public InlineBox lastContentBox;
+    }
+    
+    /**
+     * Creates a list of inline boxes given a range of offsets. This method is
+     * used when creating both ParagraphBoxes and InlineElementBoxes.
+     * @param context LayoutContext to be used.
+     * @param containingElement Element containing both offsets
+     * @param startOffset The start of the range to convert to inline boxes.
+     * @param endOffset The end of the range to convert to inline boxes.
+     * @return
+     */
+    static InlineBoxes createInlineBoxes(LayoutContext context, Element containingElement, int startOffset, int endOffset) {
+    
+        InlineBoxes result = new InlineBoxes();
+        
+        Node[] nodes = containingElement.getChildNodes();
+        for (int i = 0; i < nodes.length; i++) {
+
+            Node node = nodes[i];
+            InlineBox child;
+            
+            if (node.getStartOffset() >= endOffset) {
+                break;
+            } else if (node instanceof Text) {
+                
+                // This check is different for Text and Element, so we have to
+                // do it here and below, too.
+                if (node.getEndOffset() <= startOffset) {
+                    continue;
+                }
+                
+                int start = Math.max(startOffset, node.getStartOffset());
+                int end = Math.min(endOffset, node.getEndOffset());
+                child = new DocumentTextBox(context, containingElement, start, end);
+                
+            } else {
+
+                if (node.getEndOffset() < startOffset) {
+                    continue;
+                }
+                
+                Element childElement = (Element) node;
+                InlineBox placeholder = new PlaceholderBox(context, containingElement, childElement.getStartOffset() - containingElement.getStartOffset());
+                result.boxes.add(placeholder);
+                if (result.firstContentBox == null) {
+                    result.firstContentBox = placeholder;
+                }
+                child = new InlineElementBox(context, childElement, startOffset, endOffset);
+            }
+
+            if (result.firstContentBox == null) {
+                result.firstContentBox = child;           
+            }
+            
+            result.lastContentBox = child;
+            
+            result.boxes.add(child);
+        }
+
+        return result;
+    }
+
+    //========================================================== PRIVATE
+    
+
+    private static InlineBox createLeftMarker(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);
+                y -= lift;
+                g.drawLine(x, y - size, x, y);
+                g.drawLine(x, y, x + size - 1, y - size/2);
+                g.drawLine(x + size - 1, y - size/2, x, y - size);
+            }
+            public Rectangle getBounds() {
+                return new Rectangle(0, -size, size, size);
+            }
+        };
+        return new DrawableBox(drawable, element, DrawableBox.START_MARKER);
+    }
+    
+    private static InlineBox createRightMarker(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);
+                y -= lift;
+                g.drawLine(x + size - 1, y - size, x + size - 1, y);
+                g.drawLine(x + size - 1, y, x, y - size/2);
+                g.drawLine(x, y - size/2, x + size - 1, y - size);
+            }
+            public Rectangle getBounds() {
+                return new Rectangle(0, -size, size, size);
+            }
+        };
+        return new DrawableBox(drawable, element, DrawableBox.END_MARKER);
+    }
+    
+    
+    private void layout(LayoutContext context) {
+        Graphics g = context.getGraphics();
+        Styles styles = context.getStyleSheet().getStyles(element);
+        FontResource font = g.createFont(styles.getFont());
+        FontResource oldFont = g.setFont(font);
+        FontMetrics fm = g.getFontMetrics();
+        this.setHeight(styles.getLineHeight());
+        this.halfLeading = (styles.getLineHeight() - fm.getAscent() - fm.getDescent()) / 2;
+        this.baseline = this.halfLeading + fm.getAscent();
+        g.setFont(oldFont);
+        font.dispose();
+        
+        int x = 0;
+        for (int i = 0; i < this.children.length; i++) {
+             InlineBox child = this.children[i];
+             // TODO: honour the child's vertical-align property
+             child.setX(x);
+             child.setY(this.baseline - child.getBaseline());
+             x += child.getWidth();
+        }
+
+        this.setWidth(x);
+    }
+    
+
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/LayoutContext.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/LayoutContext.java
new file mode 100644
index 0000000..77e43f3
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/LayoutContext.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 org.eclipse.wst.xml.vex.core.internal.core.Graphics;
+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;
+
+
+/**
+ * Encapsulation of all the resources needed to create a box
+ * tree. Most operations on a box tree, such as creating the tree,
+ * painting the tree, and converting between spatial and model
+ * coordinates, require the context.
+ */
+public class LayoutContext {
+
+    private BoxFactory boxFactory;
+    private Document document;
+    private Graphics graphics;
+    private StyleSheet styleSheet;
+    private int selectionStart;
+    private int selectionEnd;
+    private long startTime = System.currentTimeMillis();
+    
+    /**
+     * Class constructor.
+     */
+    public LayoutContext() {
+    }
+
+    /**
+     * Returns the BoxFactory used to generate boxes for the layout.
+     */
+    public BoxFactory getBoxFactory() {
+        return boxFactory;
+    }
+
+    /**
+     * Returns the document being layed out.
+     */
+    public Document getDocument() {
+        return document;
+    }
+
+    /**
+     * Returns the <code>Graphics</code> object used for layout. Box paint
+     * methods use this graphics for painting.
+     */
+    public Graphics getGraphics() {
+	return this.graphics;
+    }
+
+    /**
+     * Returns the time the layout was started. Actually, it's the time since
+     * this context was created, as returned by System.currentTimeMills().
+     */
+    public long getStartTime() {
+        return startTime;
+    }
+    
+    /**
+     * Returns the <code>StyleSheet</code> used for this layout.
+     */
+    public StyleSheet getStyleSheet() {
+        return this.styleSheet;
+    }
+    
+    
+    /**
+     * Helper method that returns true if the given element is in the
+     * selected range.
+     * @param element Element to test. May be null, in which case this method
+     * returns false.
+     */
+    public boolean isElementSelected(Element element) {
+        return element != null
+            && element.getStartOffset() >= this.getSelectionStart()
+            && element.getEndOffset() + 1 <= this.getSelectionEnd();
+    }
+    
+    /**
+     * Resets the start time to currentTimeMillis.
+     */
+    public void resetStartTime() {
+        this.startTime = System.currentTimeMillis();
+    }
+    
+    /**
+     * Sets the BoxFactory used to generate boxes for this layout.
+     */
+    public void setBoxFactory(BoxFactory factory) {
+        boxFactory = factory;
+    }
+
+    /**
+     * Sets the document being layed out.
+     */
+    public void setDocument(Document document) {
+        this.document = document;
+    }
+
+    /**
+     * Sets the Graphics object used for this layout.
+     */
+    public void setGraphics(Graphics graphics) {
+        this.graphics = graphics;
+    }
+
+    /**
+     * Sets the stylesheet used for this layout.
+     */
+    public void setStyleSheet(StyleSheet sheet) {
+        styleSheet = sheet;
+    }
+
+    /**
+     * Returns the offset where the current selection ends.
+     */
+    public int getSelectionEnd() {
+        return selectionEnd;
+    }
+
+    /**
+     * Returns the offset where the current selection starts.
+     */
+    public int getSelectionStart() {
+        return selectionStart;
+    }
+
+    /**
+     * Sets the offset where the current selection ends.
+     * @param i the new value for selectionEnd
+     */
+    public void setSelectionEnd(int i) {
+        selectionEnd = i;
+    }
+
+    /**
+     * Sets the offset where the current selection starts.
+     * @param i the new value for selectionStart
+     */
+    public void setSelectionStart(int i) {
+        selectionStart = i;
+    }
+
+}
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/LayoutUtils.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/LayoutUtils.java
new file mode 100644
index 0000000..db6188a
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/LayoutUtils.java
@@ -0,0 +1,230 @@
+/*******************************************************************************
+ * 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.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+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.Element;
+import org.eclipse.wst.xml.vex.core.internal.dom.Node;
+
+
+/**
+ * Tools for layout and rendering of CSS-styled boxes
+ */
+public class LayoutUtils {
+
+    /**
+     * Create a List of generated inline boxes for the given pseudo-element.
+     * @param context LayoutContext in use
+     * @param pseudoElement Element representing the generated content.
+     */
+    public static List createGeneratedInlines(LayoutContext context, Element pseudoElement) {
+        String text = getGeneratedContent(context, pseudoElement);
+        List list = new ArrayList();
+        if (text.length() > 0) {
+            list.add(new StaticTextBox(context, pseudoElement, text));
+        }
+        return list;
+    }
+
+    /**
+     * Returns true if the given offset falls within the given element or range.
+     * 
+     * @param elementOrRange Element or IntRange object representing a range 
+     * of offsets.
+     * @param offset Offset to test.
+     */
+    public static boolean elementOrRangeContains(Object elementOrRange, int offset) {
+        if (elementOrRange instanceof Element) {
+            Element element = (Element) elementOrRange;
+            return offset > element.getStartOffset() && offset <= element.getEndOffset();
+        } else {
+            IntRange range = (IntRange) elementOrRange;
+            return offset >= range.getStart() && offset <= range.getEnd();
+        }
+    }
+    
+    /**
+     * Creates a string representing the generated content for the given 
+     * pseudo-element.
+     * @param context LayoutContext in use
+     * @param pseudoElement PseudoElement for which the generated content
+     * is to be returned.
+     */
+    private static String getGeneratedContent(LayoutContext context, Element pseudoElement) {
+        Styles styles = context.getStyleSheet().getStyles(pseudoElement);
+        List content = styles.getContent();
+        StringBuffer sb = new StringBuffer();
+        for (Iterator it = content.iterator(); it.hasNext(); ) {
+            sb.append((String) it.next()); // TODO: change to ContentPart
+        }
+        return sb.toString();
+    }
+
+    /**
+     * 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 context LayoutContext to use.
+     * @param displayStyles Display types to be explicitly recognized.
+     * @param element Element containing the children over which to iterate.
+     * @param startOffset Starting offset of the range containing nodes in which we're interested.
+     * @param endOffset Ending offset of the range containing nodes in which we're interested.
+     * @param callback DisplayStyleCallback through which the caller is notified
+     * of matching elements and non-matching ranges.
+     */
+    public static void iterateChildrenByDisplayStyle(StyleSheet styleSheet, Set displayStyles, Element element, int startOffset, int endOffset, ElementOrRangeCallback callback) {
+        
+        List nonMatching = new ArrayList();
+        
+        Node[] nodes = element.getChildNodes();
+        for (int i = 0; i < nodes.length; i++) {
+            if (nodes[i].getEndOffset() <= startOffset) {
+                continue;
+            } else if (nodes[i].getStartOffset() >= endOffset) {
+                break;
+            } else {
+                Node node = nodes[i];
+    
+                if (node instanceof Element) {
+                    Element childElement = (Element) node;
+                    String display = styleSheet.getStyles(childElement).getDisplay();
+                    if (displayStyles.contains(display)) {
+                        if (nonMatching.size() > 0) {
+                            Node firstNode = (Node) nonMatching.get(0);
+                            Node lastNode = (Node) nonMatching.get(nonMatching.size() - 1);
+                            if (lastNode instanceof Element) {
+                                callback.onRange(element, firstNode.getStartOffset(), lastNode.getEndOffset() + 1);
+                            } else {
+                                callback.onRange(element, firstNode.getStartOffset(), lastNode.getEndOffset());
+                            }
+                            nonMatching.clear();
+                        }
+                        callback.onElement(childElement, display);
+                    } else {
+                        nonMatching.add(node);
+                    }
+                } else {
+                    nonMatching.add(node);
+                }
+            }
+        }
+        
+        if (nonMatching.size() > 0) {
+            Node firstNode = (Node) nonMatching.get(0);
+            Node lastNode = (Node) nonMatching.get(nonMatching.size() - 1);
+            if (lastNode instanceof Element) {
+                callback.onRange(element, firstNode.getStartOffset(), lastNode.getEndOffset() + 1);
+            } else {
+                callback.onRange(element, firstNode.getStartOffset(), lastNode.getEndOffset());
+            }
+        }
+    }
+
+    /**
+     * 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 context LayoutContext to use.
+     * @param displayStyles Display types to be explicitly recognized.
+     * @param element Element containing the children over which to iterate.
+     * @param callback DisplayStyleCallback through which the caller is notified
+     * of matching elements and non-matching ranges.
+     */
+    public static void iterateChildrenByDisplayStyle(StyleSheet styleSheet, Set displayStyles, Element element, ElementOrRangeCallback callback) {
+        iterateChildrenByDisplayStyle(styleSheet, displayStyles, element, element.getStartOffset() + 1, element.getEndOffset(), callback);
+    }
+
+    /**
+     * Returns true if the given styles represent an element that can be 
+     * the child of a table element.
+     * 
+     * @param styleSheet StyleSheet to use.
+     * @param element Element to test.
+     */
+    public static boolean isTableChild(StyleSheet styleSheet, Element element) {
+        String display = styleSheet.getStyles(element).getDisplay();
+        return TABLE_CHILD_STYLES.contains(display);
+    }
+
+    public static void iterateTableRows(final StyleSheet styleSheet, final Element element, int startOffset, int endOffset, final ElementOrRangeCallback callback) {
+        
+        iterateChildrenByDisplayStyle(styleSheet, nonRowStyles, element, startOffset, endOffset, new ElementOrRangeCallback() {
+            public void onElement(Element child, String displayStyle) {
+                if (displayStyle.equals(CSS.TABLE_ROW_GROUP)
+                        || displayStyle.equals(CSS.TABLE_HEADER_GROUP)
+                        || displayStyle.equals(CSS.TABLE_FOOTER_GROUP)) {
+        
+                    // iterate rows in group
+                    iterateChildrenByDisplayStyle(styleSheet, rowStyles, child, child.getStartOffset() + 1, child.getEndOffset(), callback);
+                } else {
+                    // other element's can't contain rows
+                }
+            }
+            public void onRange(Element parent, int startOffset, int endOffset) {
+                // iterate over rows in range
+                iterateChildrenByDisplayStyle(styleSheet, rowStyles, element, startOffset, endOffset, callback);
+            }
+        });
+    
+    }
+
+    public static void iterateTableCells(StyleSheet styleSheet, Element element, int startOffset, int endOffset, final ElementOrRangeCallback callback) {
+        iterateChildrenByDisplayStyle(styleSheet, cellStyles, element, startOffset, endOffset, callback);
+    }
+
+    public static void iterateTableCells(StyleSheet styleSheet, Element element, final ElementOrRangeCallback callback) {
+        iterateChildrenByDisplayStyle(styleSheet, cellStyles, element, element.getStartOffset(), element.getEndOffset(), callback);
+    }
+    
+    /**
+     * Set of CSS display values that represent elements that can be children 
+     * of table elements.
+     */
+    public static Set TABLE_CHILD_STYLES = new HashSet();
+    
+    private static Set nonRowStyles = new HashSet();
+    private static Set rowStyles = new HashSet();
+    private static Set cellStyles = new HashSet();
+
+    
+    static {
+        nonRowStyles.add(CSS.TABLE_CAPTION);
+        nonRowStyles.add(CSS.TABLE_COLUMN);
+        nonRowStyles.add(CSS.TABLE_COLUMN_GROUP);
+        nonRowStyles.add(CSS.TABLE_ROW_GROUP);
+        nonRowStyles.add(CSS.TABLE_HEADER_GROUP);
+        nonRowStyles.add(CSS.TABLE_FOOTER_GROUP);
+        
+        rowStyles.add(CSS.TABLE_ROW);
+        
+        cellStyles.add(CSS.TABLE_CELL);
+        
+        TABLE_CHILD_STYLES.addAll(nonRowStyles);
+        TABLE_CHILD_STYLES.addAll(rowStyles);
+        TABLE_CHILD_STYLES.addAll(cellStyles);
+    }
+
+
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/LineBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/LineBox.java
new file mode 100644
index 0000000..975a563
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/LineBox.java
@@ -0,0 +1,133 @@
+/*******************************************************************************
+ * 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.dom.Element;
+
+/**
+ * Represents a line of text and inline images.
+ */
+public class LineBox extends CompositeInlineBox {
+
+    private Element element;    
+    private InlineBox[] children;
+    private InlineBox firstContentChild = null;
+    private InlineBox lastContentChild = null;
+    private int baseline;
+                
+    /**
+     * Class constructor.
+     *
+     * @param context LayoutContext for this layout. 
+     * @param children InlineBoxes that make up this line.
+     */
+    public LineBox(LayoutContext context, Element element, InlineBox[] children) {
+
+        this.element = element;
+        this.children = children;
+        
+        int height = 0;
+        int x = 0;
+        this.baseline = 0;
+        for (int i = 0; i < children.length; i++) {
+            InlineBox child = children[i];
+            child.setX(x);
+            child.setY(0); // TODO: do proper vertical alignment
+            this.baseline = Math.max(this.baseline, child.getBaseline());
+            x += child.getWidth();
+            height = Math.max(height, child.getHeight());
+            if (child.hasContent()) {
+                if (this.firstContentChild == null) {
+                    this.firstContentChild = child;           
+                }
+                this.lastContentChild = child;
+            }
+        }
+        
+        this.setHeight(height);
+        this.setWidth(x);
+    }
+    
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.InlineBox#getBaseline()
+     */
+    public int getBaseline() {
+        return this.baseline;
+    }
+    
+    public Box[] getChildren() {
+        return this.children;
+    }
+
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getElement()
+     */
+    public Element getElement() {
+        return this.element;    
+    }
+    
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getEndOffset()
+     */
+    public int getEndOffset() {
+        return this.lastContentChild.getEndOffset();
+    }
+
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getStartOffset()
+     */
+    public int getStartOffset() {
+        return this.firstContentChild.getStartOffset();
+    }
+
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#hasContent()
+     */
+    public boolean hasContent() {
+        return this.firstContentChild != null;
+    }
+    
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.CompositeInlineBox#split(org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext, org.eclipse.wst.xml.vex.core.internal.layout.InlineBox[], org.eclipse.wst.xml.vex.core.internal.layout.InlineBox[])
+     */
+    public Pair split(LayoutContext context, InlineBox[] lefts, InlineBox[] rights) {
+        
+        LineBox left = null;
+        LineBox right = null;
+        
+        if (lefts.length > 0) {
+            left = new LineBox(context, this.getElement(), lefts);
+        }
+        
+        if (rights.length > 0) {
+            right = new LineBox(context, this.getElement(), rights);
+        }
+        
+        return new Pair(left, right);
+    }
+    
+    
+    /**
+     * @see java.lang.Object#toString()
+     */
+    public String toString() {
+        Box[] children = this.getChildren();
+        StringBuffer sb = new StringBuffer();
+        for (int i = 0; i < children.length; i++) {
+            sb.append(children[i]);
+        }
+        return sb.toString();
+    }
+
+    //========================================================== PRIVATE
+    
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/ParagraphBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/ParagraphBox.java
new file mode 100644
index 0000000..227de34
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/ParagraphBox.java
@@ -0,0 +1,299 @@
+/*******************************************************************************
+ * 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.Iterator;
+import java.util.List;
+
+import org.eclipse.wst.xml.vex.core.internal.core.Caret;
+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.Styles;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+
+
+/**
+ * A box that wraps inline content into a paragraph.
+ */
+public class ParagraphBox extends AbstractBox implements BlockBox {
+
+    private LineBox[] children;
+    private LineBox firstContentLine;
+    private LineBox lastContentLine;
+    
+    /**
+     * Class constructor.
+     *
+     * @param children Line boxes that comprise the paragraph.
+     */
+    private ParagraphBox(LineBox[] children) {
+        this.children = children;
+        for (int i = 0; i < children.length; i++) {
+            if (children[i].hasContent()) {
+                if (this.firstContentLine == null) {
+                    this.firstContentLine = children[i];
+                }
+                this.lastContentLine = children[i];
+            }
+        }
+    }
+
+    /**
+     * Create a paragraph by word-wrapping a list of inline boxes.
+     * @param context LayoutContext used for this layout.
+     * @param element Element that controls the styling for this paragraph.
+     * @param inlines List of InlineBox objects to be wrapped
+     * @param width width to which the paragraph is to be wrapped
+     * @return
+     */
+    public static ParagraphBox create(LayoutContext context, Element element, List inlines, int width) {
+        InlineBox[] array = (InlineBox[]) inlines.toArray(new InlineBox[inlines.size()]);
+        return create(context, element, array, width);
+    }
+    
+    /**
+     * Create a paragraph by word-wrapping a list of inline boxes. 
+     * @param context LayoutContext used for this layout
+     * @param element Element that controls the styling of this paragraph,
+     * in particular text alignment.
+     * @param inlines Array of InlineBox objects to be wrapped.
+     * @param width width to which the paragraph is to be wrapped.
+     */
+    public static ParagraphBox create(LayoutContext context, Element element, InlineBox[] inlines, int width) {
+        
+        // lines is the list of LineBoxes we are creating
+        List lines = new ArrayList();
+
+        InlineBox right = new LineBox(context, element, inlines);
+        
+        while (right != null) {
+            InlineBox.Pair pair = right.split(context, width, true);
+            lines.add(pair.getLeft());
+            right = pair.getRight();
+        }
+        
+        Styles styles = context.getStyleSheet().getStyles(element);
+        String textAlign = styles.getTextAlign();
+
+        // y-offset of the next line
+        int y = 0;
+        
+        int actualWidth = 0;
+                
+        for (Iterator it = lines.iterator(); it.hasNext(); ) {
+            
+            LineBox lineBox = (LineBox) it.next(); 
+            
+            int x;
+            if (textAlign.equals(CSS.RIGHT)) {
+                x = width - lineBox.getWidth();
+            } else if (textAlign.equals(CSS.CENTER)) {
+                x = (width - lineBox.getWidth()) / 2;
+            } else {
+                x = 0;
+            }
+            
+            lineBox.setX(x);
+            lineBox.setY(y);
+            
+            y += lineBox.getHeight();
+            actualWidth = Math.max(actualWidth, lineBox.getWidth());
+        }
+        
+        LineBox[] children = (LineBox[]) lines.toArray(new LineBox[lines.size()]);
+        
+        ParagraphBox para = new ParagraphBox(children);
+        para.setWidth(actualWidth);
+        para.setHeight(y);
+
+        // BlockElementBox uses a scaling factor to estimate box height based
+        // on font size, layout width, and character count, as follows.
+        //
+        // estHeight = factor * fontSize * fontSize * charCount / width
+        //
+        // This bit reports the actual factor that would correctly estimate
+        // the height of a BlockElementBox containing only this paragraph.
+        //
+        // factor = estHeight * width / (fontSize * fontSize * charCount)
+        //
+        /*
+        Box firstContentBox = null;
+        for (int i = 0; i < inlines.length; i++) {
+            Box box = inlines[i];
+            if (box.hasContent()) {
+                firstContentBox = box;
+                break;
+            }
+        }
+        
+        if (firstContentBox != null) {
+            float fontSize = styles.getFontSize();
+            int charCount = lastContentBox.getEndOffset() - firstContentBox.getStartOffset();
+            float factor = para.getHeight() * para.getWidth() / (fontSize * fontSize * charCount);
+            System.out.println("Actual factor is " + factor); 
+        }
+        */
+
+        return para;
+    }
+
+
+    /**
+     * @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) {
+        
+        LineBox line = this.getLineAt(offset);
+        Caret caret = line.getCaret(context, offset);
+        caret.translate(line.getX(), line.getY());
+        return caret;
+        
+    }
+    
+    public Box[] getChildren() {
+        return this.children;
+    }
+    
+    public int getEndOffset() {
+        return this.lastContentLine.getEndOffset();
+    }
+    
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.BlockBox#getFirstLine()
+     */
+    public LineBox getFirstLine() {
+        if (this.children.length == 0) {
+            return null;    
+        } else {
+            return this.children[0];
+        }
+    }
+    
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.BlockBox#getLastLine()
+     */
+    public LineBox getLastLine() {
+        if (this.children.length == 0) {
+            return null;    
+        } else {
+            return this.children[this.children.length - 1];
+        }
+    }
+    
+    /**
+     * Returns the LineBox at the given offset.
+     * @param offset the offset to check.
+     */
+    public LineBox getLineAt(int offset) {
+        LineBox[] children = this.children;
+        for (int i = 0; i < children.length; i++) {
+            if (children[i].hasContent() && offset <= children[i].getEndOffset()) {
+                return children[i];
+            }
+        }
+        return this.lastContentLine;
+    }
+
+    public int getLineEndOffset(int offset) {
+        return this.getLineAt(offset).getEndOffset();
+    }
+    
+    public int getLineStartOffset(int offset) {
+        return this.getLineAt(offset).getStartOffset();
+    }
+
+    public int getMarginBottom() {
+        return 0;
+    }
+    
+    public int getMarginTop() {
+        return 0;
+    }
+    
+    public int getNextLineOffset(LayoutContext context, int offset, int x) {
+        LineBox nextLine = null;
+        LineBox[] children = this.children;
+        for (int i = 0; i < children.length; i++) {
+            if (children[i].hasContent() && children[i].getStartOffset() > offset) {
+                nextLine = children[i];
+                break;
+            }
+        }
+        if (nextLine == null) {
+            //return this.getEndOffset() + 1;
+            return -1;
+        } else {
+            return nextLine.viewToModel(context, x - nextLine.getX(), 0);        
+        }
+    }
+    
+    public BlockBox getParent() {
+        throw new IllegalStateException("ParagraphBox does not currently track parent");
+    }
+    
+    public int getPreviousLineOffset(LayoutContext context, int offset, int x) {
+        LineBox prevLine = null;
+        LineBox[] children = this.children;
+        for (int i = children.length - 1; i >= 0; i--) {
+            if (children[i].hasContent() && children[i].getEndOffset() < offset) {
+                prevLine = children[i];
+                break;
+            }
+        }
+        if (prevLine == null) {
+            //return this.getStartOffset() - 1;
+            return -1;
+        } else {
+            return prevLine.viewToModel(context, x - prevLine.getX(), 0);        
+        }
+    }
+
+    public int getStartOffset() {
+        return this.firstContentLine.getStartOffset();
+    }
+    
+    public boolean hasContent() {
+        return this.firstContentLine != null;
+    }
+    
+    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");
+    }
+
+    public void setInitialSize(LayoutContext context) {
+        // NOP - size calculated in factory method
+    }
+
+    public String toString() {
+        return "ParagraphBox";
+    }
+    
+    public int viewToModel(LayoutContext context, int x, int y) {
+
+        LineBox[] children = this.children;
+        for (int i = 0; i < children.length; i++) {
+            Box child = children[i];
+            if (child.hasContent() && y <= child.getY() + child.getHeight()) {
+                return child.viewToModel(context, x - child.getX(), y - child.getY());
+            }
+        }
+        throw new RuntimeException("No line at (" + x + ", " + y + ")");
+    }
+
+    //===================================================== PRIVATE
+
+}
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/PlaceholderBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/PlaceholderBox.java
new file mode 100644
index 0000000..cb670d4
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/PlaceholderBox.java
@@ -0,0 +1,130 @@
+/*******************************************************************************
+ * 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.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;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+
+
+/**
+ * A zero-width box that represents a single offset in the document.
+ */
+public class PlaceholderBox extends AbstractBox implements InlineBox {
+
+    private Element element;
+    private int relOffset;
+    private int textTop;
+    private int baseline;
+        
+    /**
+     * Class constructor.
+     * @param context LayoutContext in effect.
+     * @param element Element containing this placeholder. the element is used
+     * both to determine the size of the box and its caret, but also as a base
+     * point for relOffset.
+     * @param relOffset Offset of the placeholder, relative to the start of
+     * the element.
+     */
+    public PlaceholderBox(LayoutContext context, Element element, int relOffset) {
+        
+        this.element = element;
+        this.relOffset = relOffset;
+        
+        this.setWidth(0);
+        
+        Graphics g = context.getGraphics();
+        Styles styles = context.getStyleSheet().getStyles(element);
+        FontResource font = g.createFont(styles.getFont());
+        FontResource oldFont = g.setFont(font);
+        FontMetrics fm = g.getFontMetrics();
+        int height = fm.getAscent() + fm.getDescent();
+
+        int lineHeight = styles.getLineHeight();
+        this.textTop = (lineHeight - height) / 2;
+
+        this.baseline = this.textTop + fm.getAscent();
+        this.setHeight(lineHeight);
+        g.setFont(oldFont);
+        font.dispose();
+    }
+    
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.InlineBox#getBaseline()
+     */
+    public int getBaseline() {
+        return this.baseline;
+    }
+
+    /**
+     * @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) {
+        return new Pair(null, this);
+    }
+
+    /**
+     * @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) {
+        return new TextCaret(0, this.textTop, this.baseline - this.textTop);
+    }
+
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getElement()
+     */
+    public Element getElement() {
+        return this.element;
+    }
+
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getEndOffset()
+     */
+    public int getEndOffset() {
+        return this.element.getStartOffset() + this.relOffset;
+    }
+
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getStartOffset()
+     */
+    public int getStartOffset() {
+        return this.element.getStartOffset() + this.relOffset;
+    }
+    
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#hasContent()
+     */
+    public boolean hasContent() {
+        return true;
+    }
+
+    public boolean isEOL() {
+        return false;
+    }
+    
+    /**
+     * @see java.lang.Object#toString()
+     */
+    public String toString() {
+        return "[placeholder(" + this.getStartOffset() + ")]";
+    }
+    /**
+     * @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) {
+        return this.getStartOffset();
+    }
+
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/RootBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/RootBox.java
new file mode 100644
index 0000000..e11f631
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/RootBox.java
@@ -0,0 +1,180 @@
+/*******************************************************************************
+ * 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.VEXCorePlugin;
+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.core.IntRange;
+import org.eclipse.wst.xml.vex.core.internal.core.Rectangle;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+
+
+/**
+ * A wrapper for the top level <code>BlockElementBox</code> that applies
+ * its margins.
+ */
+public class RootBox extends AbstractBox implements BlockBox {
+
+    private Element element;
+    private BlockElementBox childBox;
+    private Box[] children = new Box[1];
+    
+    /**
+     * Class constructor.
+     * @param context LayoutContext used to create children.
+     * @param element Element associated with this box.
+     * @param width width of this box
+     */
+    public RootBox(LayoutContext context, Element element, int width) {
+        this.element = element;
+        this.setWidth(width);
+
+        this.childBox = new BlockElementBox(context, this, this.element);
+        
+        Insets insets = this.getInsets(context, this.getWidth());
+        this.childBox.setX(insets.getLeft());
+        this.childBox.setY(insets.getTop());
+        this.childBox.setInitialSize(context);
+        this.children[0] = this.childBox;
+    }
+    
+    /**
+     * @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) {
+        Caret caret = this.childBox.getCaret(context, offset);
+        caret.translate(this.childBox.getX(), this.childBox.getY());
+        return caret;
+    }
+    
+    public Box[] getChildren() {
+        return this.children;
+    }
+
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getElement()
+     */
+    public Element getElement() {
+        return this.element;
+    }
+    
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getEndOffset()
+     */
+    public int getEndOffset() {
+        return this.childBox.getEndOffset();
+    }
+
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.BlockBox#getFirstLine()
+     */
+    public LineBox getFirstLine() {
+        return this.childBox.getFirstLine();
+    }
+    
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.BlockBox#getLastLine()
+     */
+    public LineBox getLastLine() {
+        return this.childBox.getLastLine();
+    }
+    
+    public int getLineEndOffset(int offset) {
+        return this.childBox.getLineEndOffset(offset);
+    }
+    
+    public int getLineStartOffset(int offset) {
+        return this.childBox.getLineStartOffset(offset);
+    }
+    
+    public int getMarginBottom() {
+        return 0;
+    }
+    
+    public int getMarginTop() {
+        return 0;
+    }
+    
+    public int getNextLineOffset(LayoutContext context, int offset, int x) {
+        return childBox.getNextLineOffset(context, offset, x - childBox.getX());
+    }
+    
+    public BlockBox getParent() {
+        throw new IllegalStateException("RootBox does not have a parent");
+    }
+
+    public int getPreviousLineOffset(LayoutContext context, int offset, int x) {
+        return childBox.getPreviousLineOffset(context, offset, x - childBox.getX());
+    }
+
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#getStartOffset()
+     */
+    public int getStartOffset() {
+        return this.childBox.getStartOffset();
+    }
+    
+    public void invalidate(boolean direct) {
+        // do nothing. layout is always propagated to our child box.
+    }
+    
+    public IntRange layout(LayoutContext context, int top, int bottom) {
+        
+        Insets insets = this.getInsets(context, this.getWidth());
+
+    	long start = 0;
+        if (VEXCorePlugin.getInstance().isDebugging()) {
+        	start = System.currentTimeMillis();
+        }
+        
+        IntRange repaintRange = this.childBox.layout(context, top - insets.getTop(), bottom - insets.getBottom());
+        
+        
+        if (VEXCorePlugin.getInstance().isDebugging()) {
+	        long end = System.currentTimeMillis();
+	        if (end - start > 50) {
+	            System.out.println("RootBox.layout took " + (end - start) + "ms");
+	        }
+        }
+        
+        this.setHeight(this.childBox.getHeight() + insets.getTop() + insets.getBottom());
+        
+        if (repaintRange != null) {
+            return new IntRange(repaintRange.getStart() + this.childBox.getY(), repaintRange.getEnd() + this.childBox.getY());
+        } else {
+            return null;
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see net.sf.vex.layout.ContentBox#viewToModel(net.sf.vex.layout.LayoutContext, int, int)
+     */
+    public int viewToModel(LayoutContext context, int x, int y) {
+        return this.childBox.viewToModel(
+            context, x - this.childBox.getX(), y - this.childBox.getY());
+    }
+
+    public void paint(LayoutContext context, int x, int y) {
+        Rectangle r = context.getGraphics().getClipBounds();
+        long start = System.currentTimeMillis();
+        super.paint(context, x, y);
+        long end = System.currentTimeMillis();
+        if (end - start > 50) {
+            System.out.println("RootBox.paint " + r.getHeight() + " pixel rows in " + (end - start) + "ms");
+        }
+    }
+
+    public void setInitialSize(LayoutContext context) {
+        throw new IllegalStateException();
+    }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/SpaceBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/SpaceBox.java
new file mode 100644
index 0000000..8ada2a4
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/SpaceBox.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.layout;
+
+
+/**
+ * An empty inline box that simply takes up space.
+ */
+public class SpaceBox extends AbstractBox implements InlineBox {
+
+    /**
+     * Class constructor.
+     * @param width width of the box
+     * @param height height of the box
+     */
+    public SpaceBox(int width, int height) {
+        this.setWidth(width);
+        this.setHeight(height);
+    }
+    
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.InlineBox#getBaseline()
+     */
+    public int getBaseline() {
+        return this.getHeight();
+    }
+
+    public boolean isEOL() {
+        return false;
+    }
+    
+    /**
+     * @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) {
+        return new Pair(null, this);
+    }
+
+    /**
+     * @see java.lang.Object#toString()
+     */
+    public String toString() {
+        return "[spacer]";
+    }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/StaticTextBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/StaticTextBox.java
new file mode 100644
index 0000000..9d765dd
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/StaticTextBox.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.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.Element;
+
+
+/**
+ * A TextBox representing a static string.
+ * Represents text which is not editable within the VexWidget, such as enumerated list markers. 
+ */
+public class StaticTextBox extends TextBox {
+
+    public static final byte NO_MARKER = 0;
+    public static final byte START_MARKER = 1;
+    public static final byte END_MARKER = 2;
+    
+    private String text;
+    private byte marker;
+    
+    /**
+     * Class constructor.
+     * 
+     * @param context LayoutContext used to calculate the box's size.
+     * @param element Element used to style the text.
+     * @param text Static text to display
+     */
+    public StaticTextBox(LayoutContext context, Element element, String text) {
+        this(context, element, text, NO_MARKER);
+        if (text.length() == 0) {
+            throw new IllegalArgumentException("StaticTextBox cannot have an empty text string.");
+        }
+    }
+    
+    /**
+     * Class constructor. This constructor is used when generating a static
+     * text box representing a marker for the start or end of an inline element.
+     * If the selection spans the related marker, the text is drawn in the
+     * platform's text selection colours.
+     * 
+     * @param context LayoutContext used to calculate the box's size
+     * @param element Element used to style the text
+     * @param text Static text to display
+     * @param marker START_MARKER or END_MARKER, depending on whether the
+     * text represents the start sentinel or the end sentinel of the element 
+     */
+    public StaticTextBox(LayoutContext context, Element element, String text, byte marker) {
+        super(element);
+        this.text = text;
+        this.marker = marker;
+        this.calculateSize(context);
+    }
+    
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.TextBox#getText()
+     */
+    public String getText() {
+        return this.text;    
+    }
+    
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.Box#hasContent()
+     */
+    public boolean hasContent() {
+        return false;
+    }
+    
+    /**
+     * @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) {
+        
+        Styles styles = context.getStyleSheet().getStyles(this.getElement());
+        Graphics g = context.getGraphics();
+        
+        boolean drawSelected = false;
+        if (this.marker == START_MARKER) {
+            drawSelected = 
+                this.getElement().getStartOffset() >= context.getSelectionStart()
+                && this.getElement().getStartOffset() + 1 <= context.getSelectionEnd();
+        } else if (this.marker == END_MARKER) {
+            drawSelected = 
+                this.getElement().getEndOffset() >= context.getSelectionStart()
+                && this.getElement().getEndOffset() + 1 <= context.getSelectionEnd();
+        }
+        
+        FontResource font = g.createFont(styles.getFont());
+        ColorResource color = g.createColor(styles.getColor());
+
+        FontResource oldFont = g.setFont(font);
+        ColorResource oldColor = g.setColor(color);
+        
+        if (drawSelected) {
+            this.paintSelectedText(context, this.getText(), x, y);
+        } else {
+            g.drawString(this.getText(), x, y);
+        }
+        paintTextDecoration(context, styles, this.getText(), x, y);
+
+        g.setFont(oldFont);
+        g.setColor(oldColor);
+        font.dispose();
+        color.dispose();
+    }
+    
+    
+
+
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.TextBox#splitAt(int)
+     */
+    public Pair splitAt(LayoutContext context, int offset) {
+        
+        StaticTextBox left;
+        if (offset == 0) {
+            left = null; 
+        } else {
+            left = new StaticTextBox(context, this.getElement(), this.getText().substring(0, offset), this.marker);
+        }
+        
+        StaticTextBox right;
+        if (offset == this.getText().length()) {
+            right = null; 
+        } else {
+            right = new StaticTextBox(context, this.getElement(), this.getText().substring(offset), this.marker);
+        }
+        return new Pair(left, right);
+    }
+    
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/TableBodyBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/TableBodyBox.java
new file mode 100644
index 0000000..b31cfe6
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/TableBodyBox.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.layout;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.wst.xml.vex.core.internal.core.Insets;
+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;
+
+
+/**
+ * An anonymous box that contains the table row groups for a table. This box
+ * is generated by a TableBox and assumes the margins and borders of the 
+ * table element.
+ */
+public class TableBodyBox extends AbstractBlockBox {
+
+    public TableBodyBox(LayoutContext context, TableBox parent, int startOffset, int endOffset) {
+        super(context, parent, startOffset, endOffset);
+    }
+
+    protected List createChildren(final LayoutContext context) {
+        // TODO Auto-generated method stub
+        
+        // Walk children:
+        //     each table-*-group gets a non-anonymous TableRowGroupBox
+        //     runs of others get anonymous TableRowGroupBox
+        
+        final List children = new ArrayList();
+        
+        this.iterateChildrenByDisplayStyle(context.getStyleSheet(), childDisplayStyles, new ElementOrRangeCallback() {
+            public void onElement(Element child, String displayStyle) {
+                children.add(new TableRowGroupBox(context, TableBodyBox.this, child));
+            }
+            public void onRange(Element parent, int startOffset, int endOffset) {
+                children.add(new TableRowGroupBox(context, TableBodyBox.this, startOffset, endOffset));
+            }
+        });
+
+        return children;
+    }
+
+    /**
+     * Return the insets of the parent box.
+     */
+    public Insets getInsets(LayoutContext context, int containerWidth) {
+        if (this.getParent().getElement() != null) {
+            Styles styles = context.getStyleSheet().getStyles(this.getParent().getElement());
+            return AbstractBox.getInsets(styles, containerWidth);
+        } else {
+            return Insets.ZERO_INSETS;
+        }
+    }
+
+    public void paint(LayoutContext context, int x, int y) {
+        this.drawBox(context, this.getParent().getElement(), x, y, this.getParent().getWidth(), true);
+        this.paintChildren(context, x, y);
+    }
+    
+    
+    //======================================================== PRIVATE
+
+    private static Set childDisplayStyles = new HashSet();
+    
+    static {
+        childDisplayStyles.add(CSS.TABLE_ROW_GROUP);
+        childDisplayStyles.add(CSS.TABLE_HEADER_GROUP);
+        childDisplayStyles.add(CSS.TABLE_FOOTER_GROUP);
+    }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/TableBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/TableBox.java
new file mode 100644
index 0000000..dae6cc7
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/TableBox.java
@@ -0,0 +1,224 @@
+/*******************************************************************************
+ * 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.HashSet;
+import java.util.List;
+import java.util.Set;
+
+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.Element;
+
+
+/**
+ * Box that lays out a table.
+ */
+public class TableBox extends AbstractBlockBox {
+
+    /**
+     * Class constructor.
+     * @param element Element represented by this box.
+     */
+    public TableBox(LayoutContext context, BlockBox parent, Element element) {
+        super(context, parent, element);
+    }
+
+    public TableBox(LayoutContext context, BlockBox parent, int startOffset, int endOffset) {
+        super(context, parent, startOffset, endOffset);
+    }
+    
+    protected List createChildren(final LayoutContext context) {
+        
+        // Walk children:
+        //     each table-caption gets a BEB
+        //     each table-column gets a TableColumnBox
+        //     each table-column-group gets a TableColumnGroupBox
+        //     runs of others get TableBodyBox
+    
+        final List children = new ArrayList();
+        
+        this.iterateChildrenByDisplayStyle(context.getStyleSheet(), captionOrColumnStyles, new ElementOrRangeCallback() {
+            public void onElement(Element child, String displayStyle) {
+                children.add(new BlockElementBox(context, TableBox.this, child));
+            }
+            public void onRange(Element parent, int startOffset, int endOffset) {
+                children.add(new TableBodyBox(context, TableBox.this, startOffset, endOffset));
+            }
+        });
+
+        return children;
+    }
+
+    /**
+     * Returns an array of widths of the table columns. These widths do not
+     * include column spacing.
+     */
+    public int[] getColumnWidths() {
+        return this.columnWidths;
+    }
+    
+    public int getHorizonalSpacing() {
+        return this.horizonalSpacing;
+    }
+
+    public Insets getInsets(LayoutContext context, int containerWidth) {
+        return new Insets(this.getMarginTop(), 0, this.getMarginBottom(), 0);
+    }
+
+    public int getVerticalSpacing() {
+        return this.verticalSpacing;
+    }
+
+    public IntRange layout(LayoutContext context, int top, int bottom) {
+        
+        // TODO Only compute columns widths (a) if re-laying out the whole box
+        // or (b) if the invalid child row now has more columns than us
+        // or (c) if the invalid child row has < current column count and it
+        // used to be the only one with a valid child row.
+
+        int newColCount = this.computeColumnCount(context);
+        if (this.columnWidths == null || newColCount != this.columnWidths.length) {
+            this.setLayoutState(LAYOUT_REDO);
+        }
+        
+        if (this.getLayoutState() == LAYOUT_REDO) {
+            this.computeColumnWidths(context, newColCount);
+        }
+        
+        return super.layout(context, top, bottom);
+    }
+
+
+    public void paint(LayoutContext context, int x, int y) {
+
+        if (this.skipPaint(context, x, y)) {
+            return;
+        }
+        
+        this.paintChildren(context, x, y);
+        
+        this.paintSelectionFrame(context, x, y, true);
+    }
+
+    //============================================================ PRIVATE
+    
+    private static Set captionOrColumnStyles = new HashSet();
+
+    static {
+        captionOrColumnStyles.add(CSS.TABLE_CAPTION);
+        captionOrColumnStyles.add(CSS.TABLE_COLUMN);
+        captionOrColumnStyles.add(CSS.TABLE_COLUMN_GROUP);
+    }
+
+    
+    private int[] columnWidths;
+    private int horizonalSpacing;
+    private int verticalSpacing;
+    
+    private static class CountingCallback implements ElementOrRangeCallback {
+
+        public int getCount() {
+            return this.count;
+        }
+        
+        public void reset() {
+            this.count = 0;
+        }
+        
+        public void onElement(Element child, String displayStyle) {
+            this.count++;
+        }
+
+        public void onRange(Element parent, int startOffset, int endOffset) {
+            this.count++;
+        }
+        
+        private int count;
+    }
+    
+    /**
+     * Performs a quick count of this table's columns. If the count has changed, we
+     * must re-layout the entire table.
+     */
+    private int computeColumnCount(LayoutContext context) {
+        
+        Element tableElement = this.findContainingElement();
+        final int[] columnCounts = new int[1]; // work around Java's insistence on final
+        columnCounts[0] = 0;
+        final StyleSheet styleSheet = context.getStyleSheet();
+        final CountingCallback callback = new CountingCallback();
+        LayoutUtils.iterateTableRows(styleSheet, tableElement, this.getStartOffset(), this.getEndOffset(), new ElementOrRangeCallback() {
+            public void onElement(Element child, String displayStyle) {
+                LayoutUtils.iterateTableCells(styleSheet, child, callback);
+                columnCounts[0] = Math.max(columnCounts[0], callback.getCount());
+                callback.reset();
+            }
+            public void onRange(Element parent, int startOffset, int endOffset) {
+                LayoutUtils.iterateTableCells(styleSheet, parent, startOffset, endOffset, callback);
+                columnCounts[0] = Math.max(columnCounts[0], callback.getCount());
+                callback.reset();
+            }
+            
+        });
+        
+        return columnCounts[0];
+    }
+    
+    private void computeColumnWidths(final LayoutContext context, int columnCount) {
+        
+        this.columnWidths = new int[columnCount];
+
+        if (columnCount == 0) {
+            return;
+        }
+        
+
+        this.horizonalSpacing = 0;
+        this.verticalSpacing = 0;
+        int myWidth = this.getWidth();
+        int availableWidth = myWidth;
+        
+        if (!this.isAnonymous()) {
+            Styles styles = context.getStyleSheet().getStyles(this.getElement());
+            this.horizonalSpacing = styles.getBorderSpacing().getHorizontal();
+            this.verticalSpacing = styles.getBorderSpacing().getVertical();
+
+            // width available for columns
+            // Since we apply margins/borders/padding to the TableBodyBox, they're
+            // not reflected in the width of this box. Thus, we subtract them here
+            availableWidth -=
+                + styles.getMarginLeft().get(myWidth)
+                + styles.getBorderLeftWidth()
+                + styles.getPaddingLeft().get(myWidth)
+                + styles.getPaddingRight().get(myWidth)
+                + styles.getBorderRightWidth()
+                + styles.getMarginRight().get(myWidth);
+        }
+        
+        int totalColumnWidth = this.horizonalSpacing;
+        int columnWidth = (availableWidth - this.horizonalSpacing * (columnCount + 1)) / columnCount;
+        for (int i = 0; i < this.columnWidths.length - 1; i++) {
+            System.err.print(" " + columnWidth);
+            this.columnWidths[i] = columnWidth;
+            totalColumnWidth += columnWidth + this.horizonalSpacing;
+        }
+        
+        // Due to rounding errors in the expression above, we calculate the 
+        // width of the last column separately, to make it exact.
+        this.columnWidths[this.columnWidths.length - 1] = availableWidth - totalColumnWidth - this.horizonalSpacing;
+
+    }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/TableCellBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/TableCellBox.java
new file mode 100644
index 0000000..583c9ac
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/TableCellBox.java
@@ -0,0 +1,61 @@
+/*******************************************************************************
+ * 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.css.Styles;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+
+
+/**
+ * Represents an element with display:table-cell, or a generated, anonymous
+ * table cell.
+ */
+public class TableCellBox extends AbstractBlockBox {
+
+    /**
+     * Class constructor for non-anonymous table cells.
+     * 
+     * @param context LayoutContext to use.
+     * @param parent Parent box.
+     * @param element Element with which this box is associated.
+     */
+    public TableCellBox(LayoutContext context, BlockBox parent, Element element, int width) {
+        super(context, parent, element);
+        Styles styles = context.getStyleSheet().getStyles(element);
+        this.setWidth(width 
+                - styles.getBorderLeftWidth() 
+                - styles.getPaddingLeft().get(parent.getWidth())
+                - styles.getPaddingRight().get(parent.getWidth())
+                - styles.getBorderRightWidth());
+    }
+
+    public TableCellBox(LayoutContext context, BlockBox parent, int startOffset, int endOffset, int width) {
+        super(context, parent, startOffset, endOffset);
+        this.setWidth(width);
+    }
+
+    protected List createChildren(LayoutContext context) {
+        return this.createBlockBoxes(context, this.getStartOffset(), this.getEndOffset(), this.getWidth(), null, null);
+    }
+
+    public void setInitialSize(LayoutContext context) {
+        // we've already set width in the ctor
+        // override to avoid setting width again
+        this.setHeight(this.getEstimatedHeight(context));
+    }
+
+    
+
+    //======================================================= PRIVATE
+    
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/TableRowBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/TableRowBox.java
new file mode 100644
index 0000000..c0f5036
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/TableRowBox.java
@@ -0,0 +1,242 @@
+/*******************************************************************************
+ * 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.core.Caret;
+import org.eclipse.wst.xml.vex.core.internal.core.Insets;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+
+
+/**
+ * Box representing a row in a table.
+ */
+public class TableRowBox extends AbstractBlockBox {
+
+    public TableRowBox(LayoutContext context, TableRowGroupBox parent, Element element) {
+        super(context, parent, element);
+    }
+
+    
+    public TableRowBox(LayoutContext context, BlockBox parent, int startOffset, int endOffset) {
+        super(context, parent, startOffset, endOffset);
+    }
+
+
+    protected List createChildren(final LayoutContext context) {
+    
+        final List children = new ArrayList();
+        
+        Element element = this.findContainingElement();
+        final int[] widths = this.getTableBox().getColumnWidths();
+        
+        LayoutUtils.iterateTableCells(context.getStyleSheet(), element, this.getStartOffset(), this.getEndOffset(), new ElementOrRangeCallback() {
+            private int column = 0;
+            public void onElement(Element child, String displayStyle) {
+                children.add(new TableCellBox(context, TableRowBox.this, child, widths[column++]));
+            }
+            public void onRange(Element parent, int startOffset, int endOffset) {
+                children.add(new TableCellBox(context, TableRowBox.this, startOffset, endOffset, widths[column++]));
+            }
+        });
+        
+        return children;
+    }
+
+    /**
+     * Override drawBox to do nothing. Table rows have no borders in 
+     * border-collapse:separate mode.
+     */
+    public void drawBox(LayoutContext context, int x, int y, int containerWidth, boolean drawBorders) {
+    }
+
+    public Caret getCaret(LayoutContext context, int offset) {
+        
+        int hSpacing = this.getTableBox().getHorizonalSpacing();
+
+        Box[] children = this.getChildren();
+
+        // If we haven't yet laid out this block, estimate the caret.
+        if (children == null) {
+            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.getWidth()); 
+        }
+        
+        int x = hSpacing / 2;
+
+        int[] widths = this.getTableBox().getColumnWidths();
+        
+        for (int i = 0; i < children.length; i++) {
+            
+            Box child = children[i];
+            
+            if (!child.hasContent()) {
+                continue; // TODO can we really have generated table cells?
+            }
+            
+            if (offset < child.getStartOffset()) {
+                return new TextCaret(x, 0, this.getHeight());
+            }
+            
+            if (offset >= child.getStartOffset()
+                && offset <= child.getEndOffset()) {
+                
+                Caret caret = child.getCaret(context, offset);
+                caret.translate(child.getX(), child.getY());
+                return caret;
+            }
+            
+            x += widths[i] + hSpacing;
+        }
+        
+        return new TextCaret(x, 0, this.getHeight());
+    }
+
+
+    /**
+     * Override to return zero insets. Table rows have no insets in
+     * border-collapse:separate mode.
+     */
+    public Insets getInsets(LayoutContext context, int containerWidth) {
+        return Insets.ZERO_INSETS;
+    }
+    
+    public int getMarginBottom() {
+        return 0;
+    }
+
+    public int getMarginTop() {
+        return 0;
+    }
+
+    public int getNextLineOffset(LayoutContext context, int offset, int x) {
+        
+        BlockBox[] children = this.getContentChildren();
+        int[] widths = this.getTableBox().getColumnWidths();
+        int leftEdge = 0;
+        
+        for (int i = 0; i < children.length; i++) {
+            if (leftEdge + widths[i] > x) {
+                int newOffset = children[i].getNextLineOffset(context, offset, x - leftEdge);
+                if (newOffset == children[i].getEndOffset() + 1) {
+                    return -1;
+                } else {
+                    return newOffset;
+                }
+            }
+            leftEdge += widths[i];
+        }
+        
+        return -1;
+    }
+
+    public int getPreviousLineOffset(LayoutContext context, int offset, int x) {
+
+        BlockBox[] children = this.getContentChildren();
+        int[] widths = this.getTableBox().getColumnWidths();
+        int leftEdge = 0;
+        
+        for (int i = 0; i < children.length; i++) {
+            if (leftEdge + widths[i] > x) {
+                int newOffset = children[i].getPreviousLineOffset(context, offset, x - leftEdge);
+                if (newOffset == children[i].getStartOffset() - 1) {
+                    return -1;
+                } else {
+                    return newOffset;
+                }
+            }
+            leftEdge += widths[i];
+        }
+        
+        return -1;
+    }
+
+    /**
+     * Returns the TableBox associated with this row.
+     */
+    public TableBox getTableBox() {
+        return (TableBox) this.getParent().getParent().getParent();
+    }
+
+
+    protected int positionChildren(LayoutContext context) {
+        
+        int hSpacing = this.getTableBox().getHorizonalSpacing();
+        
+        int childX = hSpacing;
+        int topInset = 0;
+        int height = 0;
+        int bottomInset = 0;
+        for (int i = 0; i < this.getChildren().length; i++) {
+            Box child = this.getChildren()[i];
+            Insets insets = child.getInsets(context, this.getWidth());
+            
+            childX += insets.getLeft();
+            
+            child.setX(childX);
+            
+            childX += child.getWidth() + insets.getRight() + hSpacing;
+            
+            topInset = Math.max(topInset, insets.getTop());
+            height = Math.max(height, child.getHeight());
+            bottomInset = Math.max(bottomInset, insets.getBottom());
+        }
+        
+        this.setHeight(topInset + height + bottomInset);
+        
+        for (int i = 0; i < this.getChildren().length; i++) {
+            Box child = this.getChildren()[i];
+            child.setY(topInset);
+            child.setHeight(height);
+        }
+        
+        return -1; // TODO revisit
+    }
+
+    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 (x < child.getX()) {
+                    return child.getStartOffset() - 1;
+                } else if (x < child.getX() + child.getWidth()) {
+                    return child.viewToModel(context, x - child.getX(), y
+                            - child.getY());
+                }
+            }
+        }
+
+        return this.getEndOffset();
+    }
+
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/TableRowGroupBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/TableRowGroupBox.java
new file mode 100644
index 0000000..b9f0a84
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/TableRowGroupBox.java
@@ -0,0 +1,137 @@
+/*******************************************************************************
+ * 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.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.wst.xml.vex.core.internal.core.Insets;
+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;
+
+
+/**
+ * Container for TableRowBox objects. May correspond to an element with
+ * display:table-row-group, display:table-head-group, display:table-foot-group,
+ * or may be anonymous.
+ */
+public class TableRowGroupBox extends AbstractBlockBox {
+
+    /**
+     * Class constructor for non-anonymous table row groups.
+     * 
+     * @param context LayoutContext to use.
+     * @param parent Parent of this box.
+     * @param element Element that generated this box.
+     */
+    public TableRowGroupBox(LayoutContext context, BlockBox parent, Element element) {
+        super(context, parent, element);
+    }
+
+    
+    /**
+     * Class constructor for anonymous table row groups.
+     *
+     * @param context LayoutContext to use.
+     * @param parent Parent of this box.
+     * @param startOffset Start of the range encompassing the table.
+     * @param endOffset End of the range encompassing the table.
+     */
+    public TableRowGroupBox(LayoutContext context, BlockBox parent, int startOffset, int endOffset) {
+        super(context, parent, startOffset, endOffset);
+        
+    }
+
+
+    protected List createChildren(final LayoutContext context) {
+        // TODO Auto-generated method stub
+        
+        // Walk children in range
+        //     - table-row children get non-anonymous TableRowBox
+        //     - runs of others get anonymous TableRowBox
+        
+        final List children = new ArrayList();
+        
+        this.iterateChildrenByDisplayStyle(context.getStyleSheet(), childDisplayStyles, new ElementOrRangeCallback() {
+            public void onElement(Element child, String displayStyle) {
+                children.add(new TableRowBox(context, TableRowGroupBox.this, child));
+            }
+            public void onRange(Element parent, int startOffset, int endOffset) {
+                children.add(new TableRowBox(context, TableRowGroupBox.this, startOffset, endOffset));
+            }
+        });
+
+        return children;
+    }
+
+    public Insets getInsets(LayoutContext context, int containerWidth) {
+        return Insets.ZERO_INSETS;
+    }
+
+    public int getMarginBottom() {
+        return 0;
+    }
+
+    public int getMarginTop() {
+        return 0;
+    }
+    
+    public void paint(LayoutContext context, int x, int y) {
+
+        if (this.skipPaint(context, x, y)) {
+            return;
+        }
+        
+        this.paintChildren(context, x, y);
+        
+        this.paintSelectionFrame(context, x, y, true);
+    }
+
+
+    protected int positionChildren(LayoutContext context) {
+        
+        Styles styles = context.getStyleSheet().getStyles(this.findContainingElement());
+        int spacing = styles.getBorderSpacing().getVertical();
+        
+        int childY = spacing;
+        for (int i = 0; i < this.getChildren().length; i++) {
+            
+            TableRowBox child = (TableRowBox) this.getChildren()[i];
+            // TODO must force table row margins to be zero
+            Insets insets = child.getInsets(context, this.getWidth());
+            
+            childY += insets.getTop();
+            
+            child.setX(insets.getLeft());
+            child.setY(childY);
+            
+            childY += child.getHeight() + insets.getBottom() + spacing;
+        }
+        this.setHeight(childY);
+        
+        return -1; // TODO revisit
+    }
+
+
+    //====================================================== PRIVATE
+
+    private static Set childDisplayStyles = new HashSet();
+    
+    static {
+        childDisplayStyles.add(CSS.TABLE_ROW);
+    }
+
+    
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/TextBox.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/TextBox.java
new file mode 100644
index 0000000..1fd75c9
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/TextBox.java
@@ -0,0 +1,277 @@
+/*******************************************************************************
+ * 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.ColorResource;
+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.FontSpec;
+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.Element;
+
+
+/**
+ * An inline box containing text. The <code>getText</code> and <code>splitAt</code>
+ * methods are abstract and must be implemented by subclasses.
+ */
+public abstract class TextBox extends AbstractBox implements InlineBox {
+
+    private Element element;
+    private int baseline;
+    
+    public static final char NEWLINE_CHAR = 0xa;
+    public static final String NEWLINE_STRING = "\n";
+    
+    /**
+     * Class constructor.
+     * @param element Element containing the text. This is used for styling 
+     * information.
+     */
+    public TextBox(Element element) {
+        this.element = element;
+    }
+    
+    /**
+     * Causes the box to recalculate it size. Subclasses should call this from
+     * their constructors after they are initialized.
+     * @param context LayoutContext used to calculate size.
+     */
+    protected void calculateSize(LayoutContext context) {
+        String s = this.getText();
+        if (s.endsWith(NEWLINE_STRING)) {
+            s = s.substring(0, s.length() - 1);
+        }
+        
+        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();
+        this.setWidth(g.stringWidth(s));
+        this.setHeight(styles.getLineHeight());
+        int halfLeading = (this.getHeight() - (fm.getAscent() + fm.getDescent())) / 2;
+        this.baseline = halfLeading + fm.getAscent();
+        g.setFont(oldFont);
+        font.dispose();
+    }
+    
+    
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.layout.InlineBox#getBaseline()
+     */
+    public int getBaseline() {
+        return this.baseline;
+    }
+
+    /**
+     * @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) {
+        Graphics g = context.getGraphics();
+        Styles styles = context.getStyleSheet().getStyles(this.element);
+        FontResource oldFont = g.getFont();
+        FontResource font = g.createFont(styles.getFont());
+        g.setFont(font);
+        char[] chars = this.getText().toCharArray();
+        int x = g.charsWidth(chars, 0, offset - this.getStartOffset());
+        g.setFont(oldFont);
+        font.dispose();
+        return new TextCaret(x, 0, this.getHeight());
+    }
+
+    /**
+     * Returns the element that controls the styling for this text element.
+     */
+    public Element getElement() {
+        return this.element;
+    }
+    
+    /**
+     * Return the text that comprises this text box. The actual text can come
+     * from the document content or from a static string.
+     */
+    public abstract String getText();
+
+    /**
+     * Returns true if the given character is one where a linebreak should 
+     * occur, e.g. a space.
+     * @param c the character to test
+     */
+    public static boolean isSplitChar(char c) {
+        return Character.isWhitespace(c);    
+    }
+    
+    public boolean isEOL() {
+        String s = this.getText();
+        return s.length() > 0 && s.charAt(s.length() - 1) == NEWLINE_CHAR;
+    }
+    
+    /**
+     * Paints a string as selected text.
+     * @param context LayoutContext to be used. It is assumed that the contained
+     * Graphics object is set up with the proper font.
+     * @param s String to draw
+     * @param x x-coordinate at which to draw the text
+     * @param y y-coordinate at which to draw the text
+     */
+    protected void paintSelectedText(LayoutContext context, String s, int x, int y) {
+        Graphics g = context.getGraphics();
+        
+        boolean inSelectedBlock = false;
+        Element e = this.getElement();
+        while (e != null) {
+            Styles styles = context.getStyleSheet().getStyles(e);
+            if (styles.isBlock()) {
+                if (context.isElementSelected(e)) {
+                    inSelectedBlock = true;
+                }
+                break;
+            }
+            e = e.getParent();
+        }
+
+        if (inSelectedBlock) {
+            g.setColor(g.getSystemColor(ColorResource.SELECTION_BACKGROUND));
+            g.drawString(s, x, y);
+        } else {
+            int width = g.stringWidth(s);
+            g.setColor(g.getSystemColor(ColorResource.SELECTION_BACKGROUND));
+            g.fillRect(x, y, width, this.getHeight());
+            g.setColor(g.getSystemColor(ColorResource.SELECTION_FOREGROUND));
+            g.drawString(s, x, y);
+        }
+    }
+
+    protected void paintTextDecoration(LayoutContext context, Styles styles, String s, int x, int y) {
+        int fontStyle = styles.getFont().getStyle();
+        Graphics g = context.getGraphics();
+        FontMetrics fm = g.getFontMetrics();
+
+        if ((fontStyle & FontSpec.UNDERLINE) != 0) {
+            int lineWidth = fm.getAscent()/12;
+            int ypos = y+fm.getAscent()+lineWidth;
+            paintBaseLine(g, s, x, ypos);
+        }
+        if ((fontStyle & FontSpec.OVERLINE) != 0) {
+            int lineWidth = fm.getAscent()/12;
+            int ypos = y + lineWidth/2;
+            paintBaseLine(g, s, x, ypos);
+        }
+        if ((fontStyle & FontSpec.LINE_THROUGH) != 0) {
+            int ypos = y + fm.getHeight()/2;
+            paintBaseLine(g, s, x, ypos);
+        }
+    }
+
+    /**
+     * Paint a line along the baseline of the text, for showing underline,
+     * overline and strike-through formatting.
+     * @param context LayoutContext to be used. It is assumed that the contained
+     * Graphics object is set up with the proper font.
+     * @param x x-coordinate at which to start drawing baseline
+     * @param y x-coordinate at which to start drawing baseline (adjusted
+     *        to produce the desired under/over/though effect)
+     */
+    protected void paintBaseLine(Graphics g, String s, int x, int y) {
+        FontMetrics fm = g.getFontMetrics();
+        int width = g.stringWidth(s);
+        int lineWidth = fm.getAscent()/12;
+        g.setLineStyle(Graphics.LINE_SOLID);
+        g.setLineWidth(lineWidth);
+        g.drawLine(x, y, x+width, y);
+    }
+
+    /**
+     * @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) {
+
+        char[] chars = this.getText().toCharArray();
+
+        if (chars.length == 0) {
+            throw new IllegalStateException();
+        }
+        
+        Graphics g = context.getGraphics();
+        Styles styles = context.getStyleSheet().getStyles(this.element);
+        FontResource font = g.createFont(styles.getFont());
+        FontResource oldFont = g.setFont(font);
+        
+        int split = 0;
+        int next = 1;
+        boolean eol = false; // end of line found
+        while (next < chars.length) {
+            if (isSplitChar(chars[next - 1])) {
+                if (g.charsWidth(chars, 0, next) <= maxWidth) {
+                    split = next;
+                    if (chars[next - 1] == NEWLINE_CHAR) {
+                        eol = true;
+                        break;
+                    }
+                } else {
+                    break;
+                }
+            }
+            next++;
+        }
+
+        if (force && split == 0) {
+            // find some kind of split
+            split = 1;
+            while (split < chars.length) {
+                if (g.charsWidth(chars, 0, split+1) > maxWidth) {
+                    break;
+                }
+                split++;
+            }
+            
+        }
+        
+        // include any trailing spaces in the split
+        // this also grabs any leading spaces when split==0
+        if (!eol) {
+            while (split < chars.length - 1 && chars[split] == ' ') {
+                split++;
+            }
+        }
+
+        g.setFont(oldFont);
+        font.dispose();
+
+        return this.splitAt(context, split);
+    }
+
+    /**
+     * Return a pair of boxes representing a split at the given offset.
+     * If split is zero, then the returned left box should be null.
+     * If the split is equal to the length of the text, then the right box
+     * should be null.
+     * 
+     * @param context LayoutContext used to calculate the sizes of the resulting
+     * boxes.
+     * @param offset location of the split, relative to the start of the
+     * text box.
+     * @return
+     */
+    public abstract Pair splitAt(LayoutContext context, int offset);
+
+    /**
+     * @see java.lang.Object#toString()
+     */    
+    public String toString() {
+        return this.getText();
+    }
+
+    
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/TextCaret.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/TextCaret.java
new file mode 100644
index 0000000..2fe5ff2
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/TextCaret.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.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.Rectangle;
+
+/**
+ * A caret drawn as a vertical line between characters.
+ */
+public class TextCaret extends Caret {
+
+    private static final int LINE_WIDTH = 2;
+
+    private int height;
+    
+    /**
+     * Class constructor
+     * @param x x-coordinate of the caret
+     * @param y y-coordinate of the top of the caret
+     * @param height height of the caret
+     */
+    public TextCaret(int x, int y, int height) {
+        super(x, y);
+        this.height = height;
+    }
+    
+    public void draw(Graphics g, Color color) {
+        ColorResource newColor = g.createColor(color);
+        ColorResource oldColor = g.setColor(newColor);
+        g.fillRect(this.getX(), this.getY(), LINE_WIDTH, height);
+        g.setColor(oldColor);
+        newColor.dispose();
+    }
+    
+    public Rectangle getBounds() {
+        return new Rectangle(this.getX(), this.getY(), LINE_WIDTH, height);    
+    }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/package.html b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/package.html
new file mode 100644
index 0000000..8042cab
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/layout/package.html
@@ -0,0 +1,124 @@
+<?xml version='1.0'?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html>
+
+  <head>
+
+    <title>The Vex Layout Engine</title>
+
+  </head>
+
+  <body>
+
+    <h1>The Vex Layout Engine</h1>
+
+    <p>The purpose of the Vex Layout Engine is to create a visual 
+    representation of a document given a CSS stylesheet. This visual 
+    representation is a nested hierarchy of rectangular boxes, 
+    implemented as a tree of objects each implementing the Box 
+    interface. Each box has the following properties.</p>
+
+    <ul>
+
+      <li>The x- and y-coordinates of the box. These coordinates are 
+      relative to the containing parent box for efficiency: if a 
+      box&apos;s position changes, it need not recalculate the 
+      positions of its children. For boxes corresponding to a document 
+      element, the coordinates match the coordinates of the CSS content 
+      area, which is inside any margins, borders, and padding.</li>
+
+      <li>The height and width of the box. For boxes corresponding to a 
+      document element, this is the height and width of the CSS content 
+      area, that is, the area inside any margins, borders, and 
+      padding.</li>
+
+      <li>The document element associated with the box, if any.</li>
+
+      <li>The child boxes of the box, if any.</li>
+
+      <li>The range of document offsets represented by the box, if 
+      any.</li>
+
+    </ul>
+
+    <p>Additionally, each box supports the following operations.</p>
+
+    <ul>
+
+      <li>Determine a <i>caret</i>, that is, a visual representation of 
+      the current insertion point, for a given document offset.</li>
+
+      <li>Return the document offset closest to a given (x, y) position 
+      relative to the top-left corner of the box.</li>
+
+    </ul>
+
+    <p>There are two main types of box. <i>Block boxes</i> normally 
+    contain other boxes and stack their children vertically (with 
+    TableRowBox being an exception whose children are stacked 
+    horizontally). <i>Inline boxes</i> may contain child boxes or other 
+    content such as text; their children are stacked horizontally and 
+    they may be split to wrap content into a series of lines.</p>
+
+    <p>A box may acquire its children in a number of ways. Boxes 
+    associated with document elements (e.g. BlockElementBox) create 
+    their own children by inspecting the child nodes of the associated 
+    element. This can happen immediately in the box&apos;s constructor, 
+    or may be deferred for performance. In other cases, a box&apos;s 
+    children are created by its parent and passed to its constructor. 
+    Finally, simple boxes such as DocumentTextBox and PlaceholderBox 
+    have no child boxes; they serve simply to display content or to aid 
+    in navigation.</p>
+
+    <h2>Layout Process</h2>
+
+    <p>The layout process begins with a VexWidgetImpl object, which 
+    creates a RootBox containing a BlockElementBox corresponding with 
+    the document&apos;s root element. Each BlockElementBox does not 
+    initially create its children. Instead, it estimates its height 
+    based on the current font size and the number of characters covered 
+    by the element.</p>
+
+    <p>At any one time, the user can only view a particular horizontal 
+    band of the document. To avoid unnecessary work, the VexWidgetImpl 
+    only requests that the RootBox lay out (that is, create and 
+    position children) within that horizontal band. Each 
+    BlockElementBox that is asked to layout a band creates its 
+    children, then propagates the layout call to children that fall 
+    inside the band. Newly created children that fall outside the 
+    visible band are not laid out. Instead, they are left with their 
+    initial size estimates.</p>
+
+    <p>While many BlockElementBoxes contain only other BlockElementBox 
+    children, eventually a BlockElementBox will need to lay out a run 
+    of inline content, that is, text and inline-formatted 
+    elements...</p>
+
+    <h2>Document Changes and Layout Updates</h2>
+
+    <h2>Content Boxes</h2>
+
+    <p>A box is said to <i>have content</i> if it corresponds to a 
+    range of character offsets in the source document. The simplest 
+    type of content box is a DocumentTextBox, which corresponds to a 
+    sequence of characters in the source document. BlockElementBoxes 
+    and InlineElementBoxes also have content. Boxes such as 
+    StaticTextBox and DrawableBox are purely visual and do not 
+    represent document content.</p>
+
+    <h2>Keyboard Navigation</h2>
+
+    <p>The user can change the insertion point by pressing one of the 
+    four arrow keys, the PgUp or PgDn keys, or the Home or End keys. 
+    The challenge of keyboard navigation is to calculate a new document 
+    offset for the insertion point given the current insertion point 
+    and the desired direction of motion. Complicating the situation is 
+    the fact that ranges of offsets may be invisible (e.g. inside a 
+    display:none block); the new offset must be visible.</p>
+
+    <h2>Mouse Navigation</h2>
+
+  </body>
+
+</html>
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/AwtColor.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/AwtColor.java
new file mode 100644
index 0000000..49ae9ba
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/AwtColor.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.swing;
+
+import org.eclipse.wst.xml.vex.core.internal.core.ColorResource;
+
+/**
+ * Wrapper for the AWT Color class.
+ */
+public class AwtColor implements ColorResource {
+
+    private java.awt.Color awtColor;
+    
+    public AwtColor(java.awt.Color awtColor) {
+        this.awtColor = awtColor;
+    }
+    
+    java.awt.Color getAwtColor() {
+        return this.awtColor;
+    }
+    
+    public void dispose() {
+    }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/AwtDisplayDevice.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/AwtDisplayDevice.java
new file mode 100644
index 0000000..b1ed41b
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/AwtDisplayDevice.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.swing;
+
+import java.awt.Toolkit;
+
+import org.eclipse.wst.xml.vex.core.internal.core.DisplayDevice;
+
+
+/**
+ * Swing implementation of the Display Device abstract class
+ * @author Vincent Lambert, Matrox Imaging
+ */
+public class AwtDisplayDevice extends DisplayDevice{
+    
+    private boolean loaded = false;
+    private int horizontalPPI = 72;
+    private int verticalPPI = 72;
+    
+    /** Creates a new instance of AwtDisplayDevice */
+    public AwtDisplayDevice() {}
+    
+    public int getHorizontalPPI(){
+        if (!this.loaded) {
+            this.load();
+        }
+        return this.horizontalPPI;
+    }
+    
+    public int getVerticalPPI(){
+        if (!this.loaded) {
+            this.load();
+        }
+        return this.verticalPPI;
+    }
+    
+    private void load() {
+        Toolkit tk = Toolkit.getDefaultToolkit();
+        
+        this.horizontalPPI = tk.getScreenResolution();
+        this.verticalPPI = tk.getScreenResolution();
+        this.loaded = true;
+    }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/AwtFont.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/AwtFont.java
new file mode 100644
index 0000000..9eb3cee
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/AwtFont.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.swing;
+
+import org.eclipse.wst.xml.vex.core.internal.core.FontResource;
+
+/**
+ * Wrapper for the AWT Font class.
+ */
+public class AwtFont implements FontResource {
+
+    private java.awt.Font awtFont;
+    
+    public AwtFont(java.awt.Font awtFont) {
+        this.awtFont = awtFont;
+    }
+    
+    java.awt.Font getAwtFont() {
+        return this.awtFont;
+    }
+    
+    public void dispose() {
+    }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/AwtFontMetrics.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/AwtFontMetrics.java
new file mode 100644
index 0000000..afeff5a
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/AwtFontMetrics.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.swing;
+
+import org.eclipse.wst.xml.vex.core.internal.core.FontMetrics;
+
+/**
+ * Wrapper for the AWT FontMetrics class.
+ */
+public class AwtFontMetrics implements FontMetrics {
+    
+    private java.awt.FontMetrics awtFontMetrics;
+
+    public AwtFontMetrics(java.awt.FontMetrics awtFontMetrics) {
+        this.awtFontMetrics = awtFontMetrics;
+    }
+    
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.core.FontMetrics#getAscent()
+     */
+    public int getAscent() {
+        return this.awtFontMetrics.getAscent();
+    }
+
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.core.FontMetrics#getDescent()
+     */
+    public int getDescent() {
+        return this.awtFontMetrics.getDescent();
+    }
+
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.core.FontMetrics#getHeight()
+     */
+    public int getHeight() {
+        return this.awtFontMetrics.getHeight();
+    }
+
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.core.FontMetrics#getLeading()
+     */
+    public int getLeading() {
+        return this.awtFontMetrics.getLeading();
+    }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/AwtGraphics.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/AwtGraphics.java
new file mode 100644
index 0000000..6cff27e
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/AwtGraphics.java
@@ -0,0 +1,218 @@
+/*******************************************************************************
+ * 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.swing;
+
+import java.awt.BasicStroke;
+import java.awt.GraphicsEnvironment;
+import java.awt.Stroke;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.swing.UIManager;
+
+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.FontResource;
+import org.eclipse.wst.xml.vex.core.internal.core.FontSpec;
+import org.eclipse.wst.xml.vex.core.internal.core.Graphics;
+import org.eclipse.wst.xml.vex.core.internal.core.Rectangle;
+
+
+/**
+ * Implementation of the Vex Graphics interface, mapping it to a 
+ * org.eclipse.swt.graphics.GC object.
+ */
+public class AwtGraphics implements Graphics {
+
+    private java.awt.Graphics2D g;
+    private int originX;
+    private int originY;
+    
+    private static Set availableFontFamilies = new HashSet();
+        
+    static {
+        GraphicsEnvironment ge = 
+            GraphicsEnvironment.getLocalGraphicsEnvironment();
+        String[] names = ge.getAvailableFontFamilyNames();
+        for (int i = 0; i < names.length; i++) {
+            availableFontFamilies.add(names[i].toLowerCase());
+        }
+    }
+
+    /**
+     * Class constructor.
+     * @param gc SWT GC to which we are drawing.
+     */
+    public AwtGraphics(java.awt.Graphics2D g) {
+        this.g = g;
+    }
+    
+    public void dispose() {
+    }
+    
+    public void drawChars(char[] chars, int offset, int length, int x, int y) {
+        this.g.drawString(new String(chars, offset, length), x + originX, y + originY + this.g.getFontMetrics().getAscent());
+    }
+
+    public void drawLine(int x1, int y1, int x2, int y2) {
+        this.g.drawLine(x1 + originX, y1 + originY, x2 + originX, y2 + originY);
+    }
+
+    public void drawOval(int x, int y, int width, int height) {
+        this.g.drawOval(x + originX, y + originY, width, height);
+    }
+
+    public void drawRect(int x, int y, int width, int height) {
+        this.g.drawRect(x + originX, y + originY, width, height);
+    }
+
+    public void drawString(String s, int x, int y) {
+        this.g.drawString(s, x + originX, y + originY + this.g.getFontMetrics().getAscent());
+    }
+
+    public void fillOval(int x, int y, int width, int height) {
+        this.g.fillOval(x + originX, y + originY, width, height);
+    }
+
+    public void fillRect(int x, int y, int width, int height) {
+        this.g.fillRect(x + originX, y + originY, width, height);
+    }
+
+    public Rectangle getClipBounds() {
+        java.awt.Rectangle r = this.g.getClipBounds();
+        return new Rectangle(r.x - originX, r.y - originY , r.width, r.height);
+    }
+
+    public ColorResource getColor() {
+        return new AwtColor(this.g.getColor());
+    }
+
+    public FontResource getFont() {
+        return new AwtFont(this.g.getFont());
+    }
+
+    public FontMetrics getFontMetrics() {
+        return new AwtFontMetrics(this.g.getFontMetrics());
+    }
+
+    public int getLineStyle() {
+        return this.lineStyle;
+    }
+    
+    public int getLineWidth() {
+        return this.lineWidth;
+    }
+    
+    public boolean isAntiAliased() {
+        return false;
+    }
+
+    public void setAntiAliased(boolean antiAliased) {
+    }
+
+    public ColorResource setColor(ColorResource color) {
+        ColorResource oldColor = this.getColor();
+        this.g.setColor(((AwtColor) color).getAwtColor());
+        return oldColor;
+    }
+
+    public FontResource setFont(FontResource font) {
+        FontResource oldFont = this.getFont();
+        this.g.setFont(((AwtFont) font).getAwtFont());
+        return oldFont;
+    }
+
+    public void setLineStyle(int lineStyle) {
+        this.lineStyle = lineStyle;
+        this.makeStroke();
+    }
+    
+    public void setLineWidth(int lineWidth) {
+        this.lineWidth = lineWidth;
+        this.makeStroke();
+    }
+    
+    public int charsWidth(char[] data, int offset, int length) {
+        return this.stringWidth(new String(data, offset, length));
+    }
+
+    public void setOrigin(int x, int y) {
+        this.originX = x;
+        this.originY = y;
+    }
+    
+    public ColorResource createColor(Color rgb) {
+        return new AwtColor( 
+            new java.awt.Color(
+                rgb.getRed(), rgb.getGreen(), rgb.getBlue())); 
+    }
+
+    public FontResource createFont(FontSpec fontSpec) {
+        int style = java.awt.Font.PLAIN;
+        if ((fontSpec.getStyle() & FontSpec.BOLD) > 0) {
+            style |= java.awt.Font.BOLD;
+        }
+        if ((fontSpec.getStyle() & FontSpec.ITALIC) > 0) {
+            style |= java.awt.Font.ITALIC;
+        }
+        int size = Math.round(fontSpec.getSize());
+
+        String name = "sans-serif";
+        String[] names = fontSpec.getNames();
+        for (int i = 0; i < names.length; i++) {
+            if (availableFontFamilies.contains(names[i])) {
+                name = names[i];
+                break;        
+            }
+        }
+        return new AwtFont(new java.awt.Font(name, style, size));
+    }
+
+    public ColorResource getSystemColor(int id) {
+        
+        if (id == ColorResource.SELECTION_BACKGROUND) {
+            return new AwtColor(UIManager.getColor("TextPane.selectionBackground"));
+        } else if (id == ColorResource.SELECTION_FOREGROUND) {
+            return new AwtColor(UIManager.getColor("TextPane.selectionForeground"));
+        } else {
+            return new AwtColor(java.awt.Color.BLACK);
+        }
+    }
+
+    public int stringWidth(String s) {
+        return this.g.getFontMetrics().stringWidth(s);
+    }
+    
+    //============================================================= PRIVATE
+    
+    private int lineWidth = 1;
+    private int lineStyle;
+
+    private void makeStroke() {
+        
+        float dashLength = this.lineWidth;
+        if (this.lineStyle == LINE_DASH) {
+            dashLength = 3 * this.lineWidth;
+        }
+        
+        Stroke stroke = new BasicStroke(
+            (float) this.lineWidth,
+            BasicStroke.CAP_SQUARE, // to be compatible with SWT GC
+            BasicStroke.JOIN_MITER,
+            1.0f,
+            new float[] { dashLength, dashLength },
+            0.0f);
+        
+        this.g.setStroke(stroke);
+    }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/NullSelection.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/NullSelection.java
new file mode 100644
index 0000000..2dd1970
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/NullSelection.java
@@ -0,0 +1,20 @@
+/*******************************************************************************
+ * 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.swing;
+
+
+/**
+ * Passed to {@link SelectionListener}s to indicate that nothing is currently
+ * selected.
+ */
+public class NullSelection implements Selection {
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/Selection.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/Selection.java
new file mode 100644
index 0000000..99d8801
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/Selection.java
@@ -0,0 +1,19 @@
+/*******************************************************************************
+ * 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.swing;
+
+/**
+ * Marker interface for classes that represent selections passed to
+ * {@link SelectionListener}s.
+ */
+public interface Selection {
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/SelectionListener.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/SelectionListener.java
new file mode 100644
index 0000000..7c785f1
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/SelectionListener.java
@@ -0,0 +1,26 @@
+/*******************************************************************************
+ * 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.swing;
+
+/**
+ * Interface for receiving selection change events. Typically, objects 
+ * implementing this interface are registered with a 
+ * {@link SelectionProvider}.
+ * @see Selection
+ */
+public interface SelectionListener {
+
+    /**
+     * Called by a selection provider when the current selection changes.
+     * @param selection the new {@link Selection}.
+     */
+    public void selectionChanged(Selection selection);
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/SelectionProvider.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/SelectionProvider.java
new file mode 100644
index 0000000..13fb2c6
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/SelectionProvider.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.swing;
+
+/**
+ * Represents a class that can fire selection change events to  
+ * {@link SelectionChangeListener}s.
+ */
+public interface SelectionProvider {
+    
+    /**
+     * Add the given {@link SelectionChangeListener} to be notified when
+     * the current selection changes.
+     * @param listener SelectionChangeListener to add.
+     */
+    public void addSelectionListener(SelectionListener listener);
+
+    /**
+     * Remove the given {@link SelectionChangeListener} from the
+     * notification list.
+     * @param listener SelectionChangeListener to remove.
+     */
+    public void removeSelectionListener(SelectionListener listener);
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/SelectionProviderImpl.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/SelectionProviderImpl.java
new file mode 100644
index 0000000..5c909dd
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/SelectionProviderImpl.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.swing;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Implementation of the {@link SelectionProvider} interface. Also acts as
+ * a selection event multiplexor: any events received on its 
+ * {@link SelectionListener} interface are relayed to any registered listeners. 
+ */
+public class SelectionProviderImpl 
+    implements SelectionProvider, SelectionListener {
+
+    private List listeners = new ArrayList();
+    
+    /**
+     * @see net.sf.vex.core.SelectionProvider#addSelectionChangeListener(net.sf.vex.SelectionListener)
+     */
+    public void addSelectionListener(SelectionListener listener) {
+        this.listeners.add(listener);
+
+    }
+
+    /**
+     * Call <code>selectionChanged</code> on all registered listeners.
+     * @param selection Selection that has changed.
+     */
+    public void fireSelectionChanged(Selection selection) {
+        for (Iterator iter = listeners.iterator(); iter.hasNext(); ) {
+            SelectionListener listener = (SelectionListener) iter.next();
+            //long start = System.currentTimeMillis();
+            listener.selectionChanged(selection);
+            //long end = System.currentTimeMillis();
+            //System.out.println("" + (end-start) + ": " + listener);
+        }
+    }
+    
+    /**
+     * @see net.sf.vex.core.SelectionProvider#removeSelectionChangeListener(net.sf.vex.SelectionListener)
+     */
+    public void removeSelectionListener(SelectionListener listener) {
+        this.listeners.remove(listener);
+    }
+
+    /**
+     * @see net.sf.vex.core.SelectionListener#selectionChanged(net.sf.vex.Selection)
+     */
+    public void selectionChanged(Selection selection) {
+        this.fireSelectionChanged(selection);
+    }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/VexComponent.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/VexComponent.java
new file mode 100644
index 0000000..534c1e5
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/VexComponent.java
@@ -0,0 +1,1133 @@
+/*******************************************************************************
+ * 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.swing;
+
+import java.awt.Cursor;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.GraphicsConfiguration;
+import java.awt.GraphicsDevice;
+import java.awt.GraphicsEnvironment;
+import java.awt.Rectangle;
+import java.awt.Toolkit;
+import java.awt.datatransfer.Clipboard;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.StringSelection;
+import java.awt.datatransfer.Transferable;
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.awt.event.ComponentListener;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionAdapter;
+import java.awt.event.MouseMotionListener;
+import java.io.IOException;
+import java.net.URL;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.ActionMap;
+import javax.swing.InputMap;
+import javax.swing.JComponent;
+import javax.swing.JPopupMenu;
+import javax.swing.KeyStroke;
+import javax.swing.Scrollable;
+import javax.swing.SwingUtilities;
+import javax.swing.Timer;
+import javax.swing.UIManager;
+import javax.swing.undo.CannotRedoException;
+import javax.swing.undo.CannotUndoException;
+import javax.xml.parsers.ParserConfigurationException;
+
+
+import org.eclipse.wst.xml.vex.core.internal.VEXCorePlugin;
+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.DisplayDevice;
+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.DocumentFragment;
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentValidationException;
+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.BoxFactory;
+import org.eclipse.wst.xml.vex.core.internal.widget.HostComponent;
+import org.eclipse.wst.xml.vex.core.internal.widget.IBoxFilter;
+import org.eclipse.wst.xml.vex.core.internal.widget.VexWidgetImpl;
+import org.xml.sax.SAXException;
+/**
+ * 
+ */
+public class VexComponent extends JComponent implements Scrollable, SelectionProvider {
+
+    private VexWidgetImpl impl;
+
+    private ActionMap staticActionMap = createActionMap();
+    private static InputMap staticInputMap = createInputMap();
+
+    private Timer caretTimer;
+
+    private int originX = 0;
+    private int originY = 0;
+    
+    // Temporary clipboard is used during begin/end work
+    private Clipboard clipboard =
+        Toolkit.getDefaultToolkit().getSystemClipboard();
+
+    private SelectionProviderImpl selectionProvider =
+        new SelectionProviderImpl();
+
+    private static ResourceBundle uiStringBundle;
+
+    // Last VexComponent that had the focus. We need this sometimes to
+    // determine the target of an action.
+    private static VexComponent lastFocusedComponent;
+
+
+    //======================================================= LISTENERS
+
+    private ActionListener caretTimerListener = new ActionListener() {
+        public void actionPerformed(ActionEvent e) {
+            VexComponent.this.impl.toggleCaret();
+        }
+    };
+    
+    private ComponentListener componentListener = new ComponentAdapter() {
+        public void componentResized(ComponentEvent e) {
+            int width = VexComponent.this.getWidth();
+            VexComponent.this.impl.setLayoutWidth(width);
+        }
+    };
+
+    private FocusListener focusListener = new FocusListener() {
+        public void focusGained(FocusEvent e) {
+            lastFocusedComponent = VexComponent.this;
+            impl.setFocus(true);
+            VexComponent.this.caretTimer.start();
+        }
+
+        public void focusLost(FocusEvent e) {
+            impl.setFocus(false);
+            VexComponent.this.caretTimer.stop();
+        }
+    };
+
+    private KeyListener keyListener = new KeyListener() {
+        public void keyPressed(KeyEvent e) {
+            
+            
+            //final char NEWLINE = 0xa;
+            
+            if (VexComponent.this.impl.getDocument() == null) {
+                Toolkit.getDefaultToolkit().beep();
+                return;
+            }
+            
+            if (e.getKeyCode() == KeyEvent.VK_SHIFT){
+                return;
+            }
+            
+            try {
+                InputMap map = VexComponent.staticInputMap;
+                KeyStroke keyStroke = KeyStroke.getKeyStrokeForEvent(e);
+                Object keyStrokeBinding = map.get(keyStroke);
+                if (keyStrokeBinding != null) {
+                    BaseAction action = (BaseAction)VexComponent.this.staticActionMap.get(map.get(keyStroke));
+                    action.actionPerformed(new ActionEvent(VexComponent.this, 0, ""), VexComponent.this.impl);
+                }else if (!Character.isISOControl(e.getKeyChar())
+                && !e.isControlDown()) {
+                    // We check e.isControlDown() to ensure Ctrl-Space does not
+                    // also enter a space.
+                    if (VexComponent.this.impl.hasSelection()) {
+                        VexComponent.this.impl.deleteSelection();
+                    }
+                    insertChar(e.getKeyChar());
+                }
+            } catch (DocumentValidationException ex) {
+                Toolkit.getDefaultToolkit().beep();
+            } catch (Exception ex){
+                Toolkit.getDefaultToolkit().beep();
+                ex.printStackTrace();
+            }
+            
+            
+        }
+        
+        public void keyReleased(KeyEvent e) {
+        }
+        
+        public void keyTyped(KeyEvent e) {}
+        
+    };
+    
+    
+    
+    
+    private MouseListener mouseListener = new MouseAdapter() {
+        public void mousePressed(MouseEvent e) {
+            
+            boolean isButton1 =
+                    (e.getModifiers() & MouseEvent.BUTTON1_MASK) > 0;
+            
+            if (VexComponent.this.impl.getRootBox() != null && isButton1) {
+                if (hasFocus()) {
+                    int offset = VexComponent.this.impl.viewToModel(e.getX() - originX, e.getY() - originY);
+                    moveTo(offset);
+                    
+                    if (e.getClickCount() == 2) {
+                        selectWord();
+                    }
+                }
+                requestFocus();
+            }
+        }
+    };
+
+    
+    
+    private MouseMotionListener mouseMotionListener =
+        new MouseMotionAdapter() {
+        public void mouseDragged(MouseEvent e) {
+            boolean isButton1 =
+                (e.getModifiers() & MouseEvent.BUTTON1_MASK) > 0;
+
+            if (VexComponent.this.impl.getRootBox() != null && isButton1) {
+                int offset = VexComponent.this.viewToModel(e.getX(), e.getY());
+                moveTo(offset, true);
+                requestFocus();
+            }
+        }
+    };
+
+
+    /**
+     * @see net.sf.vex.core.SelectionProvider#addSelectionListener(net.sf.vex.SelectionListener)
+     */
+    public void addSelectionListener(SelectionListener listener) {
+        this.selectionProvider.addSelectionListener(listener);
+    }
+
+    public boolean canPaste() {
+
+        // TODO: sacrifice paste toolbar button state for performance
+        // (see note below).
+        if (true) {
+            return true;
+        }
+
+        // TODO: This next line takes a looong time in X11
+        // ~130ms on a Pentum-M 1.6GHz on Linux 2.4.21/XFree86 4.3.0        
+        Transferable tfbl = this.clipboard.getContents(null);
+        DataFlavor flavor = VexSelection.VEX_DOCUMENT_FRAGMENT_FLAVOR;
+        if (!tfbl.isDataFlavorSupported(flavor)) {
+            return this.canPasteText();
+        }
+
+        DocumentFragment frag;
+        try {
+            frag = (DocumentFragment) tfbl.getTransferData(flavor);
+        } catch (UnsupportedFlavorException ex) {
+            return false;
+        } catch (IOException ex) {
+            return false;
+        }
+
+        return this.impl.canInsertFragment(frag);
+    }
+
+    /**
+     * Returns true if the clipboard has plain text content that can be
+     * pasted. Used to enable/disable the "paste text" action of a containing
+     * application.
+     */
+    public boolean canPasteText() {
+
+        // TODO: sacrifice paste toolbar button state for performance
+        // (see note below).
+        if (true) {
+            return true;
+        }
+
+        // TODO: This next line takes a looong time in X11
+        // ~130ms on a Pentum-M 1.6GHz on Linux 2.4.21/XFree86 4.3.0        
+        Transferable tfbl = this.clipboard.getContents(null);
+        DataFlavor plainText = new DataFlavor(String.class, "text/plain");
+        return tfbl.isDataFlavorSupported(plainText)
+            && this.impl.canPasteText();
+    }
+
+    /**
+     * Copy the current selection to the clipboard.
+     */
+    public void copySelection() {
+        if (this.impl.hasSelection()) {
+            StringSelection sel =
+                new VexSelection(
+                    this.impl.getSelectedText(),
+                    this.impl.getSelectedFragment());
+            this.clipboard.setContents(sel, sel);
+        }
+    }
+
+    public void cutSelection() {
+        this.copySelection();
+        this.deleteSelection();
+    }
+
+    public void deleteSelection() {
+        this.impl.deleteSelection();
+    }
+    
+    /**
+     * Returns the VexComponent that last had focus.
+     */
+    public static VexComponent getLastFocusedComponent() {
+        return lastFocusedComponent;
+    }
+
+    /**
+     * Returns a string from the resource bundle for the current locale.
+     * If the string is not found in the resource bundle, returns null.
+     *
+     * @param name property for which to return the string.
+     */
+    public static String getUIString(String name) {
+        if (uiStringBundle == null) {
+            uiStringBundle =
+                ResourceBundle.getBundle("net.sf.vex.app.UIStrings");
+        }
+
+        try {
+            return uiStringBundle.getString(name);
+        } catch (MissingResourceException ex) {
+            return null;
+        }
+    }
+
+
+    //------------------------------------------------ Scrollable methods
+
+    public Dimension getPreferredScrollableViewportSize() {
+        return this.getPreferredSize();
+    }
+
+    public int getScrollableBlockIncrement(
+        Rectangle visibleRect,
+        int orientation,
+        int direction) {
+        return Math.max(visibleRect.height - 40, 40);
+    }
+
+    public boolean getScrollableTracksViewportHeight() {
+        return false;
+    }
+
+    public boolean getScrollableTracksViewportWidth() {
+        return true;
+    }
+
+    public int getScrollableUnitIncrement(
+        Rectangle visibleRect,
+        int orientation,
+        int direction) {
+        return 20; // TODO: fix scrolling increment
+    }
+
+    public VexComponent() {
+        ActionMap customActionMap = new ActionMap();
+        customActionMap.setParent(staticActionMap);
+        this.setActionMap(customActionMap);
+
+        InputMap customInputMap = new InputMap();
+        customInputMap.setParent(staticInputMap);
+        this.setInputMap(JComponent.WHEN_FOCUSED, customInputMap);
+
+        this.addComponentListener(this.componentListener);
+        this.addFocusListener(this.focusListener);
+        this.addKeyListener(this.keyListener);
+        this.addMouseListener(this.mouseListener);
+        this.addMouseMotionListener(this.mouseMotionListener);
+        this.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
+        
+        this.caretTimer = new Timer(
+            UIManager.getInt("TextPane.caretBlinkRate"), 
+            this.caretTimerListener);
+        this.caretTimer.start();
+        
+        
+        DisplayDevice.setCurrent(new AwtDisplayDevice());
+        
+        impl = new VexWidgetImpl(this.hostComponent);
+
+    }
+    
+    public void insertElement(Element element) throws DocumentValidationException {
+        this.impl.insertElement(element);
+    }
+    
+    public void morph(Element element) throws DocumentValidationException {
+        this.impl.morph(element);
+    }
+    
+    public void paint(Graphics g){
+    	this.paintComponent(g);
+    }
+    
+    protected void paintComponent(Graphics g) {
+
+    	long start = 0;
+        if (VEXCorePlugin.getInstance().isDebugging()) {
+        	start = System.currentTimeMillis();
+        }
+        
+        if (this.impl.getDocument() == null){
+            return;
+        }
+
+        AwtGraphics awtg = new AwtGraphics((Graphics2D) g);
+        awtg.setOrigin(0, this.originY);        
+        
+        Color bgColor = impl.getBackgroundColor();
+        if (bgColor == null) {
+            bgColor = new Color(255, 255, 255);
+        }
+
+        ColorResource color = awtg.createColor(bgColor);
+        ColorResource oldColor = awtg.setColor(color);
+        Rectangle r = g.getClipBounds();
+        awtg.fillRect((int)r.getX() - this.originX, (int)r.getY() - this.originY,(int) r.getWidth(),(int) r.getHeight());
+        awtg.setColor(oldColor);
+        color.dispose();        
+        
+        this.impl.paint(awtg, 0, 0);
+        
+        
+        
+/*
+        Graphics2D g2d = (Graphics2D) g;
+        if (this.isAntiAliased()) {
+            g2d.setRenderingHint(
+                RenderingHints.KEY_ANTIALIASING,
+                RenderingHints.VALUE_ANTIALIAS_ON);
+        }
+
+        impl.paint(new AwtGraphics(g2d), 0, 0);
+
+        if (this.caretVisible && this.isEnabled()) {
+            if (this.hasFocus()) {
+                g.setColor(Color.black);
+            } else {
+                g.setColor(Color.gray);
+            }
+            g2d.setStroke(caretStroke);
+            g2d.draw(this.caretShapes[0]);
+        }
+*/
+
+        if (VEXCorePlugin.getInstance().isDebugging()) {
+        	long end = System.currentTimeMillis();
+        	System.out.println("paint took " + (end - start) + "ms");
+        }
+    }
+
+    public void paste() throws DocumentValidationException {
+
+        if (!this.canPaste()) {
+            return;
+        }
+
+        try {
+            Transferable tfbl = this.clipboard.getContents(null);
+            DataFlavor flavor = VexSelection.VEX_DOCUMENT_FRAGMENT_FLAVOR;
+            if (tfbl.isDataFlavorSupported(flavor)) {
+                DocumentFragment frag =
+                    (DocumentFragment) tfbl.getTransferData(flavor);
+                this.impl.insertFragment(frag);
+            } else {
+                this.pasteText();
+            }
+        } catch (IOException ex) {
+            ex.printStackTrace();
+        } catch (UnsupportedFlavorException ex) {
+            ex.printStackTrace();
+        }
+    }
+
+    public void pasteText() throws DocumentValidationException {
+
+        try {
+            Transferable tfbl = this.clipboard.getContents(null);
+            DataFlavor plainText = new DataFlavor(String.class, "text/plain");
+            if (tfbl.isDataFlavorSupported(plainText)) {
+                String text = (String) tfbl.getTransferData(plainText);
+                this.impl.insertText(text);
+            }
+        } catch (IOException ex) {
+            ex.printStackTrace();
+        } catch (UnsupportedFlavorException ex) {
+            ex.printStackTrace();
+        }
+    }
+    
+    public void split() throws DocumentValidationException { this.impl.split(); }
+    public void undo() throws CannotUndoException { this.impl.undo(); }
+    public int viewToModel(int x, int y) { return this.impl.viewToModel(x, y); }
+
+    public void deleteNextChar() throws DocumentValidationException { this.impl.deleteNextChar(); }
+    public void deletePreviousChar() throws DocumentValidationException { this.impl.deletePreviousChar(); }
+    public void doWork(Runnable runnable) { this.impl.doWork(runnable); }
+    public void doWork(boolean savePosition, Runnable runnable) { this.impl.doWork(savePosition, runnable); }
+    public void endWork(boolean success) { this.impl.endWork(success); }
+    public Box findInnermostBox(IBoxFilter filter) { return this.impl.findInnermostBox(filter); }
+    public BoxFactory getBoxFactory() { return this.impl.getBoxFactory(); }
+    public int getCaretOffset() { return this.impl.getCaretOffset(); }
+    public Element getCurrentElement() { return this.impl.getCurrentElement(); }
+    public Document getDocument() { return this.impl.getDocument(); }
+    public int getLayoutWidth() { return this.impl.getLayoutWidth(); }
+    public int getSelectionEnd() { return this.impl.getSelectionEnd(); }
+    public int getSelectionStart() { return this.impl.getSelectionStart(); }
+    public DocumentFragment getSelectedFragment() { return this.impl.getSelectedFragment(); }
+    public String getSelectedText() { return this.impl.getSelectedText(); }
+    public StyleSheet getStyleSheet() { return this.impl.getStyleSheet(); }
+    public int getUndoDepth() { return this.impl.getUndoDepth(); }
+    public String[] getValidInsertElements() { return this.impl.getValidInsertElements(); }
+    public String[] getValidMorphElements() { return this.impl.getValidMorphElements(); }
+    public boolean hasSelection() { return this.impl.hasSelection(); }
+    public void insertChar(char c) throws DocumentValidationException { this.impl.insertChar(c); }
+    public void insertFragment(DocumentFragment frag) throws DocumentValidationException { this.impl.insertFragment(frag); }
+    public void insertText(String text) throws DocumentValidationException { this.impl.insertText(text); }
+    public boolean isDebugging() { return impl.isDebugging(); }    
+    public void moveBy(int distance) { this.impl.moveBy(distance); }
+    public void moveBy(int distance, boolean select) { this.impl.moveBy(distance, select); }
+    public void moveTo(int offset) { this.impl.moveTo(offset); }
+    public void moveTo(int offset, boolean select) { this.impl.moveTo(offset, select); }
+    public void moveToLineEnd(boolean select) { this.impl.moveToLineEnd(select); }
+    public void moveToLineStart(boolean select) { this.impl.moveToLineStart(select); }
+    public void moveToNextLine(boolean select) { this.impl.moveToNextLine(select); }
+    public void moveToNextPage(boolean select) { this.impl.moveToNextPage(select); }
+    public void moveToNextWord(boolean select) { this.impl.moveToNextWord(select); }
+    public void moveToPreviousLine(boolean select) { this.impl.moveToPreviousLine(select); }
+    public void moveToPreviousPage(boolean select) { this.impl.moveToPreviousPage(select); }
+    public void moveToPreviousWord(boolean select) { this.impl.moveToPreviousWord(select); }
+
+    
+    public void redo() throws CannotRedoException { this.impl.redo(); }
+    public void removeAttribute(String attributeName) { this.impl.removeAttribute(attributeName); }
+    public void savePosition(Runnable runnable) { this.impl.savePosition(runnable); }
+    public void selectAll() { this.impl.selectAll(); }
+    public void selectWord() { this.impl.selectWord(); }
+    public void setAttribute(String attributeName, String value) { this.impl.setAttribute(attributeName, value); }
+    public void setBoxFactory(BoxFactory boxFactory) { this.impl.setBoxFactory(boxFactory); }
+    public void setDebugging(boolean debugging) { impl.setDebugging(debugging); }
+    public void setDocument(Document doc, StyleSheet styleSheet) { this.impl.setDocument(doc, styleSheet); }
+    public void setDocument(URL docUrl, URL ssURL) throws IOException, ParserConfigurationException, SAXException {
+        this.impl.setDocument(docUrl, ssURL);
+    }
+    public void setLayoutWidth(int width) { this.impl.setLayoutWidth(width); }
+    public void setStyleSheet(StyleSheet styleSheet) { this.impl.setStyleSheet(styleSheet); }
+    public void setStyleSheet(URL ssUrl) throws IOException { this.impl.setStyleSheet(ssUrl); }
+    
+    /**
+     * @see SelectionProvider#removeSelectionListener
+     */
+    public void removeSelectionListener(SelectionListener listener) {
+        this.selectionProvider.removeSelectionListener(listener);
+    }
+
+    public Action[] getInsertElementActions(){
+       String[] names = this.getValidInsertElements();
+       Action[] actions = new Action[names.length];
+       for (int i = 0; i < names.length; i++){
+            actions[i] = new InsertElementAction(names[i]);
+       }
+       return actions;
+    }
+    
+    public Action[] getMorphElementActions(){
+       String[] names = this.getValidMorphElements();
+       Action[] actions = new Action[names.length];
+       for (int i = 0; i < names.length; i++){
+            actions[i] = new MorphAction(names[i]);
+       }
+       return actions;
+    }
+         
+
+    /**
+     * Display a popup menu of valid elements to insert at the current caret
+     * position.
+     */
+    public void showInsertElementPopup() {
+
+        JPopupMenu popup = new JPopupMenu();
+
+        Action[] actions = this.getInsertElementActions();
+        
+        if (actions.length == 0) {
+            return;
+        }
+        
+        for (int i = 0; i < actions.length; i++) {
+            popup.add(actions[i]);
+        }
+        
+        org.eclipse.wst.xml.vex.core.internal.core.Rectangle caretBounds = this.impl.getCaret().getBounds();
+        popup.show(this, caretBounds.getX() + 10, caretBounds.getY());
+    }
+    
+    /**
+     * Display a popup menu of valid elements to which the current element
+     * can be morphed.
+     */
+    public void showMorphElementPopup() {
+
+        JPopupMenu popup = new JPopupMenu();
+
+        Action[] actions = this.getMorphElementActions();
+        
+        if (actions.length == 0) {
+            return;
+        }
+        
+        for (int i = 0; i < actions.length; i++) {
+            popup.add(actions[i]);
+        }
+        
+        org.eclipse.wst.xml.vex.core.internal.core.Rectangle caretBounds = this.impl.getCaret().getBounds();
+        popup.show(this, caretBounds.getX() + 10, caretBounds.getY());
+    }
+    
+
+    //========================================================= PRIVATE
+
+    private HostComponent hostComponent = new HostComponent() {
+
+        // This is needed to create a default Graphics object,
+        // but creating these is really slow, so we cache 'em.
+        private GraphicsEnvironment graphicsEnvironment = null;
+        private java.awt.image.BufferedImage dummyImage = null;
+
+        public org.eclipse.wst.xml.vex.core.internal.core.Graphics createDefaultGraphics() {
+            if (graphicsEnvironment == null) {
+                graphicsEnvironment =
+                    GraphicsEnvironment.getLocalGraphicsEnvironment();
+
+                GraphicsDevice gdev = graphicsEnvironment.getDefaultScreenDevice();
+                GraphicsConfiguration gconf = gdev.getDefaultConfiguration();
+                dummyImage = gconf.createCompatibleImage(1, 1);
+            }
+            Graphics g = graphicsEnvironment.createGraphics(dummyImage);
+            return new AwtGraphics((Graphics2D) g);
+        }
+
+        public void fireSelectionChanged() {
+            VexComponent.this.fireSelectionChanged();
+        }
+        
+        public void invokeLater(Runnable runnable) {
+            SwingUtilities.invokeLater(runnable);
+        }
+        
+        public void repaint() {
+            VexComponent.this.repaint();
+        }
+
+        public void repaint(int x, int y, int width, int height) {
+           VexComponent.this.repaint();//VexComponent.this.repaint(x , y, width, height);
+        }
+
+        public void scrollTo(int left, int top) {
+            VexComponent.this.setOrigin(-left, -top);
+        }
+        
+        public void setPreferredSize(int width, int height) {
+/*            Dimension size = new Dimension(width, height);
+            VexComponent.this.setPreferredSize(size);
+            VexComponent.this.setSize(size);*/
+        }
+
+        public org.eclipse.wst.xml.vex.core.internal.core.Rectangle getViewport() {
+            
+            return new org.eclipse.wst.xml.vex.core.internal.core.Rectangle(
+                            0 - VexComponent.this.originX,
+                            0 - VexComponent.this.originY,
+                            VexComponent.this.getWidth(),
+                            VexComponent.this.getHeight());
+            
+        }
+    };
+    
+
+
+    /**
+     * Action for inserting an element into the document at the current
+     * offset.
+     */
+    private class InsertElementAction extends AbstractAction {
+
+        private String name;
+
+        /**
+         * Class constructor.
+         *
+         * @param name Name of the element to insert.
+         */
+        public InsertElementAction(String name) {
+            super(name);
+            this.name = name;
+        }
+
+        public void actionPerformed(ActionEvent e) {
+            try {
+                insertElement(new Element(this.name));
+            } catch (DocumentValidationException ex) {
+                Toolkit.getDefaultToolkit().beep();
+            }
+        }
+    }
+    
+
+    /**
+     * Action for morphing offset.
+     */
+    private class MorphAction extends AbstractAction {
+
+        private String name;
+
+        /**
+         * Class constructor.
+         *
+         * @param name Name of the element to which the current element
+         * is to be morphed.
+         */
+        public MorphAction(String name) {
+            super(name);
+            this.name = name;
+        }
+
+        public void actionPerformed(ActionEvent e) {
+            try {
+                morph(new Element(this.name));
+            } catch (DocumentValidationException ex) {
+                Toolkit.getDefaultToolkit().beep();
+            }
+        }
+    }
+
+    /**
+     * Runnable to layout the control.
+     */
+//    private class LayoutRunnable implements Runnable {
+//        private boolean force;
+//        public LayoutRunnable(boolean force) {
+//            this.force = force;
+//        }
+//
+//        public void run() {
+//            relayout(force);
+//        }
+//    }
+
+
+    /**
+     * Base class for actions in the action map. Overloads the actionPerformed
+     * method to pass a VexComponent and allow a DocumentValidationException
+     * to be returned.
+     */
+    private abstract static class BaseAction extends AbstractAction {
+        /**
+         * Calls <code>actionPerformed(ActionEvent e, VexComponent c)</code>,
+         * and beeps if an exception is thrown.
+         * @param e ActionEvent 
+         */
+        public void actionPerformed(ActionEvent e) {
+            try {
+                if (e.getSource() instanceof VexWidgetImpl) {
+                    this.actionPerformed(e, (VexWidgetImpl) e.getSource());
+                }
+            } catch (Exception ex) {
+                ex.printStackTrace(); // TODO: log this
+                Toolkit.getDefaultToolkit().beep();
+            }
+        }
+
+        /**
+         * Performs the action. The corresponding VexComponent is provided,
+         * and any exception can be thrown.
+         * @param e the ActionEvent
+         * @param c the VexComponent
+         */
+        public abstract void actionPerformed(ActionEvent e, VexWidgetImpl c)
+            throws Exception;
+
+    }
+
+    public int getHeight(){
+        return impl.getHeight();        
+    }
+    
+    public void scrollBy(int x, int y){
+        setOrigin(originX - x, originY - y);
+    }
+    
+    public void scrollTo(int x, int y){
+        setOrigin(-x, -y);
+    }
+    
+    private void setOrigin(int x, int y){
+        this.originX = x;
+        this.originY = y;       
+    }
+
+    /**
+     * Creates the ActionMap for the component.
+     */
+    private ActionMap createActionMap() {
+        ActionMap am = new ActionMap();
+
+        am.put("copy-selection", new BaseAction() {
+            public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+                throws DocumentValidationException {
+                copySelection();
+            }
+        });
+        am.put("cut-selection", new BaseAction() {
+            public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+                throws DocumentValidationException {
+                cutSelection();
+            }
+        });
+        am.put("delete-next-char", new BaseAction() {
+            public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+                throws DocumentValidationException {
+                c.deleteNextChar();
+            }
+        });
+        am.put("delete-previous-char", new BaseAction() {
+            public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+                throws DocumentValidationException {
+                c.deletePreviousChar();
+            }
+        });
+        am.put("move-to-document-end", new BaseAction() {
+            public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+                throws DocumentValidationException {
+                c.moveTo(c.getDocument().getLength() - 1, false);
+            }
+        });
+        am.put("move-to-document-start", new BaseAction() {
+            public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+                throws DocumentValidationException {
+                c.moveTo(1, false);
+            }
+        });
+        am.put("move-to-line-end", new BaseAction() {
+            public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+                throws DocumentValidationException {
+                c.moveToLineEnd(false);
+            }
+        });
+        am.put("move-to-line-start", new BaseAction() {
+            public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+                throws DocumentValidationException {
+                c.moveToLineStart(false);
+            }
+        });
+        am.put("move-to-next-char", new BaseAction() {
+            public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+                throws DocumentValidationException {
+                int offset = c.getCaretOffset();
+                if (offset < c.getDocument().getLength() - 1) {
+                    c.moveTo(offset + 1, false);
+                }
+            }
+        });
+        am.put("move-to-next-line", new BaseAction() {
+            public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+                throws DocumentValidationException {
+                c.moveToNextLine(false);
+            }
+        });
+        am.put("move-to-next-word", new BaseAction() {
+            public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+                throws DocumentValidationException {
+                c.moveToNextWord(false);
+            }
+        });
+        am.put("move-to-previous-char", new BaseAction() {
+            public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+                throws DocumentValidationException {
+                int offset = c.getCaretOffset();
+                if (offset > 1) {
+                    c.moveTo(offset - 1, false);
+                }
+            }
+        });
+        am.put("move-to-previous-line", new BaseAction() {
+            public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+                throws DocumentValidationException {
+                c.moveToPreviousLine(false);
+            }
+        });
+        am.put("move-to-previous-word", new BaseAction() {
+            public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+                throws DocumentValidationException {
+                c.moveToPreviousWord(false);
+            }
+        });
+        am.put("paste", new BaseAction() {
+            public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+                throws DocumentValidationException {
+                paste();
+            }
+        });
+        am.put("paste-text", new BaseAction() {
+            public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+                throws DocumentValidationException {
+                pasteText();
+            }
+        });
+        am.put("redo", new BaseAction() {
+            public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+                throws DocumentValidationException {
+                c.redo();
+            }
+        });
+        am.put("select-to-document-end", new BaseAction() {
+            public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+                throws DocumentValidationException {
+                c.moveTo(c.getDocument().getLength() - 1, true);
+            }
+        });
+        am.put("select-to-document-start", new BaseAction() {
+            public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+                throws DocumentValidationException {
+                c.moveTo(1, true);
+            }
+        });
+        am.put("select-to-line-end", new BaseAction() {
+            public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+                throws DocumentValidationException {
+                c.moveToLineEnd(true);
+            }
+        });
+        am.put("select-to-line-start", new BaseAction() {
+            public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+                throws DocumentValidationException {
+                c.moveToLineStart(true);
+            }
+        });
+        am.put("select-to-next-char", new BaseAction() {
+            public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+                throws DocumentValidationException {
+                int offset = c.getCaretOffset();
+                if (offset < c.getDocument().getLength() - 1) {
+                    c.moveTo(offset + 1, true);
+                }
+            }
+        });
+        am.put("select-to-next-line", new BaseAction() {
+            public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+                throws DocumentValidationException {
+                c.moveToNextLine(true);
+            }
+        });
+        am.put("select-to-next-word", new BaseAction() {
+            public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+                throws DocumentValidationException {
+                c.moveToNextWord(true);
+            }
+        });
+        am.put("select-to-previous-char", new BaseAction() {
+            public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+                throws DocumentValidationException {
+                int offset = c.getCaretOffset();
+                if (offset > 1) {
+                    c.moveTo(offset - 1, true);
+                }
+            }
+        });
+        am.put("select-to-previous-line", new BaseAction() {
+            public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+                throws DocumentValidationException {
+                c.moveToPreviousLine(true);
+            }
+        });
+        am.put("select-to-previous-word", new BaseAction() {
+            public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+            throws DocumentValidationException {
+                c.moveToPreviousWord(true);
+            }
+        });
+        
+        am.put("move-to-previous-page" , new BaseAction(){
+           public void actionPerformed(ActionEvent e, VexWidgetImpl c) 
+           throws DocumentValidationException {
+                c.moveToPreviousPage(false);
+           }            
+        });
+        
+        am.put("select-to-previous-page" , new BaseAction(){
+           public void actionPerformed(ActionEvent e, VexWidgetImpl c) 
+           throws DocumentValidationException {
+                c.moveToPreviousPage(true);
+           }            
+        });
+        
+        am.put("move-to-next-page" , new BaseAction(){
+           public void actionPerformed(ActionEvent e, VexWidgetImpl c) 
+           throws DocumentValidationException {
+                c.moveToNextPage(false);
+           }            
+        });
+
+        am.put("select-to-next-page" , new BaseAction(){
+           public void actionPerformed(ActionEvent e, VexWidgetImpl c) 
+           throws DocumentValidationException {
+                c.moveToNextPage(true);
+           }            
+        });        
+        
+        am.put("show-insert-element-popup", new BaseAction() {
+            public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+            throws DocumentValidationException {
+
+                VexComponent.this.showInsertElementPopup();
+            }
+        });
+        am.put("show-morph-element-popup", new BaseAction() {
+            public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+            throws DocumentValidationException {
+                VexComponent.this.showMorphElementPopup();
+            }
+        });
+        am.put("split", new BaseAction() {
+            public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+            throws DocumentValidationException {
+                c.split();
+            }
+        });
+        am.put("undo", new BaseAction() {
+            public void actionPerformed(ActionEvent e, VexWidgetImpl c)
+                throws DocumentValidationException {
+                c.undo();
+            }
+        });
+
+        return am;
+    }
+
+
+    /**
+     * Create the input map for the control. This input map can reference
+     * both static and non-static actions.
+     */
+    public static InputMap createInputMap() {
+        InputMap im = new InputMap();
+        im.put(KeyStroke.getKeyStroke("LEFT"), "move-to-previous-char");
+        im.put(KeyStroke.getKeyStroke("RIGHT"), "move-to-next-char");
+        im.put(KeyStroke.getKeyStroke("shift LEFT"), "select-to-previous-char");
+        im.put(KeyStroke.getKeyStroke("shift RIGHT"), "select-to-next-char");
+
+        im.put(KeyStroke.getKeyStroke("control LEFT"), "move-to-previous-word");
+        im.put(KeyStroke.getKeyStroke("control RIGHT"), "move-to-next-word");
+        im.put(
+            KeyStroke.getKeyStroke("shift control LEFT"),
+            "select-to-previous-word");
+        im.put(
+            KeyStroke.getKeyStroke("shift control RIGHT"),
+            "select-to-next-word");
+
+        im.put(KeyStroke.getKeyStroke("HOME"), "move-to-line-start");
+        im.put(KeyStroke.getKeyStroke("END"), "move-to-line-end");
+        im.put(KeyStroke.getKeyStroke("shift HOME"), "select-to-line-start");
+        im.put(KeyStroke.getKeyStroke("shift END"), "select-to-line-end");
+
+        im.put(KeyStroke.getKeyStroke("PAGE_UP"), "move-to-previous-page");
+        im.put(KeyStroke.getKeyStroke("PAGE_DOWN"), "move-to-next-page");
+        im.put(KeyStroke.getKeyStroke("shift PAGE_UP"), "select-to-previous-page");
+        im.put(KeyStroke.getKeyStroke("shift PAGE_DOWN"), "select-to-next-page");
+        
+        im.put(
+            KeyStroke.getKeyStroke("control HOME"),
+            "move-to-document-start");
+        im.put(KeyStroke.getKeyStroke("control END"), "move-to-document-end");
+        im.put(
+            KeyStroke.getKeyStroke("shift control HOME"),
+            "select-to-document-start");
+        im.put(
+            KeyStroke.getKeyStroke("shift control END"),
+            "select-to-document-end");
+
+        im.put(KeyStroke.getKeyStroke("UP"), "move-to-previous-line");
+        im.put(KeyStroke.getKeyStroke("DOWN"), "move-to-next-line");
+        im.put(KeyStroke.getKeyStroke("shift UP"), "select-to-previous-line");
+        im.put(KeyStroke.getKeyStroke("shift DOWN"), "select-to-next-line");
+
+        im.put(KeyStroke.getKeyStroke("BACK_SPACE"), "delete-previous-char");
+        im.put(KeyStroke.getKeyStroke("DELETE"), "delete-next-char");
+        im.put(KeyStroke.getKeyStroke("ENTER"), "split");
+
+        im.put(KeyStroke.getKeyStroke("control C"), "copy-selection");
+        im.put(KeyStroke.getKeyStroke("control X"), "cut-selection");
+        im.put(KeyStroke.getKeyStroke("control V"), "paste");
+        im.put(KeyStroke.getKeyStroke("shift control V"), "paste-text");
+
+        im.put(KeyStroke.getKeyStroke("control Y"), "redo");
+        im.put(KeyStroke.getKeyStroke("control Z"), "undo");
+
+        im.put(KeyStroke.getKeyStroke("control U"), "unwrap");
+
+        im.put(KeyStroke.getKeyStroke("control SPACE"), "show-insert-element-popup");
+        im.put(KeyStroke.getKeyStroke("control M"), "show-morph-element-popup");
+
+        return im;
+    }
+
+    /**
+     * Fires a selection changed event to registered selection change listeners.
+     * This method suppresses events that occur while there are outstanding
+     * beginWork calls.
+     */
+    private void fireSelectionChanged() {
+//        if (this.beginWorkCount == 0) {
+//            this.selectionProvider.fireSelectionChanged(
+//                new VexComponentSelection(this));
+//        }
+    }
+
+    /**
+     * Calls relayout from the Swing event loop rather than immediately.
+     *
+     * @param force Layout should be forced, e.g. when the width of
+     * the component changed.
+     */
+//    public void relayoutLater(boolean force) {
+//        if (this.rootBox != null) {
+//            SwingUtilities.invokeLater(new LayoutRunnable(force));
+//        }
+//    }
+
+
+
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/VexComponentSelection.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/VexComponentSelection.java
new file mode 100644
index 0000000..4532830
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/VexComponentSelection.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * 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.swing;
+
+import org.eclipse.wst.xml.vex.core.internal.widget.VexWidgetImpl;
+
+/**
+ * Generated by a {@VexComponent} when it gains focus, when its caret moves, 
+ * and when its selection changes.
+ */
+public class VexComponentSelection implements Selection {
+
+    private VexWidgetImpl vexComponent;
+    
+    /**
+     * Class constructor.
+     * @param vexComponent Component that is generating this change.
+     */
+    public VexComponentSelection(VexWidgetImpl vexComponent) {
+        this.vexComponent = vexComponent;
+    }
+    
+    /**
+     * @return {@link VexComponent} that generated this selection event.
+     */
+    public VexWidgetImpl getVexComponent() {
+        return this.vexComponent;
+    }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/VexSelection.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/VexSelection.java
new file mode 100644
index 0000000..21ba362
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swing/VexSelection.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.swing;
+
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.StringSelection;
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.io.IOException;
+
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentFragment;
+
+
+/**
+ * Represents a selection of a Vex document, which can be viewed as plaintext
+ * or as a document fragment.
+ */
+public class VexSelection extends StringSelection {
+
+    /**
+     * DataFlavor representing a Vex document fragment. 
+     */
+    public static final DataFlavor VEX_DOCUMENT_FRAGMENT_FLAVOR =
+        new DataFlavor(DocumentFragment.class, DocumentFragment.MIME_TYPE);
+
+    private DataFlavor[] flavors;        
+    private DocumentFragment frag;
+    
+    /**
+     * Class constructor.
+     * @param s String representing the selection.
+     * @param frag Document fragment representing the selection. 
+     */
+    public VexSelection(String s, DocumentFragment frag) {
+        super(s);
+        this.frag = frag;
+        
+        DataFlavor[] superFlavors = super.getTransferDataFlavors();
+        this.flavors = new DataFlavor[superFlavors.length + 1];
+        System.arraycopy(superFlavors, 0, this.flavors, 0, superFlavors.length);
+        this.flavors[this.flavors.length - 1] = VEX_DOCUMENT_FRAGMENT_FLAVOR;
+    }
+
+    public Object getTransferData(DataFlavor flavor)
+        throws UnsupportedFlavorException, IOException {
+            
+        if (flavor.equals(VEX_DOCUMENT_FRAGMENT_FLAVOR)) {
+            return this.frag;
+        } else {
+            return super.getTransferData(flavor);
+        }
+    }
+
+    public DataFlavor[] getTransferDataFlavors() {
+        return this.flavors;
+    }
+
+    public boolean isDataFlavorSupported(DataFlavor flavor) {
+        if (flavor.equals(VEX_DOCUMENT_FRAGMENT_FLAVOR)) {
+            return true;
+        } else {
+            return super.isDataFlavorSupported(flavor);
+        }
+    }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/DocumentFragmentTransfer.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/DocumentFragmentTransfer.java
new file mode 100644
index 0000000..2065330
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/DocumentFragmentTransfer.java
@@ -0,0 +1,98 @@
+/*******************************************************************************
+ * 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.swt;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+
+import org.eclipse.swt.dnd.ByteArrayTransfer;
+import org.eclipse.swt.dnd.TransferData;
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentFragment;
+
+/**
+ * Transfer object that handles Vex DocumentFragments. 
+ */
+public class DocumentFragmentTransfer extends ByteArrayTransfer {
+
+    /**
+     * Returns the singleton instance of the DocumentFragmentTransfer.
+     */
+    public static DocumentFragmentTransfer getInstance() {
+        if (instance == null) {
+            instance = new DocumentFragmentTransfer();
+        }
+        return instance;
+    }
+    
+    protected String[] getTypeNames() {
+        return typeNames;
+    }
+
+    protected int[] getTypeIds() {
+        return typeIds;
+    }
+
+    public void javaToNative (Object object, TransferData transferData) {
+        if (object == null || !(object instanceof DocumentFragment)) return;
+    
+        if (isSupportedType(transferData)) {
+            DocumentFragment frag = (DocumentFragment) object;   
+            try {
+                // write data to a byte array and then ask super to convert to pMedium
+                ByteArrayOutputStream out = new ByteArrayOutputStream();
+                ObjectOutputStream oos = new ObjectOutputStream(out);
+                oos.writeObject(frag);
+                byte[] buffer = out.toByteArray();
+                oos.close();
+                super.javaToNative(buffer, transferData);
+            } catch (IOException e) {
+            }
+        }
+     }
+
+     public Object nativeToJava(TransferData transferData){ 
+ 
+        if (isSupportedType(transferData)) {
+            byte[] buffer = (byte[])super.nativeToJava(transferData);
+            if (buffer == null) return null;
+        
+            try {
+                ByteArrayInputStream in = new ByteArrayInputStream(buffer);
+                ObjectInputStream ois = new ObjectInputStream(in);
+                Object object = ois.readObject();
+                ois.close();
+                return object;
+            } catch (ClassNotFoundException ex) {
+                return null;
+            } catch (IOException ex) {
+                return null;
+            }
+        }
+ 
+        return null;
+     }
+
+    //=================================================== PRIVATE
+    
+    private static final String[] typeNames = { DocumentFragment.MIME_TYPE };
+    private static final int[] typeIds = { 
+        ByteArrayTransfer.registerType(DocumentFragment.MIME_TYPE) 
+    };
+    
+    private static DocumentFragmentTransfer instance;
+    
+    private DocumentFragmentTransfer() {
+    }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/KeyStroke.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/KeyStroke.java
new file mode 100644
index 0000000..c08e7d7
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/KeyStroke.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * 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.swt;
+
+import org.eclipse.swt.events.KeyEvent;
+
+/**
+ * Represents a keystroke and a certain set of modifiers.
+ */
+public class KeyStroke {
+
+    private char character;
+    private int keyCode;
+    private int stateMask;
+    
+    /**
+     * Class constructor.
+     * @param character the key character
+     * @param keyCode the key code
+     * @param stateMask the set of modifiers
+     */
+    public KeyStroke(char character, int keyCode, int stateMask) {
+        this.character = character;
+        this.keyCode = keyCode;
+        this.stateMask = stateMask;
+    }
+    
+    /**
+     * Class constructor.
+     * @param e a KeyEvent representing the key stroke
+     */
+    public KeyStroke(KeyEvent e) {
+        this.character = e.character;
+        this.keyCode = e.keyCode;
+        this.stateMask = e.stateMask;
+    }
+    
+    public boolean equals(Object o) {
+        if (o == null || !(o instanceof KeyStroke)) {
+            return false;
+        }
+        KeyStroke other = (KeyStroke) o;
+        return this.character == other.character
+            && this.keyCode == other.keyCode
+            && this.stateMask == other.stateMask;
+    }
+    
+    public int hashCode() {
+        return this.character + this.keyCode + this.stateMask;
+    }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/SwtColor.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/SwtColor.java
new file mode 100644
index 0000000..0a6236d
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/SwtColor.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.swt;
+
+import org.eclipse.wst.xml.vex.core.internal.core.ColorResource;
+
+/**
+ * Wrapper for the SWT Color class.
+ */
+public class SwtColor implements ColorResource {
+
+    private org.eclipse.swt.graphics.Color swtColor;
+    
+    public SwtColor(org.eclipse.swt.graphics.Color swtColor) {
+        this.swtColor = swtColor;
+    }
+    
+    org.eclipse.swt.graphics.Color getSwtColor() {
+        return this.swtColor;
+    }
+    
+    public void dispose() {
+        this.swtColor.dispose();
+    }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/SwtDisplayDevice.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/SwtDisplayDevice.java
new file mode 100644
index 0000000..824d1e5
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/SwtDisplayDevice.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * 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.swt;
+
+
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.wst.xml.vex.core.internal.core.DisplayDevice;
+
+/**
+ * Adapts the DisplayDevice display to the current SWT display.
+ */
+public class SwtDisplayDevice extends DisplayDevice {
+
+    /**
+     * Class constructor.
+     */
+    public SwtDisplayDevice() {
+        // We used to do it like this, but it turns out sometimes we did it 
+        // too early and getCurrent() returned null, so now the convoluted stuff below.
+//        Display display = Display.getCurrent();
+//        this.horizontalPPI = display.getDPI().x;
+//        this.verticalPPI = display.getDPI().y;
+    }
+
+    public int getHorizontalPPI() {
+        if (!this.loaded) {
+            this.load();
+        }
+        return this.horizontalPPI;
+    }
+
+    public int getVerticalPPI() {
+        if (!this.loaded) {
+            this.load();
+        }
+        return this.verticalPPI;
+    }
+
+    private boolean loaded = false;
+    private int horizontalPPI = 72;
+    private int verticalPPI = 72;
+    
+    private void load() {
+        Display display = Display.getCurrent();
+        if (display != null) {
+            this.horizontalPPI = display.getDPI().x;
+            this.verticalPPI = display.getDPI().y;
+            this.loaded = true;
+        }        
+    }
+    
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/SwtFont.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/SwtFont.java
new file mode 100644
index 0000000..0426b27
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/SwtFont.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.swt;
+
+import org.eclipse.wst.xml.vex.core.internal.core.FontResource;
+
+/**
+ * Wrapper for the SWT Font class.
+ */
+public class SwtFont implements FontResource {
+
+    private org.eclipse.swt.graphics.Font swtFont;
+    
+    public SwtFont(org.eclipse.swt.graphics.Font swtFont) {
+        this.swtFont = swtFont;
+    }
+    
+    org.eclipse.swt.graphics.Font getSwtFont() {
+        return this.swtFont;
+    }
+    
+    public void dispose() {
+        this.swtFont.dispose();
+    }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/SwtFontMetrics.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/SwtFontMetrics.java
new file mode 100644
index 0000000..cac979e
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/SwtFontMetrics.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.swt;
+
+import org.eclipse.wst.xml.vex.core.internal.core.FontMetrics;
+
+/**
+ * Wrapper for the SWT FontMetrics class.
+ */
+public class SwtFontMetrics implements FontMetrics {
+    
+    private org.eclipse.swt.graphics.FontMetrics swtFontMetrics;
+
+    public SwtFontMetrics(org.eclipse.swt.graphics.FontMetrics swtFontMetrics) {
+        this.swtFontMetrics = swtFontMetrics;
+    }
+    
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.core.FontMetrics#getAscent()
+     */
+    public int getAscent() {
+        return this.swtFontMetrics.getAscent();
+    }
+
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.core.FontMetrics#getDescent()
+     */
+    public int getDescent() {
+        return this.swtFontMetrics.getDescent();
+    }
+
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.core.FontMetrics#getHeight()
+     */
+    public int getHeight() {
+        return this.swtFontMetrics.getHeight();
+    }
+
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.core.FontMetrics#getLeading()
+     */
+    public int getLeading() {
+        return this.swtFontMetrics.getLeading();
+    }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/SwtGraphics.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/SwtGraphics.java
new file mode 100644
index 0000000..d5e67e5
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/SwtGraphics.java
@@ -0,0 +1,214 @@
+/*******************************************************************************
+ * 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.swt;
+
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.FontData;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.widgets.Display;
+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.FontResource;
+import org.eclipse.wst.xml.vex.core.internal.core.FontSpec;
+import org.eclipse.wst.xml.vex.core.internal.core.Graphics;
+import org.eclipse.wst.xml.vex.core.internal.core.Rectangle;
+
+/**
+ * Implementation of the Vex Graphics interface, mapping it to a 
+ * org.eclipse.swt.graphics.GC object.
+ * 
+ * <p>The GC given to us by SWT is that of the Canvas, which is just a viewport
+ * into the document. This class therefore implements an "origin", which
+ * represents the top-left corner of the document relative to the 
+ * top-left corner of the canvas. The x- and y-coordinates of the origin
+ * are always negative.</p>.
+ */
+public class SwtGraphics implements Graphics {
+
+    private GC gc;
+    private int originX;
+    private int originY;
+    
+    /**
+     * Class constructor.
+     * @param gc SWT GC to which we are drawing.
+     */
+    public SwtGraphics(GC gc) {
+        this.gc = gc;
+    }
+    
+    public void dispose() {
+        this.gc.dispose();
+    }
+    
+    public void drawChars(char[] chars, int offset, int length, int x, int y) {
+        this.drawString(new String(chars, offset, length), x, y);
+
+    }
+
+    public void drawLine(int x1, int y1, int x2, int y2) {
+        this.gc.drawLine(x1 + originX, y1 + originY, x2 + originX, y2 + originY);
+    }
+
+    public void drawOval(int x, int y, int width, int height) {
+        this.gc.drawOval(x + originX, y + originY, width, height);
+    }
+
+    public void drawRect(int x, int y, int width, int height) {
+        this.gc.drawRectangle(x + originX, y + originY, width, height);
+    }
+
+    public void drawString(String s, int x, int y) {
+        this.gc.drawString(s, x + originX, y + originY, true);
+    }
+
+    /**
+     * Fills the given oval with the <em>foreground</em> color. This overrides
+     * the default SWT behaviour to be more like Swing.
+     */
+    public void fillOval(int x, int y, int width, int height) {
+        this.gc.fillOval(x + originX, y + originY, width, height);
+    }
+
+    /**
+     * Fills the given rectangle with the <em>foreground</em> color. This overrides
+     * the default SWT behaviour to be more like Swing.
+     */
+    public void fillRect(int x, int y, int width, int height) {
+        this.gc.fillRectangle(x + originX, y + originY, width, height);
+    }
+
+    public Rectangle getClipBounds() {
+        org.eclipse.swt.graphics.Rectangle r = this.gc.getClipping();
+        return new Rectangle(r.x - this.originX, r.y - this.originY, r.width, r.height);
+    }
+
+    public ColorResource getColor() {
+        return new SwtColor(this.gc.getForeground());
+    }
+
+    public FontResource getFont() {
+        return new SwtFont(this.gc.getFont());
+    }
+
+    public FontMetrics getFontMetrics() {
+        return new SwtFontMetrics(this.gc.getFontMetrics());
+    }
+
+    public int getLineStyle() {
+        return this.lineStyle;
+    }
+    
+    public int getLineWidth() {
+        return this.gc.getLineWidth();
+    }
+    
+    public boolean isAntiAliased() {
+        return false;
+    }
+
+    public void setAntiAliased(boolean antiAliased) {
+    }
+
+    public ColorResource setColor(ColorResource color) {
+        ColorResource oldColor = this.getColor();
+        this.gc.setForeground(((SwtColor) color).getSwtColor());
+        this.gc.setBackground(((SwtColor) color).getSwtColor());
+        return oldColor;
+    }
+
+    public FontResource setFont(FontResource font) {
+        FontResource oldFont = this.getFont();
+        this.gc.setFont(((SwtFont) font).getSwtFont());
+        return oldFont;
+    }
+
+    public void setLineStyle(int lineStyle) {
+        this.lineStyle = lineStyle;
+        switch (lineStyle) {
+        case LINE_DASH:
+            this.gc.setLineStyle(SWT.LINE_DASH);
+            break;
+        case LINE_DOT:
+            this.gc.setLineStyle(SWT.LINE_DOT);
+            break;
+        default:
+            this.gc.setLineStyle(SWT.LINE_SOLID);
+            break;
+        }
+    }
+
+    public void setLineWidth(int lineWidth) {
+        this.gc.setLineWidth(lineWidth);
+    }
+    
+    public int charsWidth(char[] data, int offset, int length) {
+        return this.stringWidth(new String(data, offset, length));
+    }
+
+    public ColorResource createColor(Color rgb) {
+        return new SwtColor( 
+            new org.eclipse.swt.graphics.Color(
+                null, rgb.getRed(), rgb.getGreen(), rgb.getBlue())); 
+    }
+
+    public FontResource createFont(FontSpec fontSpec) {
+        int style = SWT.NORMAL;
+        if ((fontSpec.getStyle() & FontSpec.BOLD) > 0) {
+            style |= SWT.BOLD;
+        }
+        if ((fontSpec.getStyle() & FontSpec.ITALIC) > 0) {
+            style |= SWT.ITALIC;
+        }
+        int size = Math.round(fontSpec.getSize() * 72 / 90); // TODO: fix. SWT uses pts, AWT uses device units
+        String[] names = fontSpec.getNames();
+        FontData[] fd = new FontData[names.length];
+        for (int i = 0; i < names.length; i++) {
+            fd[i] = new FontData(names[i], size, style);
+        }
+        return new SwtFont(new org.eclipse.swt.graphics.Font(null, fd));
+    }
+
+    public ColorResource getSystemColor(int id) {
+        
+        if (id == ColorResource.SELECTION_BACKGROUND) {
+            return new SwtColor(Display.getCurrent().getSystemColor(SWT.COLOR_LIST_SELECTION));
+        } else if (id == ColorResource.SELECTION_FOREGROUND) {
+            return new SwtColor(Display.getCurrent().getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT));
+        } else {
+            return new SwtColor(Display.getCurrent().getSystemColor(-1));
+        }
+    }
+
+    /**
+     * Sets the origin of this graphics object. See the class description
+     * for more details.
+     * 
+     * @param x x-coordinate of the origin, relative to the viewport.
+     * @param y y-coordinate of the origin, relative to the viewport.
+     */
+    public void setOrigin(int x, int y) {
+        this.originX = x;
+        this.originY = y;
+    }
+    
+    public int stringWidth(String s) {
+        return this.gc.stringExtent(s).x;
+    }
+
+    
+    //========================================================== PRIVATE
+    
+    private int lineStyle = LINE_SOLID;
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/Timer.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/Timer.java
new file mode 100644
index 0000000..47d2960
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/Timer.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.swt;
+
+import org.eclipse.swt.widgets.Display;
+
+/**
+ * Periodic timer, built using the Display.timerExec method.
+ */
+public class Timer {
+
+    /**
+     * Class constructor. The timer must be explicitly started using the
+     * start() method.
+     * @param periodMs Milliseconds between each invocation.
+     * @param runnable Runnable to execute when the period expires.
+     */
+    public Timer(int periodMs, Runnable runnable) {
+        this.periodMs = periodMs;
+        this.runnable = runnable;
+    }
+
+    /**
+     * Reset the timer so that it waits another period before firing.
+     */
+    public void reset() {
+        if (this.started) {
+            this.stop();
+            this.start();
+        }
+    }
+
+    /**
+     * Start the timer. 
+     */
+    public void start() {
+        if (!this.started) {
+            this.innerRunnable = new InnerRunnable();
+            Display.getCurrent().timerExec(this.periodMs, this.innerRunnable);
+            this.started = true;
+        }
+    }
+
+    /**
+     * Stop the timer.
+     */
+    public void stop() {
+        if (this.started) {
+            this.innerRunnable.discarded = true;
+            this.innerRunnable = null;
+            this.started = false;
+        }
+    }
+    
+    //==================================================== PRIVATE
+    
+    private Runnable runnable;
+    private int periodMs;
+    private boolean started = false;
+    private InnerRunnable innerRunnable;
+    
+    private class InnerRunnable implements Runnable {
+        public boolean discarded = false;
+        public void run() {
+            if (!discarded) {
+                runnable.run();
+                //Display display = Display.getCurrent();
+                Display.getCurrent().timerExec(periodMs, this);
+            }
+        }
+    }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/VexWidget.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/VexWidget.java
new file mode 100644
index 0000000..b545414
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/swt/VexWidget.java
@@ -0,0 +1,707 @@
+/*******************************************************************************
+ * 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.swt;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.undo.CannotRedoException;
+import javax.swing.undo.CannotUndoException;
+import javax.xml.parsers.ParserConfigurationException;
+
+
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.ISelectionProvider;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.PopupList;
+import org.eclipse.swt.dnd.Clipboard;
+import org.eclipse.swt.dnd.TextTransfer;
+import org.eclipse.swt.dnd.Transfer;
+import org.eclipse.swt.events.ControlEvent;
+import org.eclipse.swt.events.ControlListener;
+import org.eclipse.swt.events.FocusEvent;
+import org.eclipse.swt.events.FocusListener;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.KeyListener;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseListener;
+import org.eclipse.swt.events.MouseMoveListener;
+import org.eclipse.swt.events.PaintEvent;
+import org.eclipse.swt.events.PaintListener;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Canvas;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.ScrollBar;
+import org.eclipse.wst.xml.vex.core.internal.action.AbstractVexAction;
+import org.eclipse.wst.xml.vex.core.internal.action.DuplicateSelectionAction;
+import org.eclipse.wst.xml.vex.core.internal.action.IVexAction;
+import org.eclipse.wst.xml.vex.core.internal.action.NextTableCellAction;
+import org.eclipse.wst.xml.vex.core.internal.action.PreviousTableCellAction;
+import org.eclipse.wst.xml.vex.core.internal.action.RemoveElementAction;
+import org.eclipse.wst.xml.vex.core.internal.action.SplitAction;
+import org.eclipse.wst.xml.vex.core.internal.action.SplitItemAction;
+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.DisplayDevice;
+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.StyleSheet;
+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.DocumentValidationException;
+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.Box;
+import org.eclipse.wst.xml.vex.core.internal.layout.BoxFactory;
+import org.eclipse.wst.xml.vex.core.internal.widget.HostComponent;
+import org.eclipse.wst.xml.vex.core.internal.widget.IBoxFilter;
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+import org.eclipse.wst.xml.vex.core.internal.widget.VexWidgetImpl;
+import org.xml.sax.SAXException;
+
+/**
+ * An implementation of the Vex widget based on SWT. 
+ */
+public class VexWidget 
+    extends Canvas 
+    implements IVexWidget, ISelectionProvider {
+    
+    public VexWidget(Composite parent, int style) {
+        super(parent, style);
+        
+        if (DisplayDevice.getCurrent() == null) {
+            DisplayDevice.setCurrent(new SwtDisplayDevice());
+        }
+
+        this.impl = new VexWidgetImpl(hostComponent);
+        this.setBackground(this.getDisplay().getSystemColor(SWT.COLOR_WHITE));
+        
+        ScrollBar vbar = this.getVerticalBar();
+        if (vbar != null) {
+            vbar.setIncrement(20);
+            vbar.addSelectionListener(selectionListener);
+        }
+        
+        this.addControlListener(this.controlListener);
+        this.addFocusListener(this.focusListener);
+        this.addKeyListener(this.keyListener);
+        this.addMouseListener(this.mouseListener);
+        this.addMouseMoveListener(this.mouseMoveListener);
+        this.addPaintListener(this.painter);
+    }
+
+    public void dispose() {
+        super.dispose();
+        this.impl.setFocus(false); // This stops the caret timer, in case the control
+                                   // is disposed before focus is lost.
+    }
+
+    //----------------------------------------- IInputProvider methods
+    
+    public Object getInput() {
+        return this.impl.getDocument();
+    }
+    
+    //----------------------------------------- ISelectionProvider methods
+    
+    public void addSelectionChangedListener(ISelectionChangedListener listener) {
+        this.selectionListeners.add(listener);
+    }
+    public ISelection getSelection() {
+        return this.selection;
+    }
+    public void removeSelectionChangedListener(ISelectionChangedListener listener) {
+        this.selectionListeners.remove(listener);
+    }
+    public void setSelection(ISelection selection) {
+        throw new RuntimeException("Unexpected call to setSelection");
+    }
+    
+    public void beginWork() { this.impl.beginWork(); }
+
+    public boolean canPaste() {
+        // TODO Auto-generated method stub
+        return false;
+    }
+
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget#canPasteText()
+     */
+    public boolean canPasteText() {
+        // TODO Auto-generated method stub
+        return false;
+    }
+
+    public boolean canRedo() { return this.impl.canRedo(); }
+    public boolean canUndo() { return this.impl.canUndo(); }
+    public boolean canUnwrap() { return this.impl.canUnwrap(); }
+
+    
+    public Point computeSize(int wHint, int hHint, boolean changed) {
+        org.eclipse.swt.graphics.Rectangle r = this.getClientArea();
+        int height = r.height;
+        
+        ScrollBar vbar = this.getVerticalBar();
+        if (vbar != null) {
+            height = vbar.getMaximum(); 
+        }
+        return new Point(r.width, height);
+    }
+
+    public void copySelection() {
+        Clipboard clipboard = new Clipboard(this.getDisplay());
+        Object[] data = {
+            this.getSelectedFragment(),
+            this.getSelectedText()
+        };
+        Transfer[] transfers = {
+            DocumentFragmentTransfer.getInstance(),
+            TextTransfer.getInstance()
+        };
+        clipboard.setContents(data, transfers);
+    }
+
+    public void cutSelection() { 
+        this.copySelection();
+        this.deleteSelection();
+    }
+    
+    public void deleteNextChar() throws DocumentValidationException { this.impl.deleteNextChar(); }
+    public void deletePreviousChar() throws DocumentValidationException { this.impl.deletePreviousChar(); }
+    public void deleteSelection() { this.impl.deleteSelection(); }
+    public void doWork(Runnable runnable) { this.impl.doWork(runnable); }
+    public void doWork(boolean savePosition, Runnable runnable) { this.impl.doWork(savePosition, runnable); }
+    public void endWork(boolean success) { this.impl.endWork(success); }
+    public Box findInnermostBox(IBoxFilter filter) { return this.impl.findInnermostBox(filter); }
+    public BoxFactory getBoxFactory() { return this.impl.getBoxFactory(); }
+    public int getCaretOffset() { return this.impl.getCaretOffset(); }
+    public Element getCurrentElement() { return this.impl.getCurrentElement(); }
+    public Document getDocument() { return this.impl.getDocument(); }
+    public int getLayoutWidth() { return this.impl.getLayoutWidth(); }
+    public int getSelectionEnd() { return this.impl.getSelectionEnd(); }
+    public int getSelectionStart() { return this.impl.getSelectionStart(); }
+    public DocumentFragment getSelectedFragment() { return this.impl.getSelectedFragment(); }
+    public String getSelectedText() { return this.impl.getSelectedText(); }
+    public StyleSheet getStyleSheet() { return this.impl.getStyleSheet(); }
+    public int getUndoDepth() { return this.impl.getUndoDepth(); }
+    public String[] getValidInsertElements() { return this.impl.getValidInsertElements(); }
+    public String[] getValidMorphElements() { return this.impl.getValidMorphElements(); }
+    public boolean hasSelection() { return this.impl.hasSelection(); }
+    public void insertChar(char c) throws DocumentValidationException { this.impl.insertChar(c); }
+    public void insertFragment(DocumentFragment frag) throws DocumentValidationException { this.impl.insertFragment(frag); }
+    public void insertElement(Element element) throws DocumentValidationException { this.impl.insertElement(element); }
+    public void insertText(String text) throws DocumentValidationException { this.impl.insertText(text); }
+    public boolean isDebugging() { return impl.isDebugging(); }
+    public void morph(Element element) throws DocumentValidationException { this.impl.morph(element); }
+    public void moveBy(int distance) { this.impl.moveBy(distance); }
+    public void moveBy(int distance, boolean select) { this.impl.moveBy(distance, select); }
+    public void moveTo(int offset) { this.impl.moveTo(offset); }
+    public void moveTo(int offset, boolean select) { this.impl.moveTo(offset, select); }
+    public void moveToLineEnd(boolean select) { this.impl.moveToLineEnd(select); }
+    public void moveToLineStart(boolean select) { this.impl.moveToLineStart(select); }
+    public void moveToNextLine(boolean select) { this.impl.moveToNextLine(select); }
+    public void moveToNextPage(boolean select) { this.impl.moveToNextPage(select); }
+    public void moveToNextWord(boolean select) { this.impl.moveToNextWord(select); }
+    public void moveToPreviousLine(boolean select) { this.impl.moveToPreviousLine(select); }
+    public void moveToPreviousPage(boolean select) { this.impl.moveToPreviousPage(select); }
+    public void moveToPreviousWord(boolean select) { this.impl.moveToPreviousWord(select); }
+
+    
+    public IAction[] getValidInsertActions() {
+        String[] names = this.getValidInsertElements();
+        IAction[] actions = new IAction[names.length];
+        for (int i = 0; i < names.length; i++) {
+            actions[i] = new InsertElementAction(names[i]);
+        }
+        return actions;
+    }
+    
+    public IAction[] getValidMorphActions() {
+        String[] names = this.getValidMorphElements();
+        IAction[] actions = new IAction[names.length];
+        for (int i = 0; i < names.length; i++) {
+            actions[i] = new MorphElementAction(names[i]);
+        }
+        return actions;
+    }
+    
+
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget#paste()
+     */
+    public void paste() throws DocumentValidationException {
+        Clipboard clipboard = new Clipboard(this.getDisplay());
+        DocumentFragment frag = (DocumentFragment) 
+            clipboard.getContents(DocumentFragmentTransfer.getInstance());
+        if (frag != null) {
+            this.insertFragment(frag);
+        } else {
+            this.pasteText();
+        }
+    }
+
+    /**
+     * @see org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget#pasteText()
+     */
+    public void pasteText() throws DocumentValidationException {
+        Clipboard clipboard = new Clipboard(this.getDisplay());
+        String text = (String) clipboard.getContents(TextTransfer.getInstance());
+        if (text != null) {
+            this.insertText(text);
+        }
+    }
+
+    public void redo() throws CannotRedoException { this.impl.redo(); }
+    public void removeAttribute(String attributeName) { this.impl.removeAttribute(attributeName); }
+    public void savePosition(Runnable runnable) { this.impl.savePosition(runnable); }
+    public void selectAll() { this.impl.selectAll(); }
+    public void selectWord() { this.impl.selectWord(); }
+    public void setAttribute(String attributeName, String value) { this.impl.setAttribute(attributeName, value); }
+    public void setBoxFactory(BoxFactory boxFactory) { this.impl.setBoxFactory(boxFactory); }
+    public void setDebugging(boolean debugging) { impl.setDebugging(debugging); }
+    public void setDocument(Document doc, StyleSheet styleSheet) { this.impl.setDocument(doc, styleSheet); }
+    public void setDocument(URL docUrl, URL ssURL) throws IOException, ParserConfigurationException, SAXException {
+        this.impl.setDocument(docUrl, ssURL);
+    }
+    public void setLayoutWidth(int width) { this.impl.setLayoutWidth(width); }
+    public void setStyleSheet(StyleSheet styleSheet) { this.impl.setStyleSheet(styleSheet); }
+    public void setStyleSheet(URL ssUrl) throws IOException { this.impl.setStyleSheet(ssUrl); }
+
+    /**
+     * Show a popup list of elements that are valid to be inserted at the 
+     * current position. If one of the elements is selected, it is inserted
+     * before returning.
+     */
+    public void showInsertElementPopup() {
+        PopupList list = new PopupList(this.getShell());
+        list.setItems(this.getValidInsertElements());
+        
+        Rectangle caret = this.impl.getCaret().getBounds();
+        Point display = this.toDisplay(caret.getX() + 10, caret.getY());
+        String selected = list.open(new org.eclipse.swt.graphics.Rectangle(display.x, display.y, 200, 0));
+        if (selected != null) {
+            try {
+                this.insertElement(new Element(selected));
+            } catch (DocumentValidationException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+    
+    /**
+     * Show a popup list of elements to which it is valid to morph the current
+     * element. If one of the elements is selected, the current element is 
+     * morphed before returning.
+     */
+    public void showMorphElementPopup() {
+        PopupList list = new PopupList(this.getShell());
+        list.setItems(this.getValidMorphElements());
+        
+        Rectangle caret = this.impl.getCaret().getBounds();
+        Point display = this.toDisplay(caret.getX() + 10, caret.getY());
+        String selected = list.open(new org.eclipse.swt.graphics.Rectangle(display.x, display.y, 200, 0));
+        if (selected != null) {
+            try {
+                this.morph(new Element(selected));
+            } catch (DocumentValidationException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+    
+    public void split() throws DocumentValidationException { this.impl.split(); }
+    public void undo() throws CannotUndoException { this.impl.undo(); }
+    public int viewToModel(int x, int y) { return this.impl.viewToModel(x, y); }
+
+    //====================================================== PRIVATE
+    
+    //------------------------------------------------------ Fields
+    
+    private static final char CHAR_NONE = 0;
+    private static Map keyMap;
+
+    private VexWidgetImpl impl;
+    
+    // Fields controlling scrolling
+    int originX = 0;
+    int originY = 0;
+    
+    private List selectionListeners = new ArrayList();
+    private ISelection selection;
+
+    //------------------------------------------------------ Inner Classes
+    
+    private Runnable caretTimerRunnable = new Runnable() {
+        public void run() {
+            impl.toggleCaret();
+        }
+    };
+    private Timer caretTimer = new Timer(500, this.caretTimerRunnable);
+    
+    private ControlListener controlListener = new ControlListener() {
+        public void controlMoved(ControlEvent e) {
+        }
+        public void controlResized(ControlEvent e) {
+            org.eclipse.swt.graphics.Rectangle r = getClientArea();
+            // There seems to be a bug in SWT (at least on Linux/GTK+)
+            // When maximizing the editor, the width is first set to 1,
+            // then to the correct width
+            if (r.width == 1) {
+                return;
+            }
+            impl.setLayoutWidth(r.width);
+            
+            ScrollBar vbar = getVerticalBar();
+            if (vbar != null) {
+                vbar.setThumb(r.height);
+                vbar.setPageIncrement(Math.round(r.height * 0.9f));
+            }
+        }
+    };
+    
+    private FocusListener focusListener = new FocusListener() {
+        public void focusGained(FocusEvent e) {
+            impl.setFocus(true);
+            caretTimer.start();
+        }
+        public void focusLost(FocusEvent e) {
+            impl.setFocus(false);
+            caretTimer.stop();
+        }
+    };
+    
+    private HostComponent hostComponent = new HostComponent() {
+
+        public Graphics createDefaultGraphics() {
+            if (VexWidget.this.isDisposed()) {
+                System.out.println("*** Woot! VexWidget is disposed!");
+            }
+            return new SwtGraphics(new GC(VexWidget.this));
+        }
+
+        public void fireSelectionChanged() {
+
+            if (hasSelection()) {
+                Node[] nodes = getDocument().getNodes(getSelectionStart(), getSelectionEnd());
+                selection = new StructuredSelection(nodes);
+            } else {
+                selection = new StructuredSelection(getCurrentElement());
+            }
+            
+            SelectionChangedEvent e = new SelectionChangedEvent(VexWidget.this, selection);
+            for (int i = 0; i < selectionListeners.size(); i++) {
+                ISelectionChangedListener listener = (ISelectionChangedListener) 
+                    selectionListeners.get(i);
+                listener.selectionChanged(e);
+            }
+            caretTimer.reset();
+        }
+
+        public Rectangle getViewport() {
+            return new Rectangle(
+                    getClientArea().x - originX,
+                    getClientArea().y - originY,
+                    getClientArea().width,
+                    getClientArea().height);
+        }
+        
+        public void invokeLater(Runnable runnable) {
+            VexWidget.this.getDisplay().asyncExec(runnable);
+        }
+
+        public void repaint() {
+            if (!VexWidget.this.isDisposed()) {
+                // We can sometimes get a repaint from the VexWidgetImpl's
+                // caret timer thread after the Widget is disposed.
+                VexWidget.this.redraw();
+            }
+        }
+
+        public void repaint(int x, int y, int width, int height) {
+            VexWidget.this.redraw(x + originX, y + originY, width, height, true);
+        }
+
+        public void scrollTo(int left, int top) {
+            ScrollBar vbar = getVerticalBar();
+            if (vbar != null) {
+                vbar.setSelection(top);
+            }
+            setOrigin(-left, -top);
+        }
+        
+        public void setPreferredSize(int width, int height) {
+            ScrollBar vbar = getVerticalBar();
+            if (vbar != null) {
+                vbar.setMaximum(height);
+            }
+        }
+
+    };
+    
+    private static abstract class Action extends AbstractVexAction {
+        
+        public void run(IVexWidget vexWidget) {
+            try {
+                this.runEx(vexWidget);
+            } catch (Exception ex) {
+                ex.printStackTrace();
+            }
+        }
+
+        public abstract void runEx(IVexWidget w) throws Exception;
+    }
+    
+    private KeyListener keyListener = new KeyListener() {
+
+        public void keyPressed(KeyEvent e) {
+            //System.out.println("Key pressed, keyCode is " + e.keyCode + ", keyChar is " + ((int) e.character) + ", stateMask is " + e.stateMask);
+            KeyStroke keyStroke = new KeyStroke(e);
+            Map map = getKeyMap();
+            if (map.containsKey(keyStroke)) {
+                Object action = map.get(keyStroke);
+                if (action instanceof IVexAction) {
+                    ((IVexAction) action).run(VexWidget.this);
+                } else {
+                    try {
+                        ((Action) map.get(keyStroke)).runEx(VexWidget.this);
+                    } catch (Exception ex) {
+                        ex.printStackTrace();
+                    }
+                }
+            } else if (!Character.isISOControl(e.character)) {
+                try {
+                    insertChar(e.character);
+                } catch (DocumentValidationException e1) {
+                    // TODO Auto-generated catch block
+                    e1.printStackTrace();
+                }                
+            }
+        }
+
+        public void keyReleased(KeyEvent e) {
+        }
+    };
+    
+    private MouseListener mouseListener = new MouseListener() {
+        public void mouseDoubleClick(MouseEvent e) {
+            if (e.button == 1) {
+                selectWord();
+            }
+        }
+        public void mouseDown(MouseEvent e) {
+            if (e.button == 1) {
+                int offset = viewToModel(e.x - originX, e.y - originY);
+                boolean select = (e.stateMask == SWT.SHIFT);
+                moveTo(offset, select);
+            }
+        }
+        public void mouseUp(MouseEvent e) {
+        }    
+    };
+    
+    private MouseMoveListener mouseMoveListener = new MouseMoveListener() {
+        public void mouseMove(MouseEvent e) {
+            if ((e.stateMask & SWT.BUTTON1) > 0) {
+                int offset = viewToModel(e.x - originX, e.y - originY);
+                moveTo(offset, true);
+            }
+        }
+    };
+    
+    private PaintListener painter = new PaintListener() {
+        public void paintControl(PaintEvent e) {
+            
+            SwtGraphics g = new SwtGraphics(e.gc);
+            g.setOrigin(originX, originY);
+            
+            Color bgColor = impl.getBackgroundColor();
+            if (bgColor == null) {
+                bgColor = new Color(255, 255, 255);
+            }
+
+            ColorResource color = g.createColor(bgColor);
+            ColorResource oldColor = g.setColor(color);
+            Rectangle r = g.getClipBounds();
+            g.fillRect(r.getX(), r.getY(), r.getWidth(), r.getHeight());
+            g.setColor(oldColor);
+            color.dispose();
+            
+            impl.paint(g, 0, 0);
+        }
+    };
+
+    private SelectionListener selectionListener = new SelectionListener() {
+        public void widgetSelected(SelectionEvent e) {
+            ScrollBar vbar = getVerticalBar();
+            if (vbar != null) {
+                int y = - vbar.getSelection();
+                setOrigin(0, y);
+            }
+        }
+        public void widgetDefaultSelected(SelectionEvent e) {
+        }
+    };
+    
+    private class InsertElementAction extends org.eclipse.jface.action.Action {
+        public InsertElementAction(String name) {
+            this.name = name;
+            this.setText(name);
+        }
+        public void run() {
+            try {
+                insertElement(new Element(name));
+            } catch (DocumentValidationException e) {
+            }
+        }
+        private String name;
+    }
+
+    private class MorphElementAction extends org.eclipse.jface.action.Action {
+        public MorphElementAction(String elementName) {
+            this.elementName = elementName;
+            this.setText(elementName);
+        }
+        public void run() {
+            try {
+                morph(new Element(elementName));
+            } catch (DocumentValidationException e) {
+                // TODO Auto-generated catch block
+                e.printStackTrace();
+            }
+        }
+        private String elementName;
+    }
+
+
+    //------------------------------------------------------ Methods
+
+    private static void addKey(char character, int keyCode, int stateMask, Action action) {
+        keyMap.put(new KeyStroke(character, keyCode, stateMask), action);
+    }
+    
+    private static void addKey(char character, int keyCode, int stateMask, IVexAction action) {
+        keyMap.put(new KeyStroke(character, keyCode, stateMask), action);
+    }
+    
+
+    private static void buildKeyMap() {
+        addKey(CHAR_NONE, SWT.ARROW_DOWN, SWT.NONE, new Action() {
+            public void runEx(IVexWidget w) { w.moveToNextLine(false); } });
+        addKey(CHAR_NONE, SWT.ARROW_DOWN, SWT.SHIFT, new Action() {
+            public void runEx(IVexWidget w) { w.moveToNextLine(true); } });
+            
+        addKey(CHAR_NONE, SWT.ARROW_LEFT, SWT.NONE, new Action() {
+            public void runEx(IVexWidget w) { w.moveBy(-1); } });
+        addKey(CHAR_NONE, SWT.ARROW_LEFT, SWT.SHIFT, new Action() {
+            public void runEx(IVexWidget w) { w.moveBy(-1, true); } });
+        addKey(CHAR_NONE, SWT.ARROW_LEFT, SWT.CONTROL, new Action() {
+            public void runEx(IVexWidget w) { w.moveToPreviousWord(false); } });
+        addKey(CHAR_NONE, SWT.ARROW_LEFT, SWT.SHIFT | SWT.CONTROL, new Action() {
+            public void runEx(IVexWidget w) { w.moveToPreviousWord(true); } });
+            
+        addKey(CHAR_NONE, SWT.ARROW_RIGHT, SWT.NONE, new Action() {
+            public void runEx(IVexWidget w) { w.moveBy(+1); } });
+        addKey(CHAR_NONE, SWT.ARROW_RIGHT, SWT.SHIFT, new Action() {
+            public void runEx(IVexWidget w) { w.moveBy(+1, true); } });
+        addKey(CHAR_NONE, SWT.ARROW_RIGHT, SWT.CONTROL, new Action() {
+            public void runEx(IVexWidget w) { w.moveToNextWord(false); } });
+        addKey(CHAR_NONE, SWT.ARROW_RIGHT, SWT.SHIFT | SWT.CONTROL, new Action() {
+            public void runEx(IVexWidget w) { w.moveToNextWord(true); } });
+
+        addKey(CHAR_NONE, SWT.ARROW_UP, SWT.NONE, new Action() {
+            public void runEx(IVexWidget w) { w.moveToPreviousLine(false); } });
+        addKey(CHAR_NONE, SWT.ARROW_UP, SWT.SHIFT, new Action() {
+            public void runEx(IVexWidget w) { w.moveToPreviousLine(true); } });
+
+        addKey(SWT.BS, SWT.BS, SWT.NONE, new Action() {
+            public void runEx(IVexWidget w) throws Exception { w.deletePreviousChar(); } });
+        addKey(SWT.DEL, SWT.DEL, SWT.NONE, new Action() {
+            public void runEx(IVexWidget w) throws Exception { w.deleteNextChar(); } });
+
+        addKey(SWT.TAB, SWT.TAB, SWT.NONE, new NextTableCellAction());
+        addKey(SWT.TAB, SWT.TAB, SWT.SHIFT, new PreviousTableCellAction());
+        
+        addKey(CHAR_NONE, SWT.END, SWT.NONE, new Action() {
+            public void runEx(IVexWidget w) { w.moveToLineEnd(false); } });
+        addKey(CHAR_NONE, SWT.END, SWT.SHIFT, new Action() {
+            public void runEx(IVexWidget w) { w.moveToLineEnd(true); } });
+        addKey(CHAR_NONE, SWT.END, SWT.CONTROL, new Action() {
+            public void runEx(IVexWidget w) { w.moveTo(w.getDocument().getLength() - 1); } });
+        addKey(CHAR_NONE, SWT.END, SWT.SHIFT | SWT.CONTROL, new Action() {
+            public void runEx(IVexWidget w) { w.moveTo(w.getDocument().getLength() - 1, true); } });
+
+        addKey(CHAR_NONE, SWT.HOME, SWT.NONE, new Action() {
+            public void runEx(IVexWidget w) { w.moveToLineStart(false); } });
+        addKey(CHAR_NONE, SWT.HOME, SWT.SHIFT, new Action() {
+            public void runEx(IVexWidget w) { w.moveToLineStart(true); } });
+        addKey(CHAR_NONE, SWT.HOME, SWT.CONTROL, new Action() {
+            public void runEx(IVexWidget w) { w.moveTo(1); } });
+        addKey(CHAR_NONE, SWT.HOME, SWT.SHIFT | SWT.CONTROL, new Action() {
+            public void runEx(IVexWidget w) { w.moveTo(1, true); } });
+
+        addKey(CHAR_NONE, SWT.PAGE_DOWN, SWT.NONE, new Action() {
+            public void runEx(IVexWidget w) { w.moveToNextPage(false); } });
+        addKey(CHAR_NONE, SWT.PAGE_DOWN, SWT.SHIFT, new Action() {
+            public void runEx(IVexWidget w) { w.moveToNextPage(true); } });
+            
+        addKey(CHAR_NONE, SWT.PAGE_UP, SWT.NONE, new Action() {
+            public void runEx(IVexWidget w) { w.moveToPreviousPage(false); } });
+        addKey(CHAR_NONE, SWT.PAGE_UP, SWT.SHIFT, new Action() {
+            public void runEx(IVexWidget w) { w.moveToPreviousPage(true); } });
+            
+        addKey(' ', 0, SWT.CONTROL, new Action() { // Ctrl-Space
+            public void runEx(IVexWidget w) { ((VexWidget)w).showInsertElementPopup(); } });
+        addKey('\r', 0, SWT.CONTROL, new Action() { // Ctrl-M
+            public void runEx(IVexWidget w) { ((VexWidget)w).showMorphElementPopup(); } });
+        addKey((char) 23, 0, SWT.CONTROL, new RemoveElementAction());
+//        addKey('\r', '\r', SWT.NONE, new Action() { // Enter key
+//            public void runEx(IVexWidget w) throws Exception { w.split(); } });
+        addKey('\r', '\r', SWT.NONE, new SplitAction());
+        addKey('\r', '\r', SWT.SHIFT, new SplitItemAction());
+
+        addKey((char) 4, 100, SWT.CONTROL, new DuplicateSelectionAction()); // Ctrl-D
+    }
+
+    private static Map getKeyMap() {
+        if (keyMap == null) {
+            keyMap = new HashMap();
+            buildKeyMap();    
+        }
+        return keyMap;
+    }
+    
+    /**
+     * Scrolls to the given position in the widget. 
+     * @param x x-coordinate of the position to which to scroll
+     * @param y y-coordinate of the position to which to scroll
+     */
+    private void setOrigin(int x, int y) {
+        int destX = x - originX;
+        int destY = y - originY;
+        org.eclipse.swt.graphics.Rectangle ca = getClientArea();
+        scroll(destX, destY, 0, 0, ca.width, ca.height, false);
+        originX = x;
+        originY = y;
+    }
+    
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/undo/CannotRedoException.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/undo/CannotRedoException.java
new file mode 100644
index 0000000..8a55a04
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/undo/CannotRedoException.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.undo;
+
+/**
+ * Thrown when an IUndoableEdit cannot be undone.
+ */
+public class CannotRedoException extends RuntimeException {
+
+    /**
+     * Class constructor. 
+     */
+    public CannotRedoException() {
+    }
+
+    /**
+     * Class constructor.
+     * @param message Message indicating the reason for the failure.
+     */
+    public CannotRedoException(String message) {
+        super(message);
+    }
+
+    /**
+     * Class constructor.
+     * @param cause Root cause of the failure.
+     */
+    public CannotRedoException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Class constructor.
+     * @param message Message indicating the reason for the failure.
+     * @param cause Root cause of the failure.
+     */
+    public CannotRedoException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/undo/CannotUndoException.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/undo/CannotUndoException.java
new file mode 100644
index 0000000..2c5cbea
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/undo/CannotUndoException.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.undo;
+
+/**
+ * Thrown when an IUndoableEdit cannot be undone.
+ */
+public class CannotUndoException extends RuntimeException {
+
+    /**
+     * Class constructor. 
+     */
+    public CannotUndoException() {
+    }
+
+    /**
+     * Class constructor.
+     * @param message Message indicating the reason for the failure.
+     */
+    public CannotUndoException(String message) {
+        super(message);
+    }
+
+    /**
+     * Class constructor.
+     * @param cause Root cause of the failure.
+     */
+    public CannotUndoException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Class constructor.
+     * @param message Message indicating the reason for the failure.
+     * @param cause Root cause of the failure.
+     */
+    public CannotUndoException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/undo/CompoundEdit.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/undo/CompoundEdit.java
new file mode 100644
index 0000000..7e7197b
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/undo/CompoundEdit.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.undo;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * An undoable edit that is a composite of others.
+ */
+public class CompoundEdit implements IUndoableEdit {
+
+    /**
+     * Class constructor.
+     */
+    public CompoundEdit() {
+    }
+
+    /**
+     * Adds an edit to the list.
+     * @param edit Edit to be undone/redone as part of the compound group.
+     */
+    public void addEdit(IUndoableEdit edit) {
+        edits.add(edit);
+    }
+    
+    public boolean combine(IUndoableEdit edit) {
+        return false;
+    }
+
+    /**
+     * Calls redo() on each contained edit, in the order that they were added.
+     */
+    public void redo() {
+        for (int i = 0; i < this.edits.size(); i++) {
+            IUndoableEdit edit = (IUndoableEdit) this.edits.get(i);
+            edit.redo();
+        }
+    }
+
+    /**
+     * Calls undo() on each contained edit, in reverse order from which they
+     * were added.
+     */
+    public void undo() {
+        for (int i = this.edits.size() - 1; i >= 0; i--) {
+            IUndoableEdit edit = (IUndoableEdit) this.edits.get(i);
+            edit.undo();
+        }
+    }
+
+    //===================================================== PRIVATE
+    
+    private List edits = new ArrayList();
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/undo/IUndoableEdit.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/undo/IUndoableEdit.java
new file mode 100644
index 0000000..4edb719
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/undo/IUndoableEdit.java
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ * 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.undo;
+
+/**
+ * Represents a change to a document (an edit) that can be undone and redone.
+ * Typically, the edit source (i.e. the document) will have a flag that is set 
+ * by the edit to indicate that the edits being performed are part of an undo
+ * or redo. The document can use this to supress events to any 
+ * IUndoableEventListeners during undo/redo. 
+ */
+public interface IUndoableEdit {
+
+    /**
+     * Try to combine the given edit event with this one. The common use-case
+     * involves a user typing sequential characters into the document: all 
+     * such insertions should be undone in one go.
+     * 
+     * @param edit IUndoableEdit to be combined with this one.
+     * @return True if the given edit was successfully combined into this one.
+     */
+    public boolean combine(IUndoableEdit edit);
+    
+    /**
+     * Redo the edit.
+     */
+    public void redo() throws CannotRedoException;
+
+    /**
+     * Undo the edit.
+     */
+    public void undo() throws CannotUndoException;
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/widget/CssWhitespacePolicy.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/widget/CssWhitespacePolicy.java
new file mode 100644
index 0000000..56ad19c
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/widget/CssWhitespacePolicy.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.widget;
+
+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.dom.IWhitespacePolicy;
+
+
+
+/**
+ * Implementation of WhitespacePolicy using a CSS stylesheet.
+ */
+public class CssWhitespacePolicy implements IWhitespacePolicy {
+
+    /**
+     * Class constructor.
+     * @param styleSheet The stylesheet used for the policy.
+     */
+    public CssWhitespacePolicy(StyleSheet styleSheet) {
+        this.styleSheet = styleSheet;
+    }
+    
+    public boolean isBlock(Element element) {
+        return this.styleSheet.getStyles(element).isBlock();
+    }
+
+    public boolean isPre(Element element) {
+        return CSS.PRE.equals(this.styleSheet.getStyles(element).getWhiteSpace());
+    }
+    
+    //===================================================== PRIVATE
+    
+    private StyleSheet styleSheet;
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/widget/HostComponent.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/widget/HostComponent.java
new file mode 100644
index 0000000..bf85907
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/widget/HostComponent.java
@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * 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.widget;
+
+import org.eclipse.wst.xml.vex.core.internal.core.Graphics;
+import org.eclipse.wst.xml.vex.core.internal.core.Rectangle;
+
+/**
+ * Callback interface through which VexComponentImpl accesses its host
+ * component.
+ */
+public interface HostComponent {
+
+    /**
+     * Creates a Graphics object for the default system display. The returned
+     * object must be disposed after use.
+     */
+    public Graphics createDefaultGraphics();
+    
+    /**
+     * If the component is scrollable, return the height of the viewport;
+     * otherwise, return the size of the widget. 
+     */
+    public Rectangle getViewport();
+    
+    /**
+     * Called when the selection in the widget has changed. This method
+     * should reset the caret timer to the full interval.
+     */
+    public void fireSelectionChanged();
+    
+    public void invokeLater(Runnable runnable);
+    
+    /**
+     * Flag the entire component for a repaint.
+     */
+    public void repaint();
+
+    
+    /**
+     * Flag a rectangular area of the component to be repainted.
+     * @param x X-coordinate of the region to be repainted.
+     * @param y Y-coordinate of the region to be repainted.
+     * @param width Width of the region to be repainted.
+     * @param height Height of the region to be repainted.
+     */
+    public void repaint(int x, int y, int width, int height);
+    
+    /**
+     * Move the viewport to a new location
+     * @param left New left-side of the viewport
+     * @param top New top-side of the viewport
+     */
+    public void scrollTo(int left, int top);
+    
+    /**
+     * Sets the preferred size of the component.
+     * @param width Preferred width of the component.
+     * @param height Preferred height of the component.
+     */
+    public void setPreferredSize(int width, int height);
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/widget/IBoxFilter.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/widget/IBoxFilter.java
new file mode 100644
index 0000000..07fb04b
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/widget/IBoxFilter.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * 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.widget;
+
+import org.eclipse.wst.xml.vex.core.internal.layout.Box;
+
+/**
+ * Interface implemented by classes that determine whether a Box
+ * matches certain criteria. 
+ * 
+ * @see IVexWidget#
+ */
+public interface IBoxFilter {
+
+    /**
+     * Returns <code>true</code> if the given box matches the criteria.
+     * 
+     * @param box Box to be tested.
+     */
+    public boolean matches(Box box);
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/widget/IVexWidget.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/widget/IVexWidget.java
new file mode 100644
index 0000000..4cb436a
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/widget/IVexWidget.java
@@ -0,0 +1,513 @@
+/*******************************************************************************
+ * 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.widget;
+
+import java.io.IOException;
+import java.net.URL;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+
+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.DocumentFragment;
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentValidationException;
+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.BoxFactory;
+import org.eclipse.wst.xml.vex.core.internal.undo.CannotRedoException;
+import org.eclipse.wst.xml.vex.core.internal.undo.CannotUndoException;
+import org.xml.sax.SAXException;
+
+/**
+ * Methods implemented by implementations of the Vex widget on all platforms.
+ * This interface is more important as a place to gather common Javadoc than
+ * as a way to enforce a contract. 
+ */
+public interface IVexWidget {
+    /**
+     * Signals the start of a set of operations that should be considered
+     * a single unit for undo/redo purposes.
+     * 
+     * <p><b>It is <i>strongly</i> recommended to use the {@link #doWork(IRunnable)} method 
+     * instead of manually implementing beginWork/endWork.</b></p>
+     * 
+     * <p>Each call to beginWork should
+     * be matched with a call to {@link #endWork(boolean)}. The following pattern can be used
+     * to enforce this rules even in the face of exceptions.</p>
+     * 
+     * <pre>
+     * VexComponent c = ...;
+     * boolean success = false;
+     * try {
+     *     c.beginWork();
+     *     // do multiple inserts/deletes
+     *     success = true;
+     * } finally {
+     *     c.endWork(success);
+     * }
+     * </pre>
+     * 
+     * <p>In the case of nested beginWork/endWork calls, only the outermost
+     * results in an undoable event.</p>
+     * 
+     * @see endWork(boolean)
+     */
+    public void beginWork();
+
+    /**
+     * Returns true if the clipboard has content that can be
+     * pasted. Used to enable/disable the paste action of a containing
+     * application.
+     */
+    public boolean canPaste();
+    
+    /**
+     * Returns true if the clipboard has plain text content that can be
+     * pasted. Used to enable/disable the "paste text" action of a containing
+     * application.
+     */
+    public boolean canPasteText();
+    
+    /**
+     * Returns true if a redo can be performed.
+     */
+    public boolean canRedo();
+
+    /**
+     * Returns true if an undo can be performed.
+     */
+    public boolean canUndo();
+
+    /**
+     * Returns true if the current element can be unwrapped, i.e. replaced with
+     * its content.
+     */
+    public boolean canUnwrap();
+    
+    /**
+     * Copy the current selection to the clipboard.
+     */
+    public void copySelection();
+
+    /**
+     * Cuts the current selection to the clipboard.
+     */
+    public void cutSelection();
+
+    /**
+     * Deletes the character to the right of the caret.
+     */
+    public void deleteNextChar() throws DocumentValidationException;
+
+    /**
+     * Deletes the character to the left of the caret.
+     */
+    public void deletePreviousChar() throws DocumentValidationException;
+
+    /**
+     * Delete the current selection. Does nothing if there is no
+     * current selection.
+     */
+    public void deleteSelection();
+
+    /**
+     * Perform the runnable's run method within a beginWork/endWork pair.
+     * All operations in the runnable are treated as a single unit of 
+     * work, and can be undone in one operation by the user. Also, if
+     * a later operation fails, all earlier operations are also undone.
+     *  
+     * @param runnable Runnable implementing the work to be done.
+     */
+    public void doWork(Runnable runnable);
+    
+    /**
+     * Perform the runnable's run method within a beginWork/endWork pair.
+     * All operations in the runnable are treated as a single unit of 
+     * work, and can be undone in one operation by the user. Also, if
+     * a later operation fails, all earlier operations are also undone.
+     *  
+     * @param savePosition If true, the current caret position is saved
+     * and restored once the operation is complete.
+     * @param runnable Runnable implementing the work to be done.
+     */
+    public void doWork(boolean savePosition, Runnable runnable);
+    
+    /**
+     * Signals the end of a set of operations that should be treated as
+     * a single unit for undo/redo purposes.
+     * 
+     * @param success If true, an edit is added to the undo stack.
+     * If false, all the changes since the matching beginWork call
+     * are undone.
+     * 
+     * @see #beginWork()
+     */
+    public void endWork(boolean success);
+
+    /**
+     * Returns the innermost box containing the current caret offset
+     * that matches the given filter. 
+     *
+     * @param filter IBoxFilter that determines which box to return
+     */
+    public Box findInnermostBox(IBoxFilter filter);
+    
+    /**
+     * Returns the <code>BoxFactory</code> used for generating boxes in the
+     * layout.
+     */
+    public BoxFactory getBoxFactory();
+
+    /**
+     * Return the offset into the document represented by the caret.
+     */
+    public int getCaretOffset();
+
+    /**
+     * Returns the element at the current caret offset.
+     */
+    public Element getCurrentElement();
+
+    /**
+     * Returns the document associated with this component.
+     */
+    public Document getDocument();
+
+    /**
+     * Returns the width to which the document was layed out.
+     */
+    public int getLayoutWidth();
+    
+    /**
+     * Returns the offset at which the selection ends.
+     */
+    public int getSelectionEnd();
+
+    /**
+     * Returns the offset at which the selection starts.
+     */
+    public int getSelectionStart();
+
+    /**
+     * Returns the currently selected document fragment, or null if
+     * there is no current selection.
+     */
+    public DocumentFragment getSelectedFragment();
+
+    /**
+     * Returns the currently selected string, or an empty string if
+     * there is no current selection.
+     */
+    public String getSelectedText();
+
+
+    /**
+     * Returns the style sheet used to format the document while editing.
+     */
+    public StyleSheet getStyleSheet();
+    
+    /**
+     * Returns the number of undoable edits that have occurred on this document
+     * since editing has started, not including limitations due to maximum
+     * undo depth.
+     */
+    public int getUndoDepth();
+
+    /**
+     * Returns an array of names of elements that are valid to insert at the
+     * given caret offset and selection
+     */
+    public String[] getValidInsertElements();
+
+    /**
+     * Returns an array of names of elements to which the element at the 
+     * current caret location can be morphed.
+     */
+    public String[] getValidMorphElements();
+
+    /**
+     * Returns true if the user currently has some text selected.
+     */
+    public boolean hasSelection();
+
+    /**
+     * Inserts the given character at the current caret position. Any selected
+     * content is deleted. The main difference between this method and
+     * insertText is that this method does not use beginWork/endWork, so
+     * consecutive calls to insertChar are collapsed into a single
+     * IUndoableEdit. This method should normally only be called in response
+     * to a user typing a key.
+     *  
+     * @param c Character to insert.
+     */
+    public void insertChar(char c) throws DocumentValidationException;
+    
+    /**
+     * Inserts the given document fragment at the current caret position. Any
+     * selected content is deleted.
+     *
+     * @param frag DocumentFragment to insert.
+     */
+    public void insertFragment(DocumentFragment frag)
+        throws DocumentValidationException;
+
+    /**
+     * Inserts the given element at the current caret position. Any
+     * selected content becomes the new contents of the element.
+     *
+     * @param element Element to insert.
+     */
+    public void insertElement(Element element)
+        throws DocumentValidationException;
+        
+    /**
+     * Inserts the given text at the current caret position. Any
+     * selected content is first deleted.
+     *
+     * @param text String to insert.
+     */
+    public void insertText(String text) throws DocumentValidationException;
+
+    /**
+     * Returns the value of the debugging flag.
+     */
+    public boolean isDebugging();
+    
+    /**
+     * Sets the value of the debugging flag. When debugging, copious information
+     * is dumped to stdout.
+     * @param debugging true if debugging is to be enabled.
+     */
+    public void setDebugging(boolean debugging);
+
+    /**
+     * Replaces the current element with the given element. 
+     * The content of the element is preserved.
+     * @param element Element to replace the current element with.
+     * @throws DocumentValidationException if the given element is
+     * not valid at this place in the document, or if the current
+     * element's content is not compatible with the given element.
+     */
+    public void morph(Element element) throws DocumentValidationException;
+    
+    /**
+     * Moves the caret a given distance relative to the current caret
+     * offset.
+     * @param distance Amount by which to alter the caret offset. 
+     * Positive values increase the caret offset.
+     */
+    public void moveBy(int distance);
+
+    /**
+     * Moves the caret a given distance relative to the current caret
+     * offset.
+     * @param distance Amount by which to alter the caret offset. 
+     * Positive values increase the caret offset.
+     * @param select if true, the current selection is extended to
+     * match the new caret offset
+     */
+    public void moveBy(int distance, boolean select);
+
+    /**
+     * Moves the caret to a new offset. The selection is not extended.
+     * This is equivalent to <code>moveTo(offset, false)</code>. 
+     * @param int new offset for the caret. The offset must be >= 1 and less
+     * than the document size; if not, it is silently ignored.
+     */
+    public void moveTo(int offset);
+    
+    /**
+     * Moves the caret to the new offset, possibly changing the selection.
+     *
+     * @param int new offset for the caret. The offset must be >= 1 and less
+     * than the document size; if not, it is silently ignored.
+     * @param select if true, the current selection is extended to
+     * match the new caret offset.
+     */
+    public void moveTo(int offset, boolean select);
+
+    /**
+     * Move the caret to the end of the current line.
+     * @param select If true, the selection is extended.
+     */
+    public void moveToLineEnd(boolean select);
+
+    /**
+     * Move the caret to the start of the current line.
+     * @param select If true, the selection is extended.
+     */
+    public void moveToLineStart(boolean select);
+
+    /**
+     * Move the caret down to the next line. Attempts to preserve the
+     * same distance from the left edge of the control.
+     * @param select If true, the selection is extended.
+     */
+    public void moveToNextLine(boolean select);
+
+    /**
+     * Move the caret down to the next page. Attempts to preserve the same
+     * distance from the left edge of the control.
+     * @param select If true, the selection is extended.
+     */
+    public void moveToNextPage(boolean select);
+    
+    /**
+     * Moves the caret to the end of the current or next word.
+     * @param select If true, the selection is extended.
+     */
+    public void moveToNextWord(boolean select);
+    
+    /**
+     * Moves the caret up to the previous line. 
+     * @param select If true, the selection is extended
+     */
+    public void moveToPreviousLine(boolean select);
+
+    /**
+     * Moves the caret up to the previous page. 
+     * @param select If true, the selection is extended
+     */
+    public void moveToPreviousPage(boolean select);
+
+    /**
+     * Moves the caret to the start of the current or previous word.
+     * @param select If true, the selection is extended.
+     */
+    public void moveToPreviousWord(boolean select);
+
+    /**
+     * Paste the current clipboard contents into the document at the
+     * current caret position.
+     */
+    public void paste() throws DocumentValidationException;
+
+    /**
+     * Paste the current clipboard contents as plain text into the document at the
+     * current caret position.
+     */
+    public void pasteText() throws DocumentValidationException;
+    /**
+     * Redoes the last action on the redo stack.
+     * @throws CannotRedoException if the last action cannot be re-done, or if
+     * there is nothing to redo.
+     */
+    public void redo();
+
+    /**
+     * Removes an attribute from the current element. Attributes removed in this manner
+     * (as opposed to calling Element.setAttribute directly) will be subject to
+     * undo/redo.
+     * @param attributeName Name of the attribute to remove.
+     */
+    public void removeAttribute(String attributeName);
+
+    /**
+     * Execute a Runnable, restoring the caret position to its original position
+     * afterward.
+     * 
+     * @param runnable Runnable to be invoked.
+     */
+    public void savePosition(Runnable runnable);
+    
+    /**
+     * Selects all content in the document.
+     */
+    public void selectAll();
+    
+    /**
+     * Selects the word at the current caret offset.
+     */
+    public void selectWord();
+
+    /**
+     * Sets the value of an attribute in the current element. 
+     * Attributes set in this manner
+     * (as opposed to calling Element.setAttribute directly) will be subject to
+     * undo/redo.
+     * @param attributeName Name of the attribute being changed.
+     * @param value New value for the attribute. If null, the attribute is 
+     * removed from the element.
+     */
+    public void setAttribute(String attributeName, String value);
+
+    /**
+     * Sets the box factory to be applied to the current document during editing.
+     * 
+     * @param boxFactory the new BoxFactory to use
+     */
+    public void setBoxFactory(BoxFactory boxFactory);
+
+    /**
+     * Sets a new document for this control.
+     * 
+     * @param document new Document to display
+     * @param styleSheet StyleSheet to use for formatting
+     */    
+    public void setDocument(Document document, StyleSheet styleSheet);
+        
+    
+    /**
+     * Sets a new document for this control.
+     * 
+     * @param docURL URL of the document to display.
+     * @param ssURL URL of the stylesheet to use for formatting.
+     */    
+    public void setDocument(URL docURL, URL ssURL) 
+		throws IOException, ParserConfigurationException, SAXException;
+    
+    /**
+     * Sets the width to which the document should be layed out. The actual
+     * resulting width may be different due to overflowing boxes. 
+     */    
+    public void setLayoutWidth(int width);
+    
+    /**
+     * Sets the style sheet to be applied to the current document during editing.
+     * If no resolver has been set, the style sheet will also be used for any
+     * subsequently loaded documents. If a resolver has been set, the style sheet
+     * returned by the resolver will be used for subsequently loaded documents.
+     * 
+     * @param styleSheet the new StyleSheet to use
+     */
+    public void setStyleSheet(StyleSheet styleSheet);
+    
+    /**
+     * Sets the stylesheet to be used for this widget.
+     *
+     * @param ssUrl URL of the CSS style sheet to use.
+     */
+    public void setStyleSheet(URL ssUrl) throws IOException;
+        
+    /**
+     * Split the element at the current caret offset. This is the normal behaviour
+     * when the user presses Enter.
+     */
+    public void split() throws DocumentValidationException;
+
+    /**
+     * Undoes the last action on the undo stack.
+     * @throws CannotUndoException if the last action cannot be undone, or
+     * if there's nothing left to undo.
+     */
+    public void undo();
+
+    /**
+     * Return the offset into the document for the given coordinates.
+     *
+     * @param x the x-coordinate
+     * @param y the y-coordinate
+     */
+    public int viewToModel(int x, int y);
+
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/widget/VexWidgetImpl.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/widget/VexWidgetImpl.java
new file mode 100644
index 0000000..c1be685
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.core/src/org/eclipse/wst/xml/vex/core/internal/widget/VexWidgetImpl.java
@@ -0,0 +1,1638 @@
+/*******************************************************************************
+ * 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.widget;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+
+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.Graphics;
+import org.eclipse.wst.xml.vex.core.internal.core.IntRange;
+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.StyleSheetReader;
+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.DocumentEvent;
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentFragment;
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentListener;
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentReader;
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentValidationException;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.dom.IWhitespacePolicy;
+import org.eclipse.wst.xml.vex.core.internal.dom.IWhitespacePolicyFactory;
+import org.eclipse.wst.xml.vex.core.internal.dom.Position;
+import org.eclipse.wst.xml.vex.core.internal.dom.Validator;
+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.BoxFactory;
+import org.eclipse.wst.xml.vex.core.internal.layout.CssBoxFactory;
+import org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext;
+import org.eclipse.wst.xml.vex.core.internal.layout.RootBox;
+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.CompoundEdit;
+import org.eclipse.wst.xml.vex.core.internal.undo.IUndoableEdit;
+import org.xml.sax.SAXException;
+
+/**
+ * A Swing component that allows the display and edit of an XML
+ * document with an associated CSS stylesheet.
+ */
+public class VexWidgetImpl implements IVexWidget {
+
+    /** 
+     * Number of pixel rows above and below the caret that are rendered
+     * at a time.
+     */
+    private static final int LAYOUT_WINDOW = 5000;
+    
+    /** 
+     * Because the height of each BlockElementBox is initially estimated,
+     * we sometimes have to try several times before the band being laid
+     * out is properly positioned about the offset. When the position
+     * of the offset changes by less than this amount between subsequent
+     * layout calls, the layout is considered stable.
+     */
+    private static final int LAYOUT_TOLERANCE = 500;
+    
+    /**
+     * Minimum layout width, in pixels. Prevents performance problems
+     * when width is very small.
+     */
+    private static final int MIN_LAYOUT_WIDTH = 200;
+    
+    private boolean debugging;
+    
+    private HostComponent hostComponent;
+    private int layoutWidth = 500; // something reasonable to handle a document
+                                   // being set before the widget is sized
+    
+    private Document document;
+    private StyleSheet styleSheet;
+    private BoxFactory boxFactory = new CssBoxFactory();
+    
+    private RootBox rootBox;
+
+    /** Stacks of UndoableEditEvents; items added and removed from end of list */
+    private LinkedList undoList = new LinkedList();
+    private LinkedList redoList = new LinkedList();
+    private static final int MAX_UNDO_STACK_SIZE = 100;
+    private int undoDepth;
+
+    /** Support for beginWork/endWork */
+    private int beginWorkCount = 0;
+    private int beginWorkCaretOffset;
+    private CompoundEdit compoundEdit;
+
+    private int caretOffset;
+    private int mark;
+    private int selectionStart;
+    private int selectionEnd;
+    
+    private Element currentElement;
+
+    private boolean caretVisible = true;
+    private Caret caret;
+    private Color caretColor;
+    
+    // x offset to be maintained when moving vertically    
+    private int magicX = -1;
+
+    private boolean antiAliased = false;
+    
+
+    //======================================================= LISTENERS
+
+    private DocumentListener documentListener = new DocumentListener() {
+
+        public void attributeChanged(DocumentEvent e) {
+            invalidateElementBox(e.getParentElement());
+            
+            // flush cached styles, since they might depend attribute values
+            // via conditional selectors
+            getStyleSheet().flushStyles(e.getParentElement());
+            
+            if (beginWorkCount == 0) {
+                VexWidgetImpl.this.relayout();
+            }
+            
+            addEdit(e.getUndoableEdit(), getCaretOffset());
+            hostComponent.fireSelectionChanged();
+        }
+
+        public void beforeContentDeleted(DocumentEvent e) {
+        }
+        
+        public void beforeContentInserted(DocumentEvent e) {
+        }
+        
+        public void contentDeleted(DocumentEvent e) {
+            invalidateElementBox(e.getParentElement());
+
+            if (beginWorkCount == 0) {
+                VexWidgetImpl.this.relayout();
+            }
+
+            addEdit(e.getUndoableEdit(), getCaretOffset());
+        }
+
+        public void contentInserted(DocumentEvent e) {
+            invalidateElementBox(e.getParentElement());
+            
+            if (beginWorkCount == 0) {
+                VexWidgetImpl.this.relayout();
+            }
+
+            addEdit(e.getUndoableEdit(), getCaretOffset());
+        }
+
+    };
+    
+    //======================================================= PUBLIC INTERFACE
+
+    /**
+     * Class constructor.
+     */
+    public VexWidgetImpl(HostComponent hostComponent) {
+        this.hostComponent = hostComponent;
+    }
+
+    public void beginWork() {
+        if (this.beginWorkCount == 0) {
+            this.beginWorkCaretOffset = this.getCaretOffset();
+            this.compoundEdit = new CompoundEdit();
+        }
+        this.beginWorkCount++;
+    }
+
+    /**
+     * Returns true if the given fragment can be inserted at the current 
+     * caret position.
+     * @param frag DocumentFragment to be inserted.
+     */
+    public boolean canInsertFragment(DocumentFragment frag) {
+
+        Document doc = this.getDocument();
+        if (doc == null) {
+            return false;
+        }
+
+        Validator validator = doc.getValidator();
+        if (validator == null) {
+            return true;
+        }
+
+        int startOffset = this.getCaretOffset();
+        int endOffset = this.getCaretOffset();
+        if (this.hasSelection()) {
+            startOffset = this.getSelectionStart();
+            endOffset = this.getSelectionEnd();
+        }
+
+        Element parent = this.getDocument().getElementAt(startOffset);
+        String[] seq1 =
+            doc.getNodeNames(parent.getStartOffset() + 1, startOffset);
+        String[] seq2 = frag.getNodeNames();
+        String[] seq3 = doc.getNodeNames(endOffset, parent.getEndOffset());
+
+        return validator.isValidSequence(
+            parent.getName(),
+            seq1,
+            seq2,
+            seq3,
+            true);
+    }
+
+    /**
+     * Returns true if text can be inserted at the current position.
+     */
+    public boolean canInsertText() {
+
+        Document doc = this.getDocument();
+        if (doc == null) {
+            return false;
+        }
+
+        Validator validator = this.document.getValidator();
+        if (validator == null) {
+            return true;
+        }
+
+        int startOffset = this.getCaretOffset();
+        int endOffset = this.getCaretOffset();
+        if (this.hasSelection()) {
+            startOffset = this.getSelectionStart();
+            endOffset = this.getSelectionEnd();
+        }
+
+        Element parent = this.getDocument().getElementAt(startOffset);
+        String[] seq1 =
+            doc.getNodeNames(parent.getStartOffset() + 1, startOffset);
+        String[] seq2 = new String[] { Validator.PCDATA };
+        String[] seq3 = doc.getNodeNames(endOffset, parent.getEndOffset());
+
+        return validator.isValidSequence(
+            parent.getName(),
+            seq1,
+            seq2,
+            seq3,
+            true);
+    }
+
+    public boolean canPaste() {
+        throw new UnsupportedOperationException("Must be implemented in tookit-specific widget.");
+    }
+
+    public boolean canPasteText() {
+        throw new UnsupportedOperationException("Must be implemented in tookit-specific widget.");
+    }
+
+    public boolean canRedo() {
+        return this.redoList.size() > 0;
+    }
+
+    public boolean canUndo() {
+        return this.undoList.size() > 0;
+    }
+
+    public boolean canUnwrap() {
+        Document doc = this.getDocument();
+        if (doc == null) {
+            return false;
+        }
+
+        Validator validator = doc.getValidator();
+        if (validator == null) {
+            return false;
+        }
+
+        Element element = doc.getElementAt(this.getCaretOffset());
+        Element parent = element.getParent();
+        if (parent == null) {
+            // can't unwrap the root
+            return false;
+        }
+
+        String[] seq1 =
+            doc.getNodeNames(
+                parent.getStartOffset() + 1,
+                element.getStartOffset());
+        String[] seq2 =
+            doc.getNodeNames(
+                element.getStartOffset() + 1,
+                element.getEndOffset());
+        String[] seq3 =
+            doc.getNodeNames(element.getEndOffset() + 1, parent.getEndOffset());
+
+        return validator.isValidSequence(
+            parent.getName(),
+            seq1,
+            seq2,
+            seq3,
+            true);
+    }
+
+    public void copySelection() {
+        throw new UnsupportedOperationException("Must be implemented in tookit-specific widget.");
+    }
+
+    public void cutSelection() {
+        throw new UnsupportedOperationException("Must be implemented in tookit-specific widget.");
+    }
+
+    public void deleteNextChar() throws DocumentValidationException {
+        if (this.hasSelection()) {
+            this.deleteSelection();
+        } else {
+            int offset = this.getCaretOffset();
+            Document doc = this.getDocument();
+            int n = doc.getLength() - 1;
+            Element element = doc.getElementAt(offset);
+            
+            if (offset == n) {
+                // nop
+            } else if (this.isBetweenMatchingElements(offset)) {
+                this.joinElementsAt(offset);
+            } else if (this.isBetweenMatchingElements(offset + 1)) {
+                this.joinElementsAt(offset + 1);
+            } else if (element.isEmpty()) {
+                // deleting the right sentinel of an empty element
+                // so just delete the whole element an move on
+                this.moveTo(offset - 1, false);
+                this.moveTo(offset + 1, true);
+                this.deleteSelection();
+            } else if (doc.getElementAt(offset + 1).isEmpty()) {
+                // deleting the left sentinel of an empty element
+                // so just delete the whole element an move on
+                this.moveTo(offset + 2, true);
+                this.deleteSelection();
+            } else {
+                if (doc.getCharacterAt(offset) != 0) {
+                    this.moveTo(offset, false);
+                    this.moveTo(offset + 1, true);
+                    this.deleteSelection();
+                }
+            }
+        }
+    }
+
+    public void deletePreviousChar() throws DocumentValidationException {
+
+        if (this.hasSelection()) {
+            this.deleteSelection();
+        } else {
+            int offset = this.getCaretOffset();
+            Document doc = this.getDocument();
+            Element element = doc.getElementAt(offset);
+            
+            if (offset == 1) {
+                // nop
+            } else if (this.isBetweenMatchingElements(offset)) {
+                this.joinElementsAt(offset);
+            } else if (this.isBetweenMatchingElements(offset - 1)) {
+                this.joinElementsAt(offset - 1);
+            } else if (element.isEmpty()) {
+                // deleting the left sentinel of an empty element
+                // so just delete the whole element an move on
+                this.moveTo(offset - 1, false);
+                this.moveTo(offset + 1, true);
+                this.deleteSelection();
+            } else if (doc.getElementAt(offset - 1).isEmpty()) {
+                // deleting the right sentinel of an empty element
+                // so just delete the whole element an move on
+                this.moveTo(offset - 2, true);
+                this.deleteSelection();
+            } else {
+                offset--;
+                if (doc.getCharacterAt(offset) != 0) {
+                    this.moveTo(offset, false);
+                    this.moveTo(offset + 1, true);
+                    this.deleteSelection();
+                }
+            }
+        }
+
+    }
+
+    public void deleteSelection() {
+        try {
+            if (this.hasSelection()) {
+                this.document.delete(this.getSelectionStart(), this.getSelectionEnd());
+                this.moveTo(this.getSelectionStart());
+            }
+        } catch (DocumentValidationException ex) {
+            ex.printStackTrace(); // This should never happen, because we
+                                  // constrain the selection
+        }
+    }
+
+    public void doWork(Runnable runnable) {
+        this.doWork(false, runnable);
+    }
+    
+    public void doWork(boolean savePosition, Runnable runnable) {
+        Position position = null;
+        
+        if (savePosition) {
+            position = this.getDocument().createPosition(this.getCaretOffset());
+        }
+        
+        boolean success = false;
+        try {
+            this.beginWork();
+            runnable.run();
+            success = true;
+        } catch (Exception ex) {
+            ex.printStackTrace();
+        } finally {
+            this.endWork(success);
+            if (position != null) {
+                this.moveTo(position.getOffset());
+            }
+        }
+    }
+    
+    public void endWork(boolean success) {
+        this.beginWorkCount--;
+        if (this.beginWorkCount == 0) {
+            //this.compoundEdit.end();
+            if (success) {
+                this.undoList.add(
+                    new UndoableAndOffset(
+                        this.compoundEdit,
+                        this.beginWorkCaretOffset));
+                this.undoDepth++;
+                if (undoList.size() > MAX_UNDO_STACK_SIZE) {
+                    undoList.removeFirst();
+                }
+                this.redoList.clear();
+                this.relayout();
+                this.hostComponent.fireSelectionChanged();
+            } else {
+                try {
+                    this.compoundEdit.undo();
+                    this.moveTo(this.beginWorkCaretOffset);
+                } catch (CannotUndoException e) {
+                    // TODO: handle exception
+                }
+            }
+            this.compoundEdit = null;
+        }
+    }
+
+    public Box findInnermostBox(IBoxFilter filter) {
+        return this.findInnermostBox(filter, this.getCaretOffset());
+    }
+    
+    /**
+     * Returns the innermost box containing the given offset
+     * that matches the given filter. 
+     *
+     * @param filter IBoxFilter that determines which box to return
+     * @param offset Document offset around which to search.
+     */
+    private Box findInnermostBox(IBoxFilter filter, int offset) {
+
+        Box box = this.rootBox.getChildren()[0];
+        Box matchingBox = null;
+
+        for (;;) {
+            if (filter.matches(box)) {
+                matchingBox = box;
+            }
+
+            Box original = box;
+            Box[] children = box.getChildren();
+            for (int i = 0; i < children.length; i++) {
+
+                Box child = children[i];
+                
+                if (child.hasContent()
+                        && offset >= child.getStartOffset()
+                        && offset <= child.getEndOffset()) {
+                    box = child;
+                    break;
+                }
+            }
+
+            if (box == original) {
+                // No child found containing offset,
+                // so just return the latest match.
+                return matchingBox;
+            }
+        }
+        
+    }
+
+    /**
+     * Returns the background color for the control, which is the same
+     * as the background color of the root element.
+     */
+    public Color getBackgroundColor() {
+        return this.styleSheet.getStyles(this.document.getRootElement()).getBackgroundColor();
+    }
+    
+    public BoxFactory getBoxFactory() {
+        return this.boxFactory;
+    }
+
+    /**
+     * Returns the current caret.
+     */
+    public Caret getCaret() {
+        return this.caret;
+    }
+    
+    public int getCaretOffset() {
+        return this.caretOffset;
+    }
+
+    public Element getCurrentElement() {
+        return this.currentElement;
+    }
+
+    public Document getDocument() {
+        return this.document;
+    }
+
+    /**
+     * Returns the natural height of the widget based on the current layout
+     * width.
+     */
+    public int getHeight() {
+        return this.rootBox.getHeight();
+    }
+    
+    public String[] getValidInsertElements() {
+
+        Document doc = this.getDocument();
+        if (doc == null) {
+            return new String[0];
+        }
+
+        Validator validator = doc.getValidator();
+        if (validator == null) {
+            return new String[0];
+        }
+
+        int startOffset = this.getCaretOffset();
+        int endOffset = this.getCaretOffset();
+        if (this.hasSelection()) {
+            startOffset = this.getSelectionStart();
+            endOffset = this.getSelectionEnd();
+        }
+
+        Element parent = doc.getElementAt(startOffset);
+        String[] prefix =
+            doc.getNodeNames(parent.getStartOffset() + 1, startOffset);
+        String[] suffix = doc.getNodeNames(endOffset, parent.getEndOffset());
+        List candidates = new ArrayList();
+        candidates.addAll(
+            validator.getValidItems(parent.getName(), prefix, suffix));
+        candidates.remove(Validator.PCDATA);
+
+        // If there's a selection, root out those candidates that can't
+        // contain it.
+        if (this.hasSelection()) {
+            String[] selectedNodes = doc.getNodeNames(startOffset, endOffset);
+            for (Iterator iter = candidates.iterator(); iter.hasNext();) {
+                String candidate = (String) iter.next();
+                if (!validator
+                    .isValidSequence(candidate, selectedNodes, true)) {
+                    iter.remove();
+                }
+            }
+        }
+
+        Collections.sort(candidates);
+        return (String[]) candidates.toArray(new String[candidates.size()]);
+    }
+
+    /**
+     * Returns the value of the antiAliased flag.
+     */
+    public boolean isAntiAliased() {
+        return this.antiAliased;
+    }
+
+    public boolean isDebugging() {
+        return debugging;
+    }
+    
+    public String[] getValidMorphElements() {
+
+        Document doc = this.getDocument();
+        if (doc == null) {
+            return new String[0];
+        }
+
+        Validator validator = doc.getValidator();
+        if (validator == null) {
+            return new String[0];
+        }
+
+        Element element = doc.getElementAt(this.getCaretOffset());
+        Element parent = element.getParent();
+        if (parent == null) {
+            // can't morph the root
+            return new String[0];
+        }
+
+        String[] prefix =
+            doc.getNodeNames(
+                parent.getStartOffset() + 1,
+                element.getStartOffset());
+        String[] suffix =
+            doc.getNodeNames(element.getEndOffset() + 1, parent.getEndOffset());
+
+        List candidates = new ArrayList();
+        candidates.addAll(
+            validator.getValidItems(parent.getName(), prefix, suffix));
+        candidates.remove(Validator.PCDATA);
+
+        // root out those that can't contain the current content        
+        String[] content =
+            doc.getNodeNames(
+                element.getStartOffset() + 1,
+                element.getEndOffset());
+        for (Iterator iter = candidates.iterator(); iter.hasNext();) {
+            String candidate = (String) iter.next();
+            if (!validator.isValidSequence(candidate, content, true)) {
+                iter.remove();
+            }
+        }
+
+        Collections.sort(candidates);
+        return (String[]) candidates.toArray(new String[candidates.size()]);
+    }
+
+    public int getSelectionEnd() {
+        return this.selectionEnd;
+    }
+
+    public int getSelectionStart() {
+        return this.selectionStart;
+    }
+
+    public DocumentFragment getSelectedFragment() {
+        if (this.hasSelection()) {
+            return this.document.getFragment(
+                this.getSelectionStart(),
+                this.getSelectionEnd());
+        } else {
+            return null;
+        }
+    }
+
+    public String getSelectedText() {
+        if (this.hasSelection()) {
+            return this.document.getText(
+                this.getSelectionStart(),
+                this.getSelectionEnd());
+        } else {
+            return "";
+        }
+    }
+
+    public StyleSheet getStyleSheet() {
+        return this.styleSheet;
+    }
+    
+    public int getUndoDepth() {
+        return this.undoDepth;
+    }
+
+    public int getLayoutWidth() {
+        return this.layoutWidth;
+    }
+    
+    public RootBox getRootBox(){
+    	return this.rootBox;    	
+    }
+    
+    public boolean hasSelection() {
+        return this.getSelectionStart() != this.getSelectionEnd();
+    }
+
+    public void insertChar(char c) throws DocumentValidationException {
+        if (this.hasSelection()) {
+            this.deleteSelection();
+        }
+        this.document.insertText(this.getCaretOffset(), Character.toString(c));
+        this.moveBy(+1);
+    }
+
+    public void insertFragment(DocumentFragment frag)
+        throws DocumentValidationException {
+
+        if (this.hasSelection()) {
+            this.deleteSelection();
+        }
+
+        this.document.insertFragment(this.getCaretOffset(), frag);
+        this.moveTo(this.getCaretOffset() + frag.getLength());
+    }
+
+    public void insertElement(Element element)
+        throws DocumentValidationException {
+        
+        boolean success = false;
+        try {
+            this.beginWork();
+
+            DocumentFragment frag = null;
+            if (this.hasSelection()) {
+                frag = this.getSelectedFragment();
+                this.deleteSelection();
+            }
+
+            this.document.insertElement(this.getCaretOffset(), element);
+            this.moveTo(this.getCaretOffset() + 1);
+            if (frag != null) {
+                this.insertFragment(frag);
+            }
+            this.scrollCaretVisible();
+            success = true;
+        } finally {
+            this.endWork(success);
+        }
+    }
+
+    public void insertText(String text) throws DocumentValidationException {
+
+        if (this.hasSelection()) {
+            this.deleteSelection();
+        }
+
+        boolean success = false;
+        try {
+            this.beginWork();
+            int i = 0;
+            for (;;) {
+                int j = text.indexOf('\n', i);
+                if (j == -1) {
+                    break;
+                }
+                this.document.insertText(this.getCaretOffset(), text.substring(i, j));
+                this.moveTo(this.getCaretOffset() + (j - i));
+                this.split();
+                i = j + 1;
+            }
+
+            if (i < text.length()) {
+                this.document.insertText(this.getCaretOffset(), text.substring(i));
+                this.moveTo(this.getCaretOffset() + (text.length() - i));
+            }
+            success = true;
+        } finally {
+            this.endWork(success);
+        }
+    }
+
+    public void morph(Element element) throws DocumentValidationException {
+
+        Document doc = this.getDocument();
+        int offset = this.getCaretOffset();
+        Element currentElement = doc.getElementAt(offset);
+
+        if (currentElement == doc.getRootElement()) {
+            throw new DocumentValidationException("Cannot morph the root element.");
+        }
+
+        boolean success = false;
+        try {
+            this.beginWork();
+            this.moveTo(currentElement.getStartOffset() + 1, false);
+            this.moveTo(currentElement.getEndOffset(), true);
+            DocumentFragment frag = this.getSelectedFragment();
+            this.deleteSelection();
+            this.moveBy(-1, false);
+            this.moveBy(2, true);
+            this.deleteSelection();
+            this.insertElement(element);
+            if (frag != null) {
+            	this.insertFragment(frag);
+            }
+            this.moveTo(offset, false);
+            success = true;
+        } finally {
+            this.endWork(success);
+        }
+
+    }
+
+    public void moveBy(int distance) {
+        this.moveTo(this.getCaretOffset() + distance, false);
+    }
+
+    public void moveBy(int distance, boolean select) {
+        this.moveTo(this.getCaretOffset() + distance, select);
+    }
+
+    public void moveTo(int offset) {
+        this.moveTo(offset, false);
+    }
+    
+    public void moveTo(int offset, boolean select) {
+        
+        if (offset >= 1 && offset <= this.document.getLength() - 1) {
+
+            // repaint the selection area, if any
+            this.repaintCaret();
+            this.repaintRange(this.getSelectionStart(), this.getSelectionEnd());
+
+            Element oldElement = this.currentElement;
+            
+            this.caretOffset = offset;
+            
+            this.currentElement = this.document.getElementAt(offset);
+            
+            if (select) {
+                this.selectionStart = Math.min(this.mark, this.caretOffset);
+                this.selectionEnd = Math.max(this.mark, this.caretOffset);
+                
+                // move selectionStart and selectionEnd to make sure we don't 
+                // select a partial element
+                Element commonElement =
+                    this.document.findCommonElement(this.selectionStart, this.selectionEnd);
+                
+                Element element = this.document.getElementAt(this.selectionStart);
+                while (element != commonElement) {
+                    this.selectionStart = element.getStartOffset();
+                    element = this.document.getElementAt(this.selectionStart);
+                }
+                
+                element = this.document.getElementAt(this.selectionEnd);
+                while (element != commonElement) {
+                    this.selectionEnd = element.getEndOffset() + 1;
+                    element = this.document.getElementAt(this.selectionEnd);
+                }
+                
+            } else {
+                this.mark = offset;
+                this.selectionStart = offset;
+                this.selectionEnd = offset;
+            }
+            
+            if (this.beginWorkCount == 0) {
+                this.relayout();
+            }
+            
+            Graphics g = this.hostComponent.createDefaultGraphics();
+            LayoutContext context = this.createLayoutContext(g);
+            this.caret = this.rootBox.getCaret(context, offset);
+            
+            Element element = this.getCurrentElement();
+            if (element != oldElement) {
+                this.caretColor = Color.BLACK;
+                while (element != null) {
+                    Color bgColor = this.styleSheet.getStyles(element).getBackgroundColor();
+                    if (bgColor != null) {
+                        int red = ~bgColor.getRed() & 0xff;
+                        int green = ~bgColor.getGreen() & 0xff;
+                        int blue = ~bgColor.getBlue() & 0xff;
+                        this.caretColor = new Color(red, green, blue);
+                        break;
+                    }
+                    element = element.getParent();
+                }
+            }
+            
+            g.dispose();
+            
+            this.magicX = -1;
+
+            this.scrollCaretVisible();
+
+            this.hostComponent.fireSelectionChanged();
+
+            this.caretVisible = true;
+
+            this.repaintRange(this.getSelectionStart(), this.getSelectionEnd());
+        }
+    }
+
+    public void moveToLineEnd(boolean select) {
+        this.moveTo(this.rootBox.getLineEndOffset(this.getCaretOffset()), select);
+    }
+
+    public void moveToLineStart(boolean select) {
+        this.moveTo(this.rootBox.getLineStartOffset(this.getCaretOffset()), select);
+    }
+
+    public void moveToNextLine(boolean select) {
+        int x = this.magicX == -1 ? this.caret.getBounds().getX() : this.magicX;
+        
+        Graphics g = this.hostComponent.createDefaultGraphics();
+        int offset = this.rootBox.getNextLineOffset(this.createLayoutContext(g), this.getCaretOffset(), x);
+        g.dispose();
+         
+        this.moveTo(offset, select);
+        this.magicX = x;
+    }
+
+    public void moveToNextPage(boolean select) {
+        int x = this.magicX == -1 ? this.caret.getBounds().getX() : this.magicX;
+        int y = this.caret.getY() + 
+            Math.round(this.hostComponent.getViewport().getHeight() * 0.9f);
+        this.moveTo(this.viewToModel(x, y), select);
+        this.magicX = x;
+    }
+
+    public void moveToNextWord(boolean select) {
+        Document doc = this.getDocument();
+        int n = doc.getLength() - 1;
+        int offset = this.getCaretOffset();
+        while (offset < n
+            && !Character.isLetterOrDigit(doc.getCharacterAt(offset))) {
+            offset++;
+        }
+
+        while (offset < n
+            && Character.isLetterOrDigit(doc.getCharacterAt(offset))) {
+            offset++;
+        }
+
+        this.moveTo(offset, select);
+    }
+
+    public void moveToPreviousLine(boolean select) {
+        int x = this.magicX == -1 ? this.caret.getBounds().getX() : this.magicX;
+
+        Graphics g = this.hostComponent.createDefaultGraphics();
+        int offset = this.rootBox.getPreviousLineOffset(this.createLayoutContext(g), this.getCaretOffset(), x);
+        g.dispose();
+         
+        this.moveTo(offset, select);
+        this.magicX = x;
+    }
+
+    public void moveToPreviousPage(boolean select) {
+        int x = this.magicX == -1 ? this.caret.getBounds().getX() : this.magicX;
+        int y = this.caret.getY() - 
+            Math.round(this.hostComponent.getViewport().getHeight() * 0.9f);
+        this.moveTo(this.viewToModel(x, y), select);
+        this.magicX = x;
+    }
+
+    public void moveToPreviousWord(boolean select) {
+        Document doc = this.getDocument();
+        int offset = this.getCaretOffset();
+        while (offset > 1
+            && !Character.isLetterOrDigit(doc.getCharacterAt(offset - 1))) {
+            offset--;
+        }
+
+        while (offset > 1
+            && Character.isLetterOrDigit(doc.getCharacterAt(offset - 1))) {
+            offset--;
+        }
+
+        this.moveTo(offset, select);
+    }
+
+    /**
+     * Paints the contents of the widget in the given Graphics at the given 
+     * point.
+     * @param g Graphics in which to draw the widget contents
+     * @param x x-coordinate at which to draw the widget
+     * @param y y-coordinate at which to draw the widget
+     */
+    public void paint(Graphics g, int x, int y) {
+        
+        if (this.rootBox == null) {
+            return;
+        }
+
+        LayoutContext context = this.createLayoutContext(g);
+        
+        // Since we may be scrolling to sections of the document that have
+        // yet to be layed out, lay out any exposed area.
+        //
+        // TODO: this will probably be inaccurate, since we should really
+        // iterate the layout, but we don't have an offset around which 
+        // to iterate...what to do, what to do....
+        Rectangle rect = g.getClipBounds();
+        int oldHeight = this.rootBox.getHeight();
+        this.rootBox.layout(context, rect.getY(), rect.getY() + rect.getHeight());
+        if (this.rootBox.getHeight() != oldHeight) {
+            this.hostComponent.setPreferredSize(this.rootBox.getWidth(), this.rootBox.getHeight());
+        }
+        
+        this.rootBox.paint(context, 0, 0);
+        if (this.caretVisible) {
+            this.caret.draw(g, this.caretColor);
+        }
+        
+        // Debug hash marks
+        /*
+        ColorResource grey = g.createColor(new Color(160, 160, 160));
+        ColorResource oldColor = g.setColor(grey);
+        for (int y2 = rect.getY() - rect.getY() % 50; y2 < rect.getY() + rect.getHeight(); y2 += 50) {
+            g.drawLine(x, y + y2, x+10, y + y2);
+            g.drawString(Integer.toString(y2), x + 15, y + y2 - 10);
+        }
+        g.setColor(oldColor);
+        grey.dispose();
+        */
+    }
+    
+    public void paste() throws DocumentValidationException {
+        throw new UnsupportedOperationException("Must be implemented in tookit-specific widget.");
+    }
+
+    public void pasteText() throws DocumentValidationException {
+        throw new UnsupportedOperationException("Must be implemented in tookit-specific widget.");
+    }
+
+    public void redo() {
+        if (redoList.size() == 0) {
+            throw new CannotRedoException();
+        }
+        UndoableAndOffset event = (UndoableAndOffset) redoList.removeLast();
+        this.moveTo(event.caretOffset, false);
+        event.edit.redo();
+        this.undoList.add(event);
+        undoDepth++;
+    }
+
+    public void removeAttribute(String attributeName) {
+        try {
+            Element element = this.getCurrentElement();
+            if (element.getAttribute(attributeName) != null) {
+                element.removeAttribute(attributeName);
+            }
+        } catch (DocumentValidationException ex) {
+            ex.printStackTrace(); // TODO: when can this happen?
+        }
+    }
+
+    public void savePosition(Runnable runnable) {
+        Position pos = this.getDocument().createPosition(this.getCaretOffset());
+        try {
+            runnable.run();
+        } finally {
+            this.moveTo(pos.getOffset());
+        }
+    }
+
+    public void selectAll() {
+        this.moveTo(1);
+        this.moveTo(this.getDocument().getLength() - 1, true);
+    }
+
+    public void selectWord() {
+        Document doc = this.getDocument();
+        int startOffset = this.getCaretOffset();
+        int endOffset = this.getCaretOffset();
+        while (startOffset > 1
+            && Character.isLetterOrDigit(doc.getCharacterAt(startOffset - 1))) {
+            startOffset--;
+        }
+        int n = doc.getLength() - 1;
+        while (endOffset < n
+            && Character.isLetterOrDigit(doc.getCharacterAt(endOffset))) {
+            endOffset++;
+        }
+
+        if (startOffset < endOffset) {
+            this.moveTo(startOffset, false);
+            this.moveTo(endOffset, true);
+        }
+    }
+
+    /**
+     * Sets the value of the antiAliased flag.
+     *
+     * @param antiAliased if true, text is rendered using antialiasing.
+     */
+    public void setAntiAliased(boolean antiAliased) {
+        this.antiAliased = antiAliased;
+    }
+
+    public void setAttribute(String attributeName, String value) {
+        try {
+            Element element = this.getCurrentElement();
+            if (value == null) {
+                this.removeAttribute(attributeName);
+            } else if (!value.equals(element.getAttribute(attributeName))) {
+                element.setAttribute(attributeName, value);
+            }
+        } catch (DocumentValidationException ex) {
+            ex.printStackTrace(); // TODO: mebbe throw the exception instead
+        }
+    }
+
+    public void setBoxFactory(BoxFactory boxFactory) {
+        this.boxFactory = boxFactory;
+        if (this.document != null) {
+            this.relayout();
+        }
+    }
+
+    public void setDebugging(boolean debugging) {
+        this.debugging = debugging;
+    }
+    
+    public void setDocument(Document document, StyleSheet styleSheet) {
+
+        if (this.document != null) {
+            this.document.removeDocumentListener(this.documentListener);
+        }
+
+        this.document = document;
+        this.styleSheet = styleSheet;
+
+        this.undoList = new LinkedList();
+        this.undoDepth = 0;
+        this.redoList = new LinkedList();
+        this.beginWorkCount = 0;
+        this.compoundEdit = null;
+
+        this.createRootBox();
+        
+        this.moveTo(1);
+        this.document.addDocumentListener(this.documentListener);
+    }
+    
+    public void setDocument(URL docUrl, URL ssURL) 
+		throws IOException, ParserConfigurationException, SAXException {
+
+        StyleSheetReader ssReader = new StyleSheetReader();
+        final StyleSheet ss = ssReader.read(ssURL);
+        
+        DocumentReader reader = new DocumentReader();
+        
+        reader.setWhitespacePolicyFactory(new IWhitespacePolicyFactory() {
+            public IWhitespacePolicy getPolicy(String publicId) {
+                return new CssWhitespacePolicy(ss);
+            }
+        });
+        
+        Document doc = reader.read(docUrl);
+        this.setDocument(doc, ss);
+    }
+
+    /**
+     * Called by the host component when it gains or loses focus.
+     * @param focus true if the host component has focus
+     */    
+    public void setFocus(boolean focus) {
+        this.caretVisible = true;
+        this.repaintCaret();
+    }
+
+    public void setLayoutWidth(int width) {
+        width = Math.max(width, MIN_LAYOUT_WIDTH);
+        if (this.getDocument() != null && width != this.getLayoutWidth()) {
+            // this.layoutWidth is set by relayoutAll
+            this.relayoutAll(width, this.styleSheet);
+        } else {
+            // maybe doc is null. Let's store layoutWidth so it's right 
+            // when we set a doc
+            this.layoutWidth = width;
+        }
+    }
+    
+    public void setStyleSheet(StyleSheet styleSheet) {
+        if (this.getDocument() != null) {
+            this.relayoutAll(this.layoutWidth, styleSheet);
+        }
+    }
+    
+    public void setStyleSheet(URL ssUrl) throws IOException {
+        StyleSheetReader reader = new StyleSheetReader();
+        StyleSheet ss = reader.read(ssUrl);
+        this.setStyleSheet(ss);
+    }
+
+    public void split() throws DocumentValidationException {
+        
+        long start = System.currentTimeMillis();
+        
+        Document doc = this.getDocument();
+        Element element = doc.getElementAt(this.getCaretOffset());
+        Styles styles = this.getStyleSheet().getStyles(element);
+        while (!styles.isBlock()) {
+            element = element.getParent();
+            styles = this.getStyleSheet().getStyles(element);
+        }
+
+        boolean success = false;
+        try {
+            this.beginWork();
+            if (styles.getWhiteSpace().equals(CSS.PRE)) {
+                // can't call this.insertText() or we'll get an infinite loop
+                int offset = this.getCaretOffset();
+                doc.insertText(offset, "\n");
+                this.moveTo(offset + 1);
+            } else {
+                DocumentFragment frag = null;
+                int offset = this.getCaretOffset();
+                boolean atEnd = (offset == element.getEndOffset());
+                if (!atEnd) {
+                    this.moveTo(element.getEndOffset(), true);
+                    frag = this.getSelectedFragment();
+                    this.deleteSelection();
+                }
+
+                // either way, we are now at the end offset for the element
+                // let's move just outside
+                this.moveTo(this.getCaretOffset() + 1);
+
+                this.insertElement(new Element(element.getName()));
+                // TODO: clone attributes
+
+                if (!atEnd) {
+                    offset = this.getCaretOffset();
+                    this.insertFragment(frag);
+                    this.moveTo(offset, false);
+                }
+            }
+            success = true;
+        } finally {
+            this.endWork(success);
+        }
+        
+        if (this.isDebugging()) {
+            long end = System.currentTimeMillis();
+            System.out.println("split() took " + (end - start) + "ms");
+        }
+    }
+    
+    /**
+     * Toggles the caret to produce a flashing caret effect. This method should
+     * be called from the GUI event thread at regular intervals.
+     */
+    public void toggleCaret() {
+        this.caretVisible = !this.caretVisible;
+        this.repaintCaret();
+    }
+
+    public void undo() {
+        if (undoList.size() == 0) {
+            throw new CannotUndoException();
+        }
+        UndoableAndOffset event = (UndoableAndOffset) undoList.removeLast();
+        this.undoDepth--;
+        event.edit.undo();
+        this.moveTo(event.caretOffset, false);
+        this.redoList.add(event);
+    }
+
+    public int viewToModel(int x, int y) {
+        Graphics g = this.hostComponent.createDefaultGraphics();
+        LayoutContext context = this.createLayoutContext(g);
+        int offset = this.rootBox.viewToModel(context, x, y);
+        g.dispose();
+        return offset;
+    }
+
+    //================================================== PRIVATE
+
+    /**
+     * Captures an UndoableAction and the offset at which
+     * it occurred.
+     */
+    private class UndoableAndOffset {
+        public IUndoableEdit edit;
+        public int caretOffset;
+        public UndoableAndOffset(IUndoableEdit edit, int caretOffset) {
+            this.edit = edit;
+            this.caretOffset = caretOffset;
+        }
+    }
+
+    /**
+     * Processes the given edit, adding it to the undo stack.
+     * @param edit The edit to process.
+     * @param caretOffset Offset of the caret before the edit occurred.
+     * If the edit is undone, the caret is returned to this offset.
+     */
+    private void addEdit(IUndoableEdit edit, int caretOffset) {
+        
+        if (edit == null) {
+            return;
+        }
+        
+        if (this.compoundEdit != null) {
+            this.compoundEdit.addEdit(edit);
+        } else {
+            if (this.undoList.size() > 0 &&
+                    ((UndoableAndOffset) this.undoList.getLast()).edit.combine(edit)) {
+                return;
+            } else {
+                this.undoList.add(new UndoableAndOffset(edit, caretOffset));
+                this.undoDepth++;
+                if (undoList.size() > MAX_UNDO_STACK_SIZE) {
+                    undoList.removeFirst();
+                }
+                this.redoList.clear();
+            }
+        }
+    }
+    
+    /**
+     * Creates a layout context given a particular graphics context.
+     * @param g The graphics context to use for the layout context.
+     * @return the new layout context
+     */
+    private LayoutContext createLayoutContext(Graphics g) {
+        LayoutContext context = new LayoutContext();
+        context.setBoxFactory(this.getBoxFactory());
+        context.setDocument(this.getDocument());
+        context.setGraphics(g);
+        context.setStyleSheet(this.getStyleSheet());
+        
+        if (this.hasSelection()) {
+            context.setSelectionStart(this.getSelectionStart());
+            context.setSelectionEnd(this.getSelectionEnd());
+        } else {
+            context.setSelectionStart(this.getCaretOffset());
+            context.setSelectionEnd(this.getCaretOffset());
+        }
+
+        return context;
+    }
+
+    private void createRootBox() {
+        Graphics g = this.hostComponent.createDefaultGraphics();
+        LayoutContext context = this.createLayoutContext(g);
+        this.rootBox = new RootBox(context, this.document.getRootElement(), this.getLayoutWidth());
+        g.dispose();
+    }
+    
+    /**
+     * Invalidates the box tree due to document changes. The lowest box that completely
+     * encloses the changed element is invalidated.
+     *
+     * @param element Element for which to search.
+     */
+    private void invalidateElementBox(final Element element) {
+
+        BlockBox elementBox = (BlockBox) this.findInnermostBox(new IBoxFilter() {
+            public boolean matches(Box box) {
+                return box instanceof BlockBox 
+                && box.getElement() != null
+                && box.getStartOffset() <= element.getStartOffset() + 1
+                && box.getEndOffset() >= element.getEndOffset();
+            }
+        });
+        
+        if (elementBox != null) {
+            elementBox.invalidate(true);
+        }
+    }
+
+    /**
+     * Returns true if the given offset represents the boundary between
+     * two different elements with the same name and parent. This is used
+     * to determine if the elements can be joined via joinElementsAt.  
+     *
+     * @param int offset The offset to check.
+     */
+    private boolean isBetweenMatchingElements(int offset) {
+        if (offset <= 1 || offset >= this.getDocument().getLength() - 1) {
+            return false;
+        }
+        Element e1 = this.getDocument().getElementAt(offset - 1);
+        Element e2 = this.getDocument().getElementAt(offset + 1);
+        return e1 != e2
+            && e1.getParent() == e2.getParent()
+            && e1.getName().equals(e2.getName());
+    }
+
+    /**
+     * Calls layout() on the rootBox until the y-coordinate of a caret at
+     * the given offset converges, i.e. is less than LAYOUT_TOLERANCE pixels
+     * from the last call. 
+     * @param offset Offset around which we should lay out boxes.
+     */
+    private void iterateLayout(int offset) {
+        
+        int repaintStart = Integer.MAX_VALUE;
+        int repaintEnd = 0;
+        Graphics g = this.hostComponent.createDefaultGraphics();
+        LayoutContext context = this.createLayoutContext(g);
+        int layoutY = this.rootBox.getCaret(context, offset).getY();
+        
+        while (true) { 
+            
+            int oldLayoutY = layoutY;
+            IntRange repaintRange = this.rootBox.layout(context, layoutY - LAYOUT_WINDOW/2, layoutY + LAYOUT_WINDOW/2);
+            if (repaintRange != null) {
+                repaintStart = Math.min(repaintStart, repaintRange.getStart());
+                repaintEnd = Math.max(repaintEnd, repaintRange.getEnd());
+            }
+            
+            layoutY = this.rootBox.getCaret(context, offset).getY();
+            if (Math.abs(layoutY - oldLayoutY) < LAYOUT_TOLERANCE) {
+                break;
+            }
+        }
+        g.dispose();
+        
+        if (repaintStart < repaintEnd) {
+            Rectangle viewport = this.hostComponent.getViewport();
+            if (repaintStart < viewport.getY() + viewport.getHeight() &&
+                    repaintEnd > viewport.getY()) {
+                int start = Math.max(repaintStart, viewport.getY());
+                int end = Math.min(repaintEnd, viewport.getY() + viewport.getHeight());
+                this.hostComponent.repaint(viewport.getX(), start, viewport.getWidth(), end - start);
+            }
+        }
+    }
+    
+    /**
+     * Joins the elements at the given offset. Only works if
+     * isBetweenMatchingElements returns true for the same offset.
+     * Afterwards, the caret is left at the point where the join occurred.
+     * 
+     * @param offset Offset where the two elements meet.
+     */
+    private void joinElementsAt(int offset)
+        throws DocumentValidationException {
+
+        if (!isBetweenMatchingElements(offset)) {
+            throw new DocumentValidationException(
+                "Cannot join elements at offset " + offset);
+        }
+
+        boolean success = false;
+        try {
+            this.beginWork();
+            this.moveTo(offset + 1);
+            Element element = this.getCurrentElement();
+            boolean moveContent = !element.isEmpty();
+            DocumentFragment frag = null;
+            if (moveContent) {
+                this.moveTo(element.getEndOffset(), true);
+                frag = this.getSelectedFragment();
+                this.deleteSelection();
+            }
+            this.moveBy(-1);
+            this.moveBy(2, true);
+            this.deleteSelection();
+            this.moveBy(-1);
+            if (moveContent) {
+                int savedOffset = this.getCaretOffset();
+                this.insertFragment(frag);
+                this.moveTo(savedOffset, false);
+            }
+            success = true;
+        } finally {
+            this.endWork(success);
+        }
+    }
+
+    /**
+     * Lay out the area around the caret.
+     */
+    private void relayout() {
+        
+        long start = System.currentTimeMillis();
+        
+        int oldHeight = this.rootBox.getHeight();
+        
+        this.iterateLayout(this.getCaretOffset());
+
+        if (this.rootBox.getHeight() != oldHeight) {
+            this.hostComponent.setPreferredSize(this.rootBox.getWidth(), this.rootBox.getHeight());
+        }
+        
+        Graphics g = this.hostComponent.createDefaultGraphics();
+        LayoutContext context = this.createLayoutContext(g);
+        this.caret = this.rootBox.getCaret(context, this.getCaretOffset());
+        g.dispose();
+
+        if (this.isDebugging()) {
+            long end = System.currentTimeMillis();
+            System.out.println("VexWidget layout took " + (end - start) + "ms");
+        }
+    }
+
+    /**
+     * Re-layout the entire widget, due to either a layout width change or a
+     * stylesheet range. This method does the actual setting of the width
+     * and stylesheet, since it needs to know where the caret is <i>before</i>
+     * the change, so that it can do a reasonable job of restoring the
+     * position of the viewport after the change.
+     * 
+     * @param newWidth New width for the widget.
+     * @param newStyleSheet New stylesheet for the widget.
+     */
+    private void relayoutAll(int newWidth, StyleSheet newStyleSheet) {
+        
+        Graphics g = this.hostComponent.createDefaultGraphics();
+        LayoutContext context = this.createLayoutContext(g);
+
+        Rectangle viewport = this.hostComponent.getViewport();
+        
+        // true if the caret is within the viewport
+        //
+        // TODO: incorrect if caret near the bottom and the viewport is shrinking
+        // To fix, we probably need to save the viewport height, just like
+        // we now store viewport width (as layout width).
+        boolean caretVisible = viewport.intersects(this.caret.getBounds());
+        
+        // distance from the top of the viewport to the top of the caret
+        // use this if the caret is visible in the viewport
+        int relCaretY = 0;
+        
+        // offset around which we are laying out
+        // this is also where we put the top of the viewport if the caret
+        // isn't visible
+        int offset;
+        
+        if (caretVisible) {
+            relCaretY = this.caret.getY() - viewport.getY();
+            offset = this.getCaretOffset();
+        } else {
+            offset = this.rootBox.viewToModel(context, 0, viewport.getY());
+        }
+
+        this.layoutWidth = newWidth;
+        this.styleSheet = newStyleSheet;
+
+        // Re-create the context, since it holds the old stylesheet
+        context = this.createLayoutContext(g);
+
+        this.createRootBox();
+
+        this.iterateLayout(offset);
+
+        this.hostComponent.setPreferredSize(this.rootBox.getWidth(), this.rootBox.getHeight());
+
+        this.caret = this.rootBox.getCaret(context, this.getCaretOffset());
+
+        if (caretVisible) {
+            int viewportY = this.caret.getY() - Math.min(relCaretY, viewport.getHeight());
+            viewportY = Math.min(this.rootBox.getHeight() - viewport.getHeight(), viewportY);
+            viewportY = Math.max(0, viewportY); // this must appear after the above line, since
+                                                // that line might set viewportY negative
+            this.hostComponent.scrollTo(viewport.getX(), viewportY);
+            this.scrollCaretVisible();
+        } else {
+            int viewportY = this.rootBox.getCaret(context, offset).getY();
+            this.hostComponent.scrollTo(viewport.getX(), viewportY);
+        }
+        
+        this.hostComponent.repaint();
+        
+        g.dispose();
+        
+    }
+    
+    
+    /**
+     * Repaints the area of the caret.
+     */
+    private void repaintCaret() {
+        if (this.caret != null) {
+            // caret may be null when document is first set
+            Rectangle bounds = this.caret.getBounds();
+            this.hostComponent.repaint(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight());
+        }
+    }
+
+
+    /**
+     * Repaints area of the control corresponding to a range of
+     * offsets in the document.
+     *
+     * @param startOffset Starting offset of the range.
+     * @param endOffset Ending offset of the range.
+     */
+    private void repaintRange(int startOffset, int endOffset) {
+
+        Graphics g = this.hostComponent.createDefaultGraphics();
+
+        LayoutContext context = this.createLayoutContext(g);
+
+        Rectangle startBounds = this.rootBox.getCaret(context, startOffset).getBounds();
+        int top1 = startBounds.getY();
+        int bottom1 = top1 + startBounds.getHeight();
+
+        Rectangle endBounds = this.rootBox.getCaret(context, endOffset).getBounds();
+        int top2 = endBounds.getY();
+        int bottom2 = top2 + endBounds.getHeight();
+
+        int top = Math.min(top1, top2);
+        int bottom = Math.max(bottom1, bottom2);
+        if (top == bottom) {
+            // Account for zero-height horizontal carets
+            this.hostComponent.repaint(0, top - 1, this.getLayoutWidth(), bottom - top + 1);
+        } else {
+            this.hostComponent.repaint(0, top, this.getLayoutWidth(), bottom - top);
+        }
+        
+        g.dispose();
+    }
+
+    private void scrollCaretVisible() {
+
+        Rectangle caretBounds = this.caret.getBounds();
+        Rectangle viewport = this.hostComponent.getViewport();
+        
+        int x = viewport.getX();
+        int y = 0;
+        int offset = getCaretOffset();
+        if (offset == 1) {
+            y = 0;
+        } else if (offset == getDocument().getLength() - 1) {
+            if (this.rootBox.getHeight() < viewport.getHeight()) {
+                y = 0;
+            } else {
+                y = this.rootBox.getHeight() - viewport.getHeight();
+            }
+        } else if (caretBounds.getY() < viewport.getY()) {
+            y = caretBounds.getY();
+        } else if (caretBounds.getY() + caretBounds.getHeight() > viewport.getY() + viewport.getHeight()) {
+            y = caretBounds.getY() + caretBounds.getHeight() - viewport.getHeight();
+        } else {
+            // no scrolling required
+            return;
+        }
+        this.hostComponent.scrollTo(x, y);
+    }
+    
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/.classpath b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/.classpath
new file mode 100644
index 0000000..065ac06
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/.cvsignore b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/.cvsignore
new file mode 100644
index 0000000..81a1741
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/.cvsignore
@@ -0,0 +1,3 @@
+bin
+net.sf.vex.editor_1.0.0.jar
+vex-editor.jar
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/.options b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/.options
new file mode 100644
index 0000000..cbfc754
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/.options
@@ -0,0 +1,3 @@
+net.sf.vex.editor/debug=false
+net.sf.vex.editor/debug/config=false
+net.sf.vex.editor/debug/layout=false
\ No newline at end of file
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/.project b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/.project
new file mode 100644
index 0000000..6cf47f5
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/.project
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>org.eclipse.wst.xml.vex.ui</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>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.pde.PluginNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/.settings/org.eclipse.jdt.core.prefs b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..ca94a57
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,12 @@
+#Sun Jul 15 01:10:57 CEST 2007
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=disabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.2
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.4
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=warning
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=warning
+org.eclipse.jdt.core.compiler.source=1.3
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/.settings/org.eclipse.jdt.ui.prefs b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/.settings/org.eclipse.jdt.ui.prefs
new file mode 100644
index 0000000..8c2a508
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/.settings/org.eclipse.jdt.ui.prefs
@@ -0,0 +1,3 @@
+#Sun Jul 15 01:23:59 CEST 2007
+eclipse.preferences.version=1
+internal.default.compliance=default
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/.template b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/.template
new file mode 100644
index 0000000..d65e0f4
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/.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.ui/META-INF/MANIFEST.MF b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..6a53f77
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/META-INF/MANIFEST.MF
@@ -0,0 +1,24 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: %plugin.name
+Bundle-Version: 0.5.0.qualifier
+Bundle-Activator: org.eclipse.wst.xml.vex.ui.internal.VexPlugin
+Bundle-Vendor: %Bundle-Vendor.0
+Bundle-SymbolicName: org.eclipse.wst.xml.vex.ui;singleton:=true
+Require-Bundle: org.eclipse.core.runtime,
+ org.eclipse.ui,
+ org.eclipse.core.expressions,
+ org.eclipse.ui.views,
+ org.eclipse.core.resources,
+ org.eclipse.ui.ide,
+ org.eclipse.ui.editors,
+ org.junit,
+ org.eclipse.wst.xml.vex.core;bundle-version="0.5.0"
+Export-Package: org.eclipse.wst.xml.vex.ui.internal;x-internal:=true,
+ org.eclipse.wst.xml.vex.ui.internal.action;x-internal:=true,
+ org.eclipse.wst.xml.vex.ui.internal.config;x-internal:=true,
+ org.eclipse.wst.xml.vex.ui.internal.editor;x-internal:=true
+Bundle-Localization: plugin
+Eclipse-LazyStart: true
+
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/build.properties b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/build.properties
new file mode 100644
index 0000000..c1d4e83
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/build.properties
@@ -0,0 +1,16 @@
+bin.includes = plugin.xml,\
+               vex-editor.jar,\
+               *.gif,\
+               lgpl.txt,\
+               .,\
+               plugin_fr.properties,\
+               plugin.properties,\
+               icons/,\
+               META-INF/,\
+               schema/,\
+               bin/
+source.vex-editor.jar = src/
+output.vex-editor.jar = bin/
+source.. = src/
+jars.compile.order = .,\
+                     vex-editor.jar
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/design.xml b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/design.xml
new file mode 100644
index 0000000..44958df
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/design.xml
@@ -0,0 +1,163 @@
+<?xml version='1.0'?>
+<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN" "http://www.oasis-open.org/docbook/xml/4.3b2/docbookx.dtd">
+<article>
+
+  <articleinfo>
+
+    <title>Vex Editor Design</title>
+
+    <author><firstname>John</firstname><surname>Krasnay</surname></author>
+
+    <revhistory>
+
+      <revision><revnumber>0.1</revnumber><date>2005-01-06</date><authorinitials>jk</authorinitials><revdescription><para>Initial 
+      version.</para></revdescription></revision>
+
+    </revhistory>
+
+  </articleinfo>
+
+  <section>
+
+    <title>Introduction</title>
+
+    <para>This document describes the internal design of the Vex XML 
+    Editor. The components described here describe the higher-level UI 
+    implemented on top of the Eclipse Platform. Internals of the 
+    VexWidget are described in the design document for the <literal>
+    vex-toolkit</literal> module.</para>
+
+  </section>
+
+  <section>
+
+    <title>Configuration Items</title>
+
+    <para>Vex is an extensible platform for editing XML-based content. 
+    Because Vex is based on the Eclipse Platform, it leverages the 
+    powerful Eclipse extension point mechanism, and extensions to Vex 
+    are indeed implemented as Eclipse extensions. See 
+    <xref linkend="extensionpoints"/> for a list of the supported 
+    extension points. Typically, Vex plugins consist of declarative 
+    information such as DTD files, CSS files, and data in the <filename>
+    plugin.xml</filename> file, and can usually be implemented with 
+    little or no Java code. Extensions implementing Vex features such 
+    as new document types and styles are hereafter referred to as 
+    "configuration items".</para>
+
+    <para>One problem with Eclipse extensions, however, is that that 
+    they must be installed prior to running the executable. Within the 
+    Eclipse Plug-in Development Environment, plugins are tested by 
+    running another instance of the workbench, the Runtime Workbench. 
+    While this makes sense for Java-based plugins, it is awkward 
+    overkill for the declarative-style plugins used to extend 
+    Vex.</para>
+
+    <para>To solve this problem, we have implemented (as of v1.2) the 
+    concept of Vex Plugin Projects. A Vex Plugin Project is simply a 
+    project with the nature <literal>
+    net.sf.vex.editor.pluginNature</literal> and configured with the 
+    Vex Plugin Project Builder. When asked to build a Vex Plugin 
+    Project, the builder scans the file <filename>
+    vex-plugin.xml</filename> looking for extensions implementing Vex 
+    configuration items. Any related resources, such as a document 
+    type's DTD, are parsed. If all is well, each configuration item is 
+    registered with the VexPlugin and is available for immediate use. 
+    Most Vex configuration items (those that do not involve Java code) 
+    can therefore be created, tested, and modified from within Vex 
+    itself, without deferring to a runtime workbench.</para>
+
+    <sidebar>
+
+      <title>Why vex-plugin.xml?</title>
+
+      <para>In the Eclipse SDK, the resource name 
+      <filename>plugin.xml</filename> is mapped to the Eclipse Plug-in 
+      Manifest Editor. In the future, we would like to provide a 
+      special editor for Vex configuration items. By using a different 
+      filename, we can establish this binding without disturbing the 
+      default binding for <filename>plugin.xml</filename>.</para>
+
+    </sidebar>
+
+    <section>
+
+      <title>Data Model</title>
+
+      <para>Each Eclipse plug-in that defines Vex configuration items, 
+      and each Vex plugin project, is associated with an instance of 
+      <classname>net.sf.vex.editor.config.VexConfiguration</classname>, 
+      which contains all configuration items defined by that plugin or 
+      project. Each Vex configuration has a unique identifier, which 
+      for Eclipse plugins is the plugin identifier. The Vex plugin 
+      class, <classname>net.sf.vex.editor.VexPlugin</classname>, 
+      maintains a registry of <classname>VexConfiguration</classname> 
+      objects.</para>
+
+      <para>Configuration items are defined as subclasses of 
+      <classname>net.sf.vex.editor.ConfigItem</classname>, and are 
+      essentially data objects with properties defining the item, for 
+      example, the name and public ID of a document type. Configuration 
+      items are usually associated with a resource; the 
+      <classname>ConfigItem</classname> class tracks the path of this 
+      resource, relative to the defining project/plugin, in the 
+      <literal>resourceUri</literal> property.</para>
+
+    </section>
+
+    <section>
+
+      <title>Configuration Item Factories</title>
+
+      <para>Each type of configuration item is associated with a 
+      configuration item factory, a class that implements 
+      <classname>net.sf.vex.editor.config.IConfigItemFactory</classname>. 
+      The factory has the following responsibilities.</para>
+
+      <itemizedlist>
+
+        <listitem>
+
+          <para>To instantiate and initialize a new configuration item 
+          given an array of 
+          <classname>org.eclipse.core.runtime.IConfigurationElement</classname> 
+          objects. These objects represent the contents of an extension 
+          in <filename>plugin.xml</filename> or 
+          <filename>vex-plugin.xml</filename>.</para>
+
+        </listitem>
+
+        <listitem>
+
+          <para>To parse the resources associated with the 
+          configuration item.</para>
+
+        </listitem>
+
+        <listitem>
+
+          <para>To list the file extensions associated with resources 
+          associated with that kind of configuration item.</para>
+
+        </listitem>
+
+      </itemizedlist>
+
+      <para>For example, the <classname>DoctypeFactory</classname> 
+      produces instances of the <classname>DocumentType</classname> 
+      class, parses DTD files, and specifies that it handles re</para>
+
+      <para />
+
+    </section>
+
+  </section>
+
+  <section>
+
+    <title id="extensionpoints">Extension Points</title>
+
+  </section>
+
+</article>
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/icons/error_co.gif b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/icons/error_co.gif
new file mode 100644
index 0000000..119dccc
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/icons/error_co.gif
Binary files differ
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/icons/vex16.gif b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/icons/vex16.gif
new file mode 100644
index 0000000..a32605a
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/icons/vex16.gif
Binary files differ
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/icons/vex8.gif b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/icons/vex8.gif
new file mode 100644
index 0000000..96ef558
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/icons/vex8.gif
Binary files differ
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/icons/vex8.xcf b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/icons/vex8.xcf
new file mode 100644
index 0000000..a38000d
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/icons/vex8.xcf
Binary files differ
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/icons/warning_co.gif b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/icons/warning_co.gif
new file mode 100644
index 0000000..ee2dac4
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/icons/warning_co.gif
Binary files differ
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/lib/commons-beanutils-1.6.jar b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/lib/commons-beanutils-1.6.jar
new file mode 100644
index 0000000..795655a
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/lib/commons-beanutils-1.6.jar
Binary files differ
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/lib/commons-collections-2.1.jar b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/lib/commons-collections-2.1.jar
new file mode 100644
index 0000000..f66c6d2
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/lib/commons-collections-2.1.jar
Binary files differ
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/lib/commons-digester-1.4.1.jar b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/lib/commons-digester-1.4.1.jar
new file mode 100644
index 0000000..312be02
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/lib/commons-digester-1.4.1.jar
Binary files differ
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/lib/commons-logging-1.0.3.jar b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/lib/commons-logging-1.0.3.jar
new file mode 100644
index 0000000..b99c937
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/lib/commons-logging-1.0.3.jar
Binary files differ
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/lib/commons-logging-api-1.0.3.jar b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/lib/commons-logging-api-1.0.3.jar
new file mode 100644
index 0000000..209bcdf
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/lib/commons-logging-api-1.0.3.jar
Binary files differ
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/plugin.properties b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/plugin.properties
new file mode 100644
index 0000000..a266962
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/plugin.properties
@@ -0,0 +1,76 @@
+#
+# Language-specific strings in plugin.xml
+#
+
+plugin.name=Vex XML Editor
+
+BuildProblemDecorator.name=Vex Plug-in Build Problem Decorator
+ConfigurationView.name=Vex Configuration
+DebugView.name=Vex Debug
+DoctypePropertyPage.name=Vex Document Type
+DocumentPerspective.name=Document
+NewDocumentWizard.name=Document
+NewDocumentWizard.desc=Create an XML document using the Vex editor.
+NewPluginProjectWizard.name=Vex Plug-in Project
+NewPluginProjectWizard.desc=Create a new Vex Plug-in project.
+NewWizardCategory.name=Vex
+PluginProjectBuilder.name=Vex Plug-in Builder
+PluginProjectDecorator.name=Vex Plug-in Project Decorator
+PluginProjectNature.name=Vex Plugin Project Nature
+StylePropertyPage.name=Vex Style
+VexCommandCategory.name=Vex XML Editor
+VexEditor.name=Vex XML Editor
+VexEditorContext.name=Editing XML Documents
+VexViewCategory.name=Vex
+
+extensionPoint.doctypes=Vex Document Types
+extensionPoint.styles=Vex Styles
+
+documentActions.label=XML Editing Actions
+
+documentMenu.label=&Document
+insertMenu.label=&Insert
+tableMenu.label=&Table
+rowMenu.label=&Row
+columnMenu.label=&Column
+
+#
+# Actions
+#
+# NOTE: THESE MUST BE KEPT IN SYNC WITH THE SAME BLOCK IN 
+#       src/net/sf/vex/editor/messages.properties
+#
+# Note: when there are multiple labels for an action, the one named
+# Xxx.label is the command listed in the key bindings preference page.
+#
+ChangeElementAction.label=Change Element
+ChangeElementAction.dynamic.label=&Change <{0}> to...
+ChangeElementAction.menu.label=&Change Element...
+DeleteColumnAction.label=Delete Column
+DeleteRowAction.label=Delete Row
+DuplicateSelectionAction.label=&Duplicate Selection
+InsertColumnAfterAction.label=Insert Column After
+InsertColumnBeforeAction.label=Insert Column Before
+InsertElementAction.label=Insert Element
+InsertElementAction.contextmenu.label=Insert Element...
+InsertElementAction.mainmenu.label=&Element...
+InsertRowAboveAction.label=Insert Row Above
+InsertRowBelowAction.label=Insert Row Below
+MoveColumnLeftAction.label=Move Column Left
+MoveColumnRightAction.label=Move Column Right
+MoveRowDownAction.label=Move Row Down
+MoveRowUpAction.label=Move Row Up
+NextTableCellAction.label=Next Table Cell
+PasteTextAction.label=Paste Text
+PreviousTableCellAction.label=Previous Table Cell
+RemoveElementAction.label=Remove Element
+RemoveElementAction.dynamic.label=Remove <{0}>
+RestoreLastSelectionAction.label=Restore Last Selection
+SplitAction.label=Split Block Element
+SplitItemAction.label=Split Item
+
+#
+# End of Actions
+#
+
+Bundle-Vendor.0 = vex.sf.net
\ No newline at end of file
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/plugin.xml b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/plugin.xml
new file mode 100644
index 0000000..96c79f5
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/plugin.xml
@@ -0,0 +1,444 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?eclipse version="3.2"?>
+<plugin>
+
+   <extension-point id="doctypes" name="%extensionPoint.doctypes" schema="schema/doctype.exsd"/>
+   <extension-point id="styles" name="%extensionPoint.styles" schema="schema/style.exsd"/>
+
+   <extension
+         point="org.eclipse.ui.actionSets">
+      <actionSet id="org.eclipse.wst.xml.vex.ui.documentActions"
+            label="%documentActions.label"
+            visible="true">
+         <menu id="org.eclipse.wst.xml.vex.ui.documentMenu"
+               label="%documentMenu.label"
+               path="additions">
+            <separator name="insert"/>
+            <separator name="modify"/>
+            <separator name="table"/>
+         </menu>
+         <menu id="org.eclipse.wst.xml.vex.ui.insertMenu"
+               label="%insertMenu.label"
+               path="org.eclipse.wst.xml.vex.ui.documentMenu/insert">
+            <separator name="items"/>
+         </menu>
+         <menu id="org.eclipse.wst.xml.vex.ui.columnMenu"
+               label="%columnMenu.label"
+               path="org.eclipse.wst.xml.vex.ui.documentMenu/table">
+            <separator name="items"/>
+         </menu>
+         <menu id="org.eclipse.wst.xml.vex.ui.rowMenu"
+               label="%rowMenu.label"
+               path="org.eclipse.wst.xml.vex.ui.documentMenu/table">
+            <separator name="items"/>
+         </menu>
+               
+         <action id="net.sf.vex.action.PasteTextAction"
+                 label="%PasteTextAction.label"
+                 menubarPath="edit/cut.ext"
+                 class="org.eclipse.wst.xml.vex.ui.internal.editor.VexActionDelegate">
+         </action>
+         <action id="net.sf.vex.action.RestoreLastSelectionAction"
+                 label="%RestoreLastSelectionAction.label"
+                 menubarPath="edit/editEnd"
+                 class="org.eclipse.wst.xml.vex.ui.internal.editor.VexActionDelegate">
+         </action>
+
+         <action id="org.eclipse.wst.xml.vex.ui.action.InsertElementAction"
+                 label="%InsertElementAction.mainmenu.label"
+                 menubarPath="org.eclipse.wst.xml.vex.ui.documentMenu/org.eclipse.wst.xml.vex.ui.insertMenu/items"
+                 class="org.eclipse.wst.xml.vex.ui.internal.editor.VexActionDelegate">
+         </action>
+
+         <action id="net.sf.vex.action.DuplicateSelectionAction"
+                 label="%DuplicateSelectionAction.label"
+                 menubarPath="org.eclipse.wst.xml.vex.ui.documentMenu/modify"
+                 class="org.eclipse.wst.xml.vex.ui.internal.editor.VexActionDelegate"
+                 definitionId="net.sf.vex.action.DuplicateSelectionAction">
+         </action>
+         <action id="net.sf.vex.action.RemoveElementAction"
+                 label="%RemoveElementAction.label"
+                 menubarPath="org.eclipse.wst.xml.vex.ui.documentMenu/modify"
+                 class="org.eclipse.wst.xml.vex.ui.internal.editor.VexActionDelegate"
+                 definitionId="net.sf.vex.action.RemoveElementAction">
+         </action>
+         <action id="org.eclipse.wst.xml.vex.ui.action.ChangeElementAction"
+                 label="%ChangeElementAction.menu.label"
+                 menubarPath="org.eclipse.wst.xml.vex.ui.documentMenu/modify"
+                 class="org.eclipse.wst.xml.vex.ui.internal.editor.VexActionDelegate">
+         </action>
+
+         <action id="net.sf.vex.action.MoveRowDownAction"
+                 label="%MoveRowDownAction.label"
+                 menubarPath="org.eclipse.wst.xml.vex.ui.documentMenu/org.eclipse.wst.xml.vex.ui.rowMenu/items"
+                 class="org.eclipse.wst.xml.vex.ui.internal.editor.VexActionDelegate">
+         </action>
+         <action id="net.sf.vex.action.MoveRowUpAction"
+                 label="%MoveRowUpAction.label"
+                 menubarPath="org.eclipse.wst.xml.vex.ui.documentMenu/org.eclipse.wst.xml.vex.ui.rowMenu/items"
+                 class="org.eclipse.wst.xml.vex.ui.internal.editor.VexActionDelegate">
+         </action>
+         <action id="net.sf.vex.action.DeleteRowAction"
+                 label="%DeleteRowAction.label"
+                 menubarPath="org.eclipse.wst.xml.vex.ui.documentMenu/org.eclipse.wst.xml.vex.ui.rowMenu/items"
+                 class="org.eclipse.wst.xml.vex.ui.internal.editor.VexActionDelegate">
+         </action>
+         <action id="net.sf.vex.action.InsertRowBelowAction"
+                 label="%InsertRowBelowAction.label"
+                 menubarPath="org.eclipse.wst.xml.vex.ui.documentMenu/org.eclipse.wst.xml.vex.ui.rowMenu/items"
+                 class="org.eclipse.wst.xml.vex.ui.internal.editor.VexActionDelegate">
+         </action>
+         <action id="net.sf.vex.action.InsertRowAboveAction"
+                 label="%InsertRowAboveAction.label"
+                 menubarPath="org.eclipse.wst.xml.vex.ui.documentMenu/org.eclipse.wst.xml.vex.ui.rowMenu/items"
+                 class="org.eclipse.wst.xml.vex.ui.internal.editor.VexActionDelegate">
+         </action>
+
+         <action id="net.sf.vex.action.MoveColumnRightAction"
+                 label="%MoveColumnRightAction.label"
+                 menubarPath="org.eclipse.wst.xml.vex.ui.documentMenu/org.eclipse.wst.xml.vex.ui.columnMenu/items"
+                 class="org.eclipse.wst.xml.vex.ui.internal.editor.VexActionDelegate">
+         </action>
+         <action id="net.sf.vex.action.MoveColumnLeftAction"
+                 label="%MoveColumnLeftAction.label"
+                 menubarPath="org.eclipse.wst.xml.vex.ui.documentMenu/org.eclipse.wst.xml.vex.ui.columnMenu/items"
+                 class="org.eclipse.wst.xml.vex.ui.internal.editor.VexActionDelegate">
+         </action>
+         <action id="net.sf.vex.action.DeleteColumnAction"
+                 label="%DeleteColumnAction.label"
+                 menubarPath="org.eclipse.wst.xml.vex.ui.documentMenu/org.eclipse.wst.xml.vex.ui.columnMenu/items"
+                 class="org.eclipse.wst.xml.vex.ui.internal.editor.VexActionDelegate">
+         </action>
+         <action id="net.sf.vex.action.InsertColumnAfterAction"
+                 label="%InsertColumnAfterAction.label"
+                 menubarPath="org.eclipse.wst.xml.vex.ui.documentMenu/org.eclipse.wst.xml.vex.ui.columnMenu/items"
+                 class="org.eclipse.wst.xml.vex.ui.internal.editor.VexActionDelegate">
+         </action>
+         <action id="net.sf.vex.action.InsertColumnBeforeAction"
+                 label="%InsertColumnBeforeAction.label"
+                 menubarPath="org.eclipse.wst.xml.vex.ui.documentMenu/org.eclipse.wst.xml.vex.ui.columnMenu/items"
+                 class="org.eclipse.wst.xml.vex.ui.internal.editor.VexActionDelegate">
+         </action>
+
+      </actionSet>
+   </extension>
+   <extension
+         point="org.eclipse.ui.editors">
+      <editor
+            name="%VexEditor.name"
+            extensions="htm,html,xhtml,xml,pml"
+            icon="vex16.gif"
+            class="org.eclipse.wst.xml.vex.ui.internal.editor.VexEditor"
+            contributorClass="org.eclipse.wst.xml.vex.ui.internal.editor.VexActionBarContributor"
+            id="org.eclipse.wst.xml.vex.ui.VexEditor">
+      </editor>
+   </extension>
+   <extension
+         point="org.eclipse.ui.newWizards">
+      <category
+            name="%NewWizardCategory.name"
+            id="net.sf.vex.NewWizardCategory">
+      </category>
+      <wizard
+            name="%NewDocumentWizard.name"
+            icon="vex16.gif"
+            category="net.sf.vex.NewWizardCategory"
+            class="org.eclipse.wst.xml.vex.ui.internal.editor.NewDocumentWizard"
+            id="org.eclipse.wst.xml.vex.ui.NewDocumentWizard">
+         <description>%NewDocumentWizard.desc</description>
+      </wizard>
+   </extension>
+   <extension
+         point="org.eclipse.ui.perspectives">
+      <perspective
+            name="%DocumentPerspective.name"
+            icon="vex16.gif"
+            class="org.eclipse.wst.xml.vex.ui.internal.editor.DocumentPerspective"
+            id="org.eclipse.wst.xml.vex.ui.DocumentPerspective">
+      </perspective>
+   </extension>
+   <extension
+         point="org.eclipse.ui.contexts">
+      <context
+            name="%VexEditorContext.name"
+            id="org.eclipse.wst.xml.vex.ui.VexEditorContext"
+            parentId="org.eclipse.ui.textEditorScope">
+      </context>
+   </extension>
+   <extension
+         point="org.eclipse.ui.commands">
+      <category
+            name="%VexCommandCategory.name"
+            id="org.eclipse.wst.xml.vex.ui.VexCommandCategory">
+      </category>
+      <command
+            name="%ChangeElementAction.label"
+            categoryId="org.eclipse.wst.xml.vex.ui.VexCommandCategory"
+            id="net.sf.vex.action.ChangeElementAction">
+      </command>
+      <command
+            name="%DeleteColumnAction.label"
+            categoryId="org.eclipse.wst.xml.vex.ui.VexCommandCategory"
+            id="net.sf.vex.action.DeleteColumnAction">
+      </command>
+      <command
+            name="%DeleteRowAction.label"
+            categoryId="org.eclipse.wst.xml.vex.ui.VexCommandCategory"
+            id="net.sf.vex.action.DeleteRowAction">
+      </command>
+      <command
+            name="%DuplicateSelectionAction.label"
+            categoryId="org.eclipse.wst.xml.vex.ui.VexCommandCategory"
+            id="net.sf.vex.action.DuplicateSelectionAction">
+      </command>
+      <command
+            name="%InsertColumnAfterAction.label"
+            categoryId="org.eclipse.wst.xml.vex.ui.VexCommandCategory"
+            id="net.sf.vex.action.InsertColumnAfterAction">
+      </command>
+      <command
+            name="%InsertColumnBeforeAction.label"
+            categoryId="org.eclipse.wst.xml.vex.ui.VexCommandCategory"
+            id="net.sf.vex.action.InsertColumnBeforeAction">
+      </command>
+      <command
+            name="%InsertElementAction.label"
+            categoryId="org.eclipse.wst.xml.vex.ui.VexCommandCategory"
+            id="org.eclipse.wst.xml.vex.ui.action.InsertElementAction">
+      </command>
+      <command
+            name="%InsertRowAboveAction.label"
+            categoryId="org.eclipse.wst.xml.vex.ui.VexCommandCategory"
+            id="net.sf.vex.action.InsertRowAboveAction">
+      </command>
+      <command
+            name="%InsertRowBelowAction.label"
+            categoryId="org.eclipse.wst.xml.vex.ui.VexCommandCategory"
+            id="net.sf.vex.action.InsertRowBelowAction">
+      </command>
+      <command
+            name="%MoveColumnLeftAction.label"
+            categoryId="org.eclipse.wst.xml.vex.ui.VexCommandCategory"
+            id="net.sf.vex.action.MoveColumnLeftAction">
+      </command>
+      <command
+            name="%MoveColumnRightAction.label"
+            categoryId="org.eclipse.wst.xml.vex.ui.VexCommandCategory"
+            id="net.sf.vex.action.MoveColumnRightAction">
+      </command>
+      <command
+            name="%MoveRowDownAction.label"
+            categoryId="org.eclipse.wst.xml.vex.ui.VexCommandCategory"
+            id="net.sf.vex.action.MoveRowDownAction">
+      </command>
+      <command
+            name="%MoveRowUpAction.label"
+            categoryId="org.eclipse.wst.xml.vex.ui.VexCommandCategory"
+            id="net.sf.vex.action.MoveRowUpAction">
+      </command>
+      <command
+            name="%NextTableCellAction.label"
+            categoryId="org.eclipse.wst.xml.vex.ui.VexCommandCategory"
+            id="net.sf.vex.action.NextTableCellAction">
+      </command>
+      <command
+            name="%PasteTextAction.label"
+            categoryId="org.eclipse.wst.xml.vex.ui.VexCommandCategory"
+            id="net.sf.vex.action.PasteTextAction">
+      </command>
+      <command
+            name="%PreviousTableCellAction.label"
+            categoryId="org.eclipse.wst.xml.vex.ui.VexCommandCategory"
+            id="net.sf.vex.action.PreviousTableCellAction">
+      </command>
+      <command
+            name="%RemoveElementAction.label"
+            categoryId="org.eclipse.wst.xml.vex.ui.VexCommandCategory"
+            id="net.sf.vex.action.RemoveElementAction">
+      </command>
+      <command
+            name="%RestoreLastSelectionAction.label"
+            categoryId="org.eclipse.wst.xml.vex.ui.VexCommandCategory"
+            id="net.sf.vex.action.RestoreLastSelectionAction">
+      </command>
+      <command
+            name="%SplitAction.label"
+            categoryId="org.eclipse.wst.xml.vex.ui.VexCommandCategory"
+            id="net.sf.vex.action.SplitAction">
+      </command>
+      <command
+            name="%SplitItemAction.label"
+            categoryId="org.eclipse.wst.xml.vex.ui.VexCommandCategory"
+            id="net.sf.vex.action.SplitItemAction">
+      </command>
+      
+   </extension>
+   <extension
+         point="org.eclipse.ui.views">
+      <category
+            name="%VexViewCategory.name"
+            id="net.sf.vex.views.VexViewCategory">
+      </category>
+      <view
+            name="%DebugView.name"
+            icon="vex16.gif"
+            category="net.sf.vex.views.VexViewCategory"
+            class="org.eclipse.wst.xml.vex.ui.internal.editor.DebugView"
+            id="net.sf.vex.views.debug">
+      </view>
+   </extension>
+   <extension
+         point="org.eclipse.ui.views">
+      <view
+            name="%ConfigurationView.name"
+            icon="vex16.gif"
+            category="net.sf.vex.views.VexViewCategory"
+            class="org.eclipse.wst.xml.vex.ui.internal.config.ConfigurationView"
+            id="net.sf.vex.views.configuration">
+      </view>
+   </extension>
+   <extension
+         id="pluginNature"
+         name="%PluginProjectNature.name"
+         point="org.eclipse.core.resources.natures">
+      <runtime>
+         <run
+               class="org.eclipse.wst.xml.vex.ui.internal.config.PluginProjectNature">
+         </run>
+      </runtime>
+   </extension>
+   <extension
+         point="org.eclipse.ui.newWizards">
+      <wizard
+            name="%NewPluginProjectWizard.name"
+            icon="icons/vex16.gif"
+            category="net.sf.vex.NewWizardCategory"
+            class="org.eclipse.wst.xml.vex.ui.internal.config.NewPluginProjectWizard"
+            project="true"
+            id="org.eclipse.wst.xml.vex.ui.config.NewPluginProjectWizard">
+         <description>%NewPluginProjectWizard.desc</description>
+      </wizard>
+   </extension>
+   <extension
+         id="pluginBuilder"
+         name="%PluginProjectBuilder.name"
+         point="org.eclipse.core.resources.builders">
+      <builder>
+         <run
+               class="org.eclipse.wst.xml.vex.ui.internal.config.PluginProjectBuilder">
+         </run>
+      </builder>
+   </extension>
+   <extension
+         point="org.eclipse.ui.decorators">
+      <decorator
+            lightweight="true"
+            label="%PluginProjectDecorator.name"
+            state="true"
+            class="org.eclipse.wst.xml.vex.ui.internal.config.PluginProjectDecorator"
+            id="org.eclipse.wst.xml.vex.ui.config.vexPluginProjectDecorator">
+         <enablement>
+            <objectClass
+                  name="org.eclipse.core.resources.IProject">
+            </objectClass>
+         </enablement>
+      </decorator>
+   </extension>
+   <extension
+         point="org.eclipse.ui.decorators">
+      <decorator
+            lightweight="true"
+            label="%BuildProblemDecorator.name"
+            state="true"
+            class="org.eclipse.wst.xml.vex.ui.internal.config.BuildProblemDecorator"
+            id="org.eclipse.wst.xml.vex.ui.config.buildProblemDecorator">
+         <enablement>
+            <objectClass
+                  name="org.eclipse.core.resources.IResource">
+            </objectClass>
+         </enablement>
+      </decorator>
+   </extension>
+   <extension
+         point="org.eclipse.ui.propertyPages">
+      <page
+            objectClass="org.eclipse.core.resources.IFile"
+            name="%DoctypePropertyPage.name"
+            class="org.eclipse.wst.xml.vex.ui.internal.config.DoctypePropertyPage"
+            nameFilter="*.dtd"
+            id="org.eclipse.wst.xml.vex.ui.config.DoctypePropertyPage">
+         <filter
+               name="projectNature"
+               value="org.eclipse.wst.xml.vex.ui.pluginNature">
+         </filter>
+      </page>
+   </extension>
+   <extension
+         point="org.eclipse.ui.propertyPages">
+      <page
+            objectClass="org.eclipse.core.resources.IFile"
+            name="%StylePropertyPage.name"
+            class="org.eclipse.wst.xml.vex.ui.internal.config.StylePropertyPage"
+            nameFilter="*.css"
+            id="org.eclipse.wst.xml.vex.ui.config.StylePropertyPage">
+         <filter
+               name="projectNature"
+               value="org.eclipse.wst.xml.vex.ui.pluginNature">
+         </filter>
+      </page>
+   </extension>
+   <extension
+         point="org.eclipse.ui.bindings">
+         
+      <key
+            commandId="net.sf.vex.action.ChangeElementAction"
+            contextId="org.eclipse.wst.xml.vex.ui.VexEditorContext"
+            sequence="Ctrl+Alt+Space"
+            schemeId="org.eclipse.ui.defaultAcceleratorConfiguration">
+      </key>
+      <key
+            commandId="net.sf.vex.action.DuplicateSelectionAction"
+            contextId="org.eclipse.wst.xml.vex.ui.VexEditorContext"
+            sequence="Ctrl+D"
+            schemeId="org.eclipse.ui.defaultAcceleratorConfiguration">
+      </key>
+      <key
+            commandId="org.eclipse.wst.xml.vex.ui.action.InsertElementAction"
+            contextId="org.eclipse.wst.xml.vex.ui.VexEditorContext"
+            sequence="Insert"
+            schemeId="org.eclipse.ui.defaultAcceleratorConfiguration">
+      </key>
+      <key
+            commandId="net.sf.vex.action.NextTableCellAction"
+            contextId="org.eclipse.wst.xml.vex.ui.VexEditorContext"
+            sequence="Tab"
+            schemeId="org.eclipse.ui.defaultAcceleratorConfiguration">
+      </key>
+      <key
+            commandId="net.sf.vex.action.PreviousTableCellAction"
+            contextId="org.eclipse.wst.xml.vex.ui.VexEditorContext"
+            sequence="Shift+Tab"
+            schemeId="org.eclipse.ui.defaultAcceleratorConfiguration">
+      </key>
+      <key
+            commandId="net.sf.vex.action.RemoveElementAction"
+            contextId="org.eclipse.wst.xml.vex.ui.VexEditorContext"
+            sequence="Ctrl+U"
+            schemeId="org.eclipse.ui.defaultAcceleratorConfiguration">
+      </key>
+      <key
+            commandId="net.sf.vex.action.SplitItemAction"
+            contextId="org.eclipse.wst.xml.vex.ui.VexEditorContext"
+            sequence="Shift+Enter"
+            schemeId="org.eclipse.ui.defaultAcceleratorConfiguration">
+      </key>
+      <key
+            commandId="net.sf.vex.action.SplitAction"
+            contextId="org.eclipse.wst.xml.vex.ui.VexEditorContext"
+            sequence="Enter"
+            schemeId="org.eclipse.ui.defaultAcceleratorConfiguration">
+      </key>
+
+   </extension>
+
+</plugin>
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/plugin_fr.properties b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/plugin_fr.properties
new file mode 100644
index 0000000..4c0a97f
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/plugin_fr.properties
@@ -0,0 +1,73 @@
+#

+# Language-specific strings in plugin.xml

+#

+

+plugin.name=Editeur XML Vex

+

+BuildProblemDecorator.name=Problème avec le décorateur du Plug-in Vex

+ConfigurationView.name=Configuration de Vex

+DebugView.name=Debogage de Vex

+DoctypePropertyPage.name=Vex Document Type

+DocumentPerspective.name=Document

+NewDocumentWizard.name=Document

+NewDocumentWizard.desc=Créer un document XML avec l'éditeur Vex

+NewPluginProjectWizard.name=Projet du plug-in Vex

+NewPluginProjectWizard.desc=Créer un nouveau projet du Vex Plug-in

+NewWizardCategory.name=Vex

+PluginProjectBuilder.name=Compilateur du plug-in Vex

+PluginProjectDecorator.name=Décorateur du projet Vex

+PluginProjectNature.name=Nature du projet Vex

+StylePropertyPage.name=Style Vex

+VexCommandCategory.name=Editeur XML Vex

+VexEditor.name=Editeur XML Vex

+VexEditorContext.name=Editer les documents XML

+VexViewCategory.name=Vex

+

+extensionPoint.doctypes=Document Types Vex

+extensionPoint.styles=Styles Vex

+

+documentActions.label=Actions d'édition de Vex

+

+documentMenu.label=&Document

+insertMenu.label=&Insérer

+tableMenu.label=&Table

+

+

+#

+# Actions

+#

+# NOTE: THESE MUST BE KEPT IN SYNC WITH THE SAME BLOCK IN 

+#       src/net/sf/vex/editor/messages.properties

+#

+# Note: when there are multiple labels for an action, the one named

+# Xxx.label is the command listed in the key bindings preference page.

+#

+ChangeElementAction.label=Changer Element

+ChangeElementAction.dynamic.label=&Changer <{0}> en...

+ChangeElementAction.menu.label=&Changer Element...

+DeleteColumnAction.label=Détruire la colonne

+DeleteRowAction.label=Détruire la ligne

+DuplicateSelectionAction.label=&Dupliquer la selection

+InsertColumnAfterAction.label=Insérer une colonne après

+InsertColumnBeforeAction.label=Insérer une colonne avant

+InsertElementAction.label=Insérer un élement

+InsertElementAction.contextmenu.label=Insérer un élément...

+InsertElementAction.mainmenu.label=&Element...

+InsertRowAboveAction.label=Insérer ligne au dessus

+InsertRowBelowAction.label=Insérer ligne en dessous

+MoveColumnLeftAction.label=Déplacer une colonne à gauche

+MoveColumnRightAction.label=Déplacer une colonne à droite

+MoveRowDownAction.label=Déplacer la ligne vers le bas

+MoveRowUpAction.label=Déplacer la ligne vers le haut

+NextTableCellAction.label=Prochaine Cellule de la table

+PasteTextAction.label=Coller le texte

+PreviousTableCellAction.label=Cellule précédente de la table

+RemoveElementAction.label=Supprimer l'élement

+RemoveElementAction.dynamic.label=Supprimer <{0}>

+RestoreLastSelectionAction.label=Restaurer la dernière selection

+SplitAction.label=Couper le bloc

+SplitItemAction.label=Couper l'item

+

+#

+# End of Actions

+#

diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/schema/doctype.exsd b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/schema/doctype.exsd
new file mode 100644
index 0000000..6285162
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/schema/doctype.exsd
@@ -0,0 +1,146 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!-- Schema file written by PDE -->
+<schema targetNamespace="vex-editor">
+<annotation>
+      <appInfo>
+         <meta.schema plugin="vex-editor" id="doctype" name="Vex DocType"/>
+      </appInfo>
+      <documentation>
+         Registers a new document type with Vex.
+      </documentation>
+   </annotation>
+
+   <element name="extension">
+      <complexType>
+         <sequence>
+            <element ref="doctype"/>
+         </sequence>
+         <attribute name="point" type="string" use="required">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
+            </annotation>
+         </attribute>
+         <attribute name="id" type="string">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
+            </annotation>
+         </attribute>
+         <attribute name="name" type="string">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
+            </annotation>
+         </attribute>
+      </complexType>
+   </element>
+
+   <element name="doctype">
+      <annotation>
+         <appInfo>
+            <meta.element labelAttribute="publicId"/>
+         </appInfo>
+      </annotation>
+      <complexType>
+         <sequence>
+            <element ref="rootElement" minOccurs="0" maxOccurs="unbounded"/>
+         </sequence>
+         <attribute name="publicId" type="string">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
+            </annotation>
+         </attribute>
+         <attribute name="systemId" type="string">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
+            </annotation>
+         </attribute>
+         <attribute name="dtd" type="string">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
+               <appInfo>
+                  <meta.attribute kind="resource"/>
+               </appInfo>
+            </annotation>
+         </attribute>
+         <attribute name="outlineProvider" type="string">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
+               <appInfo>
+                  <meta.attribute kind="java" basedOn="net.sf.vex.editor.IOutlineProvider"/>
+               </appInfo>
+            </annotation>
+         </attribute>
+      </complexType>
+   </element>
+
+   <element name="rootElement">
+      <complexType>
+         <attribute name="name" type="string">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
+            </annotation>
+         </attribute>
+      </complexType>
+   </element>
+
+   <annotation>
+      <appInfo>
+         <meta.section type="since"/>
+      </appInfo>
+      <documentation>
+         [Enter the first release in which this extension point appears.]
+      </documentation>
+   </annotation>
+
+   <annotation>
+      <appInfo>
+         <meta.section type="examples"/>
+      </appInfo>
+      <documentation>
+         [Enter extension point usage example here.]
+      </documentation>
+   </annotation>
+
+   <annotation>
+      <appInfo>
+         <meta.section type="apiInfo"/>
+      </appInfo>
+      <documentation>
+         [Enter API information here.]
+      </documentation>
+   </annotation>
+
+   <annotation>
+      <appInfo>
+         <meta.section type="implementation"/>
+      </appInfo>
+      <documentation>
+         [Enter information about supplied implementation of this extension point.]
+      </documentation>
+   </annotation>
+
+   <annotation>
+      <appInfo>
+         <meta.section type="copyright"/>
+      </appInfo>
+      <documentation>
+         
+      </documentation>
+   </annotation>
+
+</schema>
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/schema/style.exsd b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/schema/style.exsd
new file mode 100644
index 0000000..26a3933
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/schema/style.exsd
@@ -0,0 +1,117 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!-- Schema file written by PDE -->
+<schema targetNamespace="vex-editor">
+<annotation>
+      <appInfo>
+         <meta.schema plugin="vex-editor" id="style" name="Vex Style"/>
+      </appInfo>
+      <documentation>
+         Registers a new document type with Vex.
+      </documentation>
+   </annotation>
+
+   <element name="extension">
+      <complexType>
+         <sequence>
+            <element ref="style"/>
+         </sequence>
+         <attribute name="point" type="string" use="required">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
+            </annotation>
+         </attribute>
+         <attribute name="id" type="string">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
+            </annotation>
+         </attribute>
+         <attribute name="name" type="string">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
+            </annotation>
+         </attribute>
+      </complexType>
+   </element>
+
+   <element name="style">
+      <complexType>
+         <sequence>
+            <element ref="doctypeRef" minOccurs="1" maxOccurs="unbounded"/>
+         </sequence>
+         <attribute name="css" type="string">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
+               <appInfo>
+                  <meta.attribute kind="resource"/>
+               </appInfo>
+            </annotation>
+         </attribute>
+      </complexType>
+   </element>
+
+   <element name="doctypeRef">
+      <complexType>
+         <attribute name="publicId" type="string">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
+            </annotation>
+         </attribute>
+      </complexType>
+   </element>
+
+   <annotation>
+      <appInfo>
+         <meta.section type="since"/>
+      </appInfo>
+      <documentation>
+         [Enter the first release in which this extension point appears.]
+      </documentation>
+   </annotation>
+
+   <annotation>
+      <appInfo>
+         <meta.section type="examples"/>
+      </appInfo>
+      <documentation>
+         [Enter extension point usage example here.]
+      </documentation>
+   </annotation>
+
+   <annotation>
+      <appInfo>
+         <meta.section type="apiInfo"/>
+      </appInfo>
+      <documentation>
+         [Enter API information here.]
+      </documentation>
+   </annotation>
+
+   <annotation>
+      <appInfo>
+         <meta.section type="implementation"/>
+      </appInfo>
+      <documentation>
+         [Enter information about supplied implementation of this extension point.]
+      </documentation>
+   </annotation>
+
+   <annotation>
+      <appInfo>
+         <meta.section type="copyright"/>
+      </appInfo>
+      <documentation>
+         
+      </documentation>
+   </annotation>
+
+</schema>
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/VexPlugin.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/VexPlugin.java
new file mode 100644
index 0000000..8851513
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/VexPlugin.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.ui.internal;
+
+
+
+import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.ui.plugin.AbstractUIPlugin;
+import org.eclipse.wst.xml.vex.core.internal.core.DisplayDevice;
+import org.eclipse.wst.xml.vex.core.internal.swt.SwtDisplayDevice;
+import org.eclipse.wst.xml.vex.ui.internal.config.ConfigLoaderJob;
+import org.osgi.framework.BundleContext;
+
+/**
+ * The main plugin class to be used in the desktop.
+ */
+public class VexPlugin extends AbstractUIPlugin {
+    
+    // The plugin's id
+    public static final String ID = "org.eclipse.wst.xml.vex.ui"; //$NON-NLS-1$
+    
+    /**
+     * The constructor.
+     */
+    public VexPlugin() {
+
+        instance = this;
+        
+    }
+    
+    /**
+     * Asserts that this method is called from the display thread. If not,
+     * an IllegalStateException is thrown.
+     */
+    public static void assertIsDisplayThread() {
+        if (Thread.currentThread() != Display.getDefault().getThread()) {
+            throw new IllegalStateException("This method must be called from the display thread."); //$NON-NLS-1$
+        }
+    }
+    
+    /**
+     * Returns the shared instance.
+     */
+    public static VexPlugin getInstance() {
+        return instance;
+    }
+
+    /**
+     * Returns the workspace instance.
+     */
+    public static IWorkspace getWorkspace() {
+        return ResourcesPlugin.getWorkspace();
+    }
+
+    /**
+     * Log an error message without an exception.
+     * @param severity One of the IStatus severity levels, e.g. IStatus.ERROR.
+     * @param message Message describing the error.
+     */
+    public void log(int severity, String message) {
+        this.getLog().log(new Status(severity, ID, 0, message, null));
+    }
+    
+    /**
+     * Log an error message.
+     * @param severity One of the IStatus severity levels, e.g. IStatus.ERROR.
+     * @param message Message describing the error.
+     * @param exception Exception related to the error, or null of none.
+     */
+    public void log(int severity, String message, Throwable exception) {
+        this.getLog().log(new Status(severity, "org.eclipse.wst.xml.vex.ui", 0, message, exception)); //$NON-NLS-1$
+    }
+    
+    /**
+     * Override the plugin startup to intialize the resource tracker.
+     */
+    public void start(BundleContext bundleContext) throws Exception {
+
+        super.start(bundleContext);
+
+        // TODO Remove DisplayDevice.setCurrent from VexPlugin.start
+        // This has been added to the VexWidget ctor, but the problem is that
+        // when loading an editor, we load the document before creating the
+        // widget, and to do that we need to load the stylesheet, and *this*
+        // needs the DisplayDevice to be set properly.
+        //
+        // One solution might be to do a simplified stylesheet load that only
+        // looks at the display property, which is enough to do space
+        // normalization but doesn't need to look at the display device.
+    
+        DisplayDevice.setCurrent(new SwtDisplayDevice());
+
+//        boolean configDebug = this.isDebugging() &&
+//            "true".equalsIgnoreCase(Platform.getDebugOption(ID + "/debug/config"));
+    
+        this.initJob.schedule();
+
+    }
+
+    public void stop(BundleContext context) throws Exception {
+        super.stop(context);
+    }
+    
+    //========================================================= PRIVATE
+    
+    private static VexPlugin instance;
+    
+    private ConfigLoaderJob initJob = new ConfigLoaderJob();
+    
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/action/ChangeElementAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/action/ChangeElementAction.java
new file mode 100644
index 0000000..5f76571
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/action/ChangeElementAction.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.ui.internal.action;
+
+import org.eclipse.wst.xml.vex.core.internal.action.IVexAction;
+import org.eclipse.wst.xml.vex.core.internal.swt.VexWidget;
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+import org.eclipse.wst.xml.vex.ui.internal.editor.MorphAssistant;
+
+
+/**
+ * Displays the Change Element dialog.
+ */
+public class ChangeElementAction implements IVexAction {
+
+    public void run(IVexWidget vexWidget) {
+        new MorphAssistant().show((VexWidget) vexWidget);
+    }
+
+    public boolean isEnabled(IVexWidget vexWidget) {
+        return true;
+    }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/action/InsertElementAction.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/action/InsertElementAction.java
new file mode 100644
index 0000000..c5e7112
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/action/InsertElementAction.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.ui.internal.action;
+
+import org.eclipse.wst.xml.vex.core.internal.action.IVexAction;
+import org.eclipse.wst.xml.vex.core.internal.swt.VexWidget;
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+import org.eclipse.wst.xml.vex.ui.internal.editor.InsertAssistant;
+
+
+/**
+ * @author john
+ *
+ * TODO To change the template for this generated type comment go to
+ * Window - Preferences - Java - Code Style - Code Templates
+ */
+public class InsertElementAction implements IVexAction {
+
+    public void run(IVexWidget vexWidget) {
+        new InsertAssistant().show((VexWidget) vexWidget);
+    }
+
+    public boolean isEnabled(IVexWidget vexWidget) {
+        return true;
+    }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/action/VexActionAdapter.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/action/VexActionAdapter.java
new file mode 100644
index 0000000..9430f22
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/action/VexActionAdapter.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.ui.internal.action;
+
+
+import org.eclipse.jface.action.Action;
+import org.eclipse.wst.xml.vex.core.internal.action.IVexAction;
+import org.eclipse.wst.xml.vex.core.internal.swt.VexWidget;
+import org.eclipse.wst.xml.vex.ui.internal.editor.Messages;
+import org.eclipse.wst.xml.vex.ui.internal.editor.VexEditor;
+
+/**
+ * Adapts a JFace Action to an IVexAction instance. The ID and definition ID of 
+ * the resulting action is set to the classname of the action. Localized
+ * strings for the action are pulled from the classname of the given action. For example,
+ * if the action is "net.sf.vex.action.PasteTextAction", the following properties
+ * are retrieved from plugin.xml:
+ * 
+ * <dl>
+ *   <dt>PasteTextAction.label</dt>
+ *   <dd>The action's label.</dd>
+ *   <dt>PasteTextAction.tooltip</dt>
+ *   <dd>The action's tooltip.</dd>
+ * </dl>
+ */
+public class VexActionAdapter extends Action {
+
+    /**
+     * Class constructor.
+     * @param editor VexEditor to which the action will apply.
+     * @param action IVexAction to be invoked.
+     */
+    public VexActionAdapter(VexEditor editor, IVexAction action) {
+        
+        this.editor = editor;
+        this.action = action;
+        
+        String id = action.getClass().getName();
+        int i = id.lastIndexOf("."); //$NON-NLS-1$
+        String key = id.substring(i+1);
+
+        this.setId(id);
+        this.setActionDefinitionId(id);
+        this.setText(Messages.getString(key + ".label")); //$NON-NLS-1$
+        this.setToolTipText(Messages.getString(key + ".tooltip")); //$NON-NLS-1$
+    }
+
+    
+    public void run() {
+        VexWidget vexWidget = this.editor.getVexWidget();
+        if (vexWidget != null) {
+            this.action.run(vexWidget);
+        }
+    }
+    
+    private VexEditor editor;
+    private IVexAction action;
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/BuildProblem.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/BuildProblem.java
new file mode 100644
index 0000000..9f5b92c
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/BuildProblem.java
@@ -0,0 +1,111 @@
+/*******************************************************************************
+ * 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.ui.internal.config;
+
+/**
+ * Represents a problem found when parsing a configuration resource.
+ */
+public class BuildProblem {
+
+    public static final int SEVERITY_ERROR = 1;
+    public static final int SEVERITY_WARNING = 2;
+
+    /**
+     * Class constructor.
+     */
+    public BuildProblem() {
+        
+    }
+
+    /**
+     * Class constructor.
+     * @param severity Severity of the problem: SEVERITY_WARNING or SEVERITY_ERROR
+     * @param resourcePath Path of the resource being built, relative to its
+     * plugin or project.
+     * @param message Description of the problem.
+     * @param lineNumber Line number on which the problem occurred, or -1 if unknown.
+     */
+    public BuildProblem(int severity, String resourcePath, String message, int lineNumber) {
+        this.severity = severity;
+        this.resourcePath = resourcePath;
+        this.message = message;
+        this.lineNumber = lineNumber;
+    }
+    
+    /**
+     * Returns the line number on which the error occurred, or -1 if no
+     * line number can be identified.
+     */
+    public int getLineNumber() {
+        return lineNumber;
+    }
+    
+    /**
+     * Returns the message describing the problem.
+     */
+    public String getMessage() {
+        return message;
+    }
+
+    /**
+     * Returns the path of the resource relative to the project root.
+     */
+    public String getResourcePath() {
+        return resourcePath;
+    }
+
+    /**
+     * Returns the severity of the problem, either SEVERITY_ERROR or 
+     * SEVERITY_WARNING.
+     */
+    public int getSeverity() {
+        return severity;
+    }
+    
+    /**
+     * Sets the line number of the problem.
+     * @param lineNumber Line number on which the problem occurred.
+     */
+    public void setLineNumber(int lineNumber) {
+        this.lineNumber = lineNumber;
+    }
+    
+    /**
+     * Sets the message describing the problem.
+     * @param message Message describing the problem.
+     */
+    public void setMessage(String message) {
+        this.message = message;
+    }
+    
+    /**
+     * Sets the path of the resource that had the problem.
+     * @param resourcePath Path of the resource, relative to the project root.
+     */
+    public void setResourcePath(String resourcePath) {
+        this.resourcePath = resourcePath;
+    }
+    
+    /**
+     * Sets the severity of the problem.
+     * @param severity Severity of the problem. Should be one of
+     * SEVERITY_ERROR or SEVERITY_WARNING.
+     */
+    public void setSeverity(int severity) {
+        this.severity = severity;
+    }
+    //==================================================== PRIVATE
+    
+    private String resourcePath;
+    private int severity;
+    private String message;
+    private int lineNumber;
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/BuildProblemDecorator.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/BuildProblemDecorator.java
new file mode 100644
index 0000000..f6b6193
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/BuildProblemDecorator.java
@@ -0,0 +1,81 @@
+/*******************************************************************************
+ * 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.ui.internal.config;
+
+import java.net.URL;
+
+
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.FileLocator;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.viewers.IDecoration;
+import org.eclipse.jface.viewers.ILightweightLabelDecorator;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.jface.viewers.LabelProviderChangedEvent;
+import org.eclipse.wst.xml.vex.ui.internal.VexPlugin;
+
+/**
+ * Decorates Vex resources that build problems.
+ */
+public class BuildProblemDecorator extends LabelProvider implements ILightweightLabelDecorator {
+
+    public static final String ID = "org.eclipse.wst.xml.vex.ui.internal.config.buildProblemDecorator"; //$NON-NLS-1$
+    
+    public void decorate(Object element, IDecoration decoration) {
+        
+        if (this.errorIcon == null) {
+            this.loadImageDescriptors();
+        }
+        
+        if (element instanceof IResource) {
+            try {
+                IResource resource = (IResource) element;
+                IMarker[] markers = resource.findMarkers(IMarker.PROBLEM, true, 0);
+                if (markers.length > 0) {
+                    decoration.addOverlay(this.errorIcon, IDecoration.BOTTOM_LEFT);
+                }
+            } catch (CoreException e) {
+            }
+        }
+    }
+
+    /**
+     * Fire a change notification that the markers on the given resource has changed.
+     * @param resources Array of resources whose markers have changed.
+     */
+    public void update(IResource resource) {
+        this.fireLabelProviderChanged(new LabelProviderChangedEvent(this, resource));
+    }
+    
+    /**
+     * Fire a change notification that the markers on the given resources has changed.
+     * @param resources Array of resources whose markers have changed.
+     */
+    public void update(IResource[] resources) {
+        this.fireLabelProviderChanged(new LabelProviderChangedEvent(this, resources));
+    }
+    
+    //======================================================== PRIVATE
+    
+    private ImageDescriptor errorIcon;
+    
+    private void loadImageDescriptors() {
+    	URL url = FileLocator.find(
+    			VexPlugin.getInstance().getBundle(),
+    			new Path("icons/error_co.gif"), //$NON-NLS-1$
+    			null); 
+        this.errorIcon = ImageDescriptor.createFromURL(url);
+    }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigEvent.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigEvent.java
new file mode 100644
index 0000000..0f45022
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigEvent.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.ui.internal.config;
+
+import java.util.EventObject;
+
+/**
+ * Event indicating a change of configuration items.
+ */
+public class ConfigEvent extends EventObject {
+
+    /**
+     * Class constructor.
+     * @param source Source of the event.
+     */
+    public ConfigEvent(Object source) {
+        super(source);
+    }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigItem.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigItem.java
new file mode 100644
index 0000000..b1a202c
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigItem.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.ui.internal.config;
+
+import java.io.Serializable;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+
+/**
+ * Base class of all configurtion items such as document types and styles.
+ */
+public abstract class ConfigItem implements Comparable, Serializable {
+    
+    /**
+     * Class constructor.
+     * @param config VexConfiguration to which this item belongs.
+     */
+    public ConfigItem(ConfigSource config) {
+        this.config = config;
+    }
+
+    public int compareTo(Object o) {
+        return this.getName().compareTo(((ConfigItem) o).getName());
+    }
+    
+    /**
+     * Generate a simple identifier for the item that is unique with its
+     * configuration.
+     */
+    public String generateSimpleId() {
+        String base = "id"; //$NON-NLS-1$
+        int i = 1;
+        for (;;) {
+            String id = base + i;
+            if (this.getConfig().getItem(id) == null) {
+                return id;
+            }
+            i++;
+        }
+    }
+  
+    /**
+     * Returns the VexConfiguration to which this item belongs.
+     */
+    public ConfigSource getConfig() {
+        return config;
+    }
+
+    /**
+     * Returns the extension point ID of configuration item. This will be 
+     * the same for all config items of a given type.
+     */
+    public abstract String getExtensionPointId();
+
+    /**
+     * Returns the simple ID of this item. This is a short string containing
+     * no periods.
+     */
+    public String getSimpleId() {
+        return this.id;
+    }
+
+    /**
+     * Returns the unique ID of this item. The unique ID is formed by concatenating
+     * the ID of the associated VexConfiguration, a period, and the simple ID
+     * of this item.
+     */
+    public String getUniqueId() {
+        return this.id == null ? null : this.getConfig().getUniqueIdentifer() + "." + this.id; //$NON-NLS-1$
+    }
+
+    /**
+     * Returns the human-readable name for this item.
+     */
+    public String getName() {
+        return this.name;
+    }
+
+    /**
+     * Returns the path of the associated resource, if any, relative to 
+     * the base directory of the associated VexConfiguration. Returns null
+     * if no resource is associated with this item.
+     */
+    public String getResourcePath() {
+        return this.resourcePath;
+    }
+
+    /**
+     * Returns the URL of the associated resource, if any. This is formed
+     * by appending the resource path to the URL of the associated
+     * VexConfiguration.
+     */
+    public URL getResourceUrl() throws MalformedURLException {
+        if (this.resourcePath == null) {
+            return null;
+        } else {
+            return new URL(this.getConfig().getBaseUrl(), this.getResourcePath());
+        }
+    }
+
+    /**
+     * Returns true if s is null or empty. Convenience method to be used
+     * in isValid().
+     * @param s String to check.
+     */
+    protected boolean isBlank(String s) {
+        return s == null || s.length() == 0;
+    }
+
+    /**
+     * Returns true if this item has sufficient information to be used as
+     * a configuration item. By default, the item must have a simple ID 
+     * and a name. Subclasses should override and provide additional checks
+     * as necessary, after calling this base implementation.
+     */
+    public boolean isValid() {
+        return !isBlank(id) && !isBlank(name);
+    }
+    
+    /**
+     * Sets the name for this item.
+     * @param name New name for this item.
+     */
+    public void setName(String name) {
+        this.name = name;
+    }
+    
+    /**
+     * Sets the resource path for this item.
+     * @param resourcePath New resource path for this item.
+     */
+    public void setResourcePath(String resourcePath) {
+        this.resourcePath = resourcePath;
+    }
+    
+    /**
+     * Sets the simple ID for this item. The simple ID should only contain
+     * letters and numbers, and must not contain a period.
+     * @param id New simple ID for this item.
+     */
+    public void setSimpleId(String id) {
+        this.id = id;
+    }
+    
+    //==================================================== PRIVATE
+    
+    private String id;
+    private String name;
+    private String resourcePath;
+    private ConfigSource config;
+    
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigLoaderJob.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigLoaderJob.java
new file mode 100644
index 0000000..22f9b7e
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigLoaderJob.java
@@ -0,0 +1,206 @@
+/*******************************************************************************
+ * 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
+ *     Ed Burnette - 7/23/2006 -  Changes needed to build on 3.2.
+ *******************************************************************************/
+package org.eclipse.wst.xml.vex.ui.internal.config;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.text.MessageFormat;
+
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IExtensionRegistry;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.wst.xml.vex.ui.internal.VexPlugin;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+
+/**
+ * Job that loads Vex configuration objects from plugins and plugin projects.
+ */
+public class ConfigLoaderJob extends Job {
+
+    public static final String PLUGIN_CONFIG_SER_PREFIX = ".vexConfig-"; //$NON-NLS-1$
+    public static final String PLUGIN_CONFIG_SER_SUFFIX = ".ser"; //$NON-NLS-1$
+
+
+    /**
+     * Class constructor.
+     */
+    public ConfigLoaderJob() {
+        super(Messages.getString("ConfigLoaderJob.loadingConfig")); //$NON-NLS-1$
+    }
+
+    protected IStatus run(IProgressMonitor monitor) {
+        
+        //System.out.println("ConfigLoaderJob starts");
+        
+        int pluginCount = Platform.getExtensionRegistry().getNamespaces().length;
+        int projectCount = ResourcesPlugin.getWorkspace().getRoot().getProjects().length;
+        
+        monitor.beginTask(Messages.getString("ConfigLoaderJob.loadingConfig"), pluginCount + projectCount); //$NON-NLS-1$
+
+        this.loadPlugins(monitor);
+        this.loadPluginProjects(monitor);
+        ConfigRegistry.getInstance().fireConfigLoaded(new ConfigEvent(this));
+
+        monitor.done();
+        
+        //System.out.println("ConfigLoaderJob ends");
+        
+        return Status.OK_STATUS;
+    }
+
+    //======================================================= PRIVATE
+    
+    /**
+     * Load configurations from all registered plugins.
+     */
+    private void loadPlugins(IProgressMonitor monitor) {
+        
+        ConfigRegistry configRegistry = ConfigRegistry.getInstance();
+        IExtensionRegistry extRegistry = Platform.getExtensionRegistry();
+        String[] namespaces = extRegistry.getNamespaces();
+        for (int i = 0; i < namespaces.length; i++) {
+            
+            String ns = namespaces[i];
+            Bundle bundle = Platform.getBundle(ns);
+            if (bundle == null)
+            	continue;
+            
+            String name = (String) bundle.getHeaders().get(Constants.BUNDLE_NAME);
+            monitor.subTask(Messages.getString("ConfigLoaderJob.loading") + name); //$NON-NLS-1$
+            
+            File stateDir = Platform.getStateLocation(bundle).toFile();
+            String version = (String) bundle.getHeaders().get(Constants.BUNDLE_VERSION);
+            String serFile = PLUGIN_CONFIG_SER_PREFIX + version + PLUGIN_CONFIG_SER_SUFFIX;
+            File configSerFile = new File(stateDir, serFile);
+
+            ConfigSource source = null;
+            if (configSerFile.exists()) {
+                try {
+                    //long start = System.currentTimeMillis();
+                    source = loadConfigSourceFromFile(configSerFile);
+                    //long end = System.currentTimeMillis();
+                    //System.out.println("  load from ser file took " + (end-start) + "ms");
+                } catch (IOException ex) {
+                    String message = MessageFormat.format(
+                            Messages.getString("ConfigLoaderJob.loadingError"), //$NON-NLS-1$
+                            new Object[] { configSerFile });
+                    this.log(IStatus.WARNING, message, ex);
+                }
+            }
+            
+            if (source == null) {
+                
+                source = ConfigPlugin.load(ns);
+
+                if (source != null) {
+                    try {
+                        saveConfigSourceToFile(source, configSerFile);
+                    } catch (IOException ex) {
+                        String message = MessageFormat.format(
+                                Messages.getString("ConfigLoaderJob.cacheError"), //$NON-NLS-1$
+                                new Object[] { configSerFile });
+                        this.log(IStatus.WARNING, message, ex);
+                    }
+                } else {
+                    if (configSerFile.exists()) {
+                        configSerFile.delete(); // Used to have a config, but now we don't
+                    }
+                }
+
+            }
+            
+            if (source != null) {
+                configRegistry.addConfigSource(source);
+            }
+            
+            monitor.worked(1);
+        }
+    }
+    
+    private static ConfigSource loadConfigSourceFromFile(File file) throws IOException {
+        
+        FileInputStream fis = null;
+        try {
+            fis = new FileInputStream(file);
+            ObjectInputStream ois = new ObjectInputStream(fis);
+            return (ConfigSource) ois.readObject();
+        } catch (ClassNotFoundException ex) {
+            throw new IOException(ex.getMessage());
+        } finally {
+            if (fis != null) {
+                try {
+                    fis.close();
+                } catch (IOException ex) {
+                }
+            }
+        }
+    }
+    
+    private static void saveConfigSourceToFile(ConfigSource config, File file) throws IOException {
+        FileOutputStream fos = null;
+        try {
+            fos = new FileOutputStream(file);
+            ObjectOutputStream oos = new ObjectOutputStream(fos);
+            oos.writeObject(config);
+        } finally {
+            if (fos != null) {
+                try {
+                    fos.close();
+                } catch (IOException ex) {
+                }
+            }
+        }
+    }
+    
+    /**
+     * Load configurations from all Vex Plugin Projects in the workspace.
+     */
+    private void loadPluginProjects(IProgressMonitor monitor) {
+        
+        IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
+        IProject[] projects = root.getProjects();
+        
+        for (int i = 0; i < projects.length; i++) {
+            try {
+                if (projects[i].isOpen() &&
+                        projects[i].hasNature(PluginProjectNature.ID)) {
+                    monitor.subTask(Messages.getString("ConfigLoaderJob.loadingProject") + projects[i].getName()); //$NON-NLS-1$
+                    PluginProject.load(projects[i]);
+                    monitor.worked(1);
+                }
+            } catch (CoreException e) {
+                String message = MessageFormat.format(
+                        Messages.getString("ConfigLoaderJob.natureError"), //$NON-NLS-1$
+                        new Object[] { projects[i].getName() });
+                VexPlugin.getInstance().log(IStatus.ERROR, message, e);
+            }
+        }
+    }
+
+
+    private void log(int severity, String message, Throwable exception) {
+        VexPlugin.getInstance().log(severity, message, exception);
+    }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigPlugin.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigPlugin.java
new file mode 100644
index 0000000..33ef9f5
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigPlugin.java
@@ -0,0 +1,78 @@
+/*******************************************************************************
+ * 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.ui.internal.config;
+
+import java.io.IOException;
+import java.net.URL;
+import java.text.MessageFormat;
+
+
+import org.eclipse.core.runtime.IExtension;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.wst.xml.vex.ui.internal.VexPlugin;
+
+/**
+ * A regular Eclipse plugin that provides Vex configuration items.
+ */
+public class ConfigPlugin extends ConfigSource {
+
+    /**
+     * Filename prefix used when serializing the config from an installed
+     * plugin. Since all versions of a plugin share the same persistence
+     * area, we incorporate the plugin version number into the filename.
+     */
+    public static final String SER_FILE_PREFIX = ".vexConfig-"; //$NON-NLS-1$
+    
+    /** Filename suffix used when serializing an installed plugin */
+    public static final String SER_FILE_SUFFIX = ".ser"; //$NON-NLS-1$
+
+    protected ConfigPlugin(String namespace) {
+        this.namespace = namespace;
+    }
+    
+    public static ConfigPlugin load(String namespace) {
+            
+        ConfigPlugin configPlugin = new ConfigPlugin(namespace);
+        configPlugin.setUniqueIdentifer(namespace);
+        
+        IExtension[] exts = Platform.getExtensionRegistry().getExtensions(namespace);
+        for (int i = 0; i < exts.length; i++) {
+            IExtension ext = exts[i];
+            try {
+                configPlugin.addItem(
+                        ext.getExtensionPointUniqueIdentifier(), 
+                        ext.getSimpleIdentifier(),
+                        ext.getLabel(),
+                        ConfigurationElementWrapper.convertArray(ext.getConfigurationElements()));
+            } catch (IOException e) {
+                String message = MessageFormat.format(
+                        Messages.getString("ConfigPlugin.loadError"), //$NON-NLS-1$
+                        new Object[] { ext.getSimpleIdentifier(), namespace });
+                VexPlugin.getInstance().log(IStatus.ERROR, message, e);
+                return null;
+            }
+        }
+        
+        configPlugin.parseResources(null);
+        
+        return configPlugin.isEmpty() ? null : configPlugin;
+    }
+    
+    
+    public URL getBaseUrl() {
+        return Platform.getBundle(namespace).getEntry("plugin.xml"); //$NON-NLS-1$
+    }
+    
+    //======================================================= PRIVATE
+    
+    private String namespace;
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigRegistry.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigRegistry.java
new file mode 100644
index 0000000..4c7b785
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigRegistry.java
@@ -0,0 +1,331 @@
+/*******************************************************************************
+ * 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.ui.internal.config;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResourceChangeEvent;
+import org.eclipse.core.resources.IResourceChangeListener;
+import org.eclipse.core.resources.IResourceDelta;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.jobs.ILock;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.wst.xml.vex.core.internal.core.ListenerList;
+
+/**
+ * Singleton registry of configuration sources and listeners.
+ * 
+ * The configuration sources may be accessed by multiple threads, and are
+ * protected by a lock. All methods that modify or iterate over config sources
+ * do so after acquiring the lock. Callers that wish to perform multiple
+ * operations as an atomic transaction must lock and unlock the registry as
+ * follows.
+ * 
+ * <pre>
+ * ConfigRegistry reg = ConfigRegistry.getInstance();
+ * try {
+ *     reg.lock();
+ *     // make modifications
+ * } finally {
+ *     reg.unlock();
+ * }
+ * </pre>
+ * 
+ * <p>This class also maintains a list of ConfigListeners. The
+ * addConfigListener and removeConfigListener methods must be called from
+ * the main UI thread. The fireConfigXXX methods may be called from other
+ * threads; this class will ensure the listeners are called on the UI thread.
+ */
+public class ConfigRegistry {
+
+    /**
+     * Returns the singleton instance of the registry.
+     */
+    public static ConfigRegistry getInstance() {
+        return instance;
+    }
+    
+    /**
+     * Add a VexConfiguration to the list of configurations.
+     * @param config VexConfiguration to be added.
+     */
+    public void addConfigSource(ConfigSource config) {
+        try {
+            this.lock();
+            this.configs.add(config);
+        } finally {
+            this.unlock();
+        }
+    }
+
+    /**
+     * Adds a ConfigChangeListener to the notification list.
+     * @param listener Listener to be added.
+     */
+    public void addConfigListener(IConfigListener listener) {
+        this.configListeners.add(listener);
+    }
+
+    /**
+     * Call the configChanged method on all registered ConfigChangeListeners.
+     * The listeners are called from the display thread, even if this method
+     * was called from another thread.
+     * @param e ConfigEvent to be fired.
+     */
+    public void fireConfigChanged(final ConfigEvent e) {
+        if (this.isConfigLoaded()) {
+            Runnable runnable = new Runnable() {
+                public void run() {
+                    configListeners.fireEvent("configChanged", e); //$NON-NLS-1$
+                }
+            };
+            
+            Display display = Display.getDefault();
+            if (display.getThread() == Thread.currentThread()) {
+                runnable.run();
+            } else {
+                display.asyncExec(runnable);
+            }
+        }
+    }
+    
+    /**
+     * Call the configLoaded method on all registered ConfigChangeListeners
+     * from the display thread.
+     * This method is called from the ConfigLoaderJob thread.
+     * @param e ConfigEvent to be fired.
+     */
+    public void fireConfigLoaded(final ConfigEvent e) {
+        Runnable runnable = new Runnable() {
+            public void run() {
+                configLoaded = true;
+                configListeners.fireEvent("configLoaded", e); //$NON-NLS-1$
+            }
+        };
+        
+        Display.getDefault().asyncExec(runnable);
+    }
+    
+    /**
+     * Returns an array of all config item factories.
+     */
+    public IConfigItemFactory[] getAllConfigItemFactories() {
+        List f = this.configItemFactories;
+        return (IConfigItemFactory[]) f.toArray(new IConfigItemFactory[f.size()]);
+    }
+    
+    /**
+     * Returns an array of all registered ConfigItem objects implementing
+     * the given extension point.
+     * @param extensionPoint ID of the desired extension point.
+     */
+    public List getAllConfigItems(String extensionPoint) {
+        try {
+            this.lock();
+            List items = new ArrayList();
+            for (Iterator it = this.configs.iterator(); it.hasNext(); ) {
+                ConfigSource config = (ConfigSource) it.next();
+                items.addAll(config.getValidItems(extensionPoint));
+            }
+            return items;
+        } finally {
+            this.unlock();
+        }
+    }
+
+    /**
+     * Returns a list of all registered ConfigSource objects.
+     * @return
+     */
+    public List getAllConfigSources() {
+        try {
+            this.lock();
+            List result = new ArrayList();
+            result.addAll(this.configs);
+            return result;
+        } finally {
+            this.unlock();
+        }
+    }
+    
+    /**
+     * Returns a specific configuration item given an extension point id
+     * and the item's id. Returns null if either the extension point or
+     * the item is not found.
+     * 
+     * @param extensionPoint ID of the desired extension point.
+     * @param id ID of the desired item.
+     */
+    public ConfigItem getConfigItem(String extensionPoint, String id) {
+        try {
+            this.lock();
+            List items = this.getAllConfigItems(extensionPoint);
+            for (Iterator it = items.iterator(); it.hasNext(); ) {
+                ConfigItem item = (ConfigItem) it.next();
+                if (item.getUniqueId().equals(id)) {
+                    return item;
+                }
+            }
+            return null;
+        } finally {
+            this.unlock();
+        }
+    }
+
+    /**
+     * Returns the IConfigItemFactory object for the given extension point
+     * or null if none exists.
+     * @param extensionPointId Extension point ID for which to search. 
+     */
+    public IConfigItemFactory getConfigItemFactory(String extensionPointId) {
+        for (Iterator it = this.configItemFactories.iterator(); it.hasNext();) {
+            IConfigItemFactory factory = (IConfigItemFactory) it.next();
+            if (factory.getExtensionPointId().equals(extensionPointId)) {
+                return factory;
+            }
+        }
+        return null;
+    }
+    
+    /**
+     * Returns true if the Vex configuration has been loaded.
+     * 
+     * @see org.eclipse.wst.xml.vex.ui.internal.config.ConfigLoaderJob 
+     */
+    public boolean isConfigLoaded() {
+        return this.configLoaded;
+    }
+    
+
+    /**
+     * Locks the registry for modification or iteration over its config sources.
+     */
+    public void lock() {
+        this.lock.acquire();
+    }
+
+    
+    /**
+     * Remove a VexConfiguration from the list of configs.
+     * @param config VexConfiguration to remove.
+     */
+    public void removeConfigSource(ConfigSource config) {
+        try {
+            this.lock();
+            this.configs.remove(config);
+        } finally {
+            this.unlock();
+        }
+    }
+    
+    /**
+     * Removes a ConfigChangeListener from the notification list.
+     * @param listener Listener to be removed.
+     */
+    public void removeConfigListener(IConfigListener listener) {
+        this.configListeners.remove(listener);
+    }
+    
+
+
+    /**
+     * Unlocks the registry.
+     */
+    public void unlock() {
+        this.lock.release();
+    }
+
+    //======================================================== PRIVATE
+    
+    private static ConfigRegistry instance = new ConfigRegistry();
+    
+    private ILock lock = Platform.getJobManager().newLock();
+    private List configs = new ArrayList();
+    private ListenerList configListeners = new ListenerList(IConfigListener.class, ConfigEvent.class);
+    private boolean configLoaded = false;
+    private List configItemFactories = new ArrayList();
+    
+
+    /**
+     * Class constructor. All initialization is performed here.
+     */
+    private ConfigRegistry() {
+        this.configItemFactories.add(new DoctypeFactory());
+        this.configItemFactories.add(new StyleFactory());
+        // TODO do we ever unregister this?
+        ResourcesPlugin.getWorkspace().addResourceChangeListener(this.resourceChangeListener);
+    }
+   
+    private IResourceChangeListener resourceChangeListener = new IResourceChangeListener() {
+
+        public void resourceChanged(final IResourceChangeEvent event) {
+            
+            //System.out.println("resourceChanged, type is " + event.getType() + ", resource is " + event.getResource());
+            
+            if (event.getType() == IResourceChangeEvent.PRE_CLOSE ||
+                    event.getType() == IResourceChangeEvent.PRE_DELETE) {
+                PluginProject pp = PluginProject.get((IProject) event.getResource());
+                if (pp != null) {
+                    //System.out.println("  removing project from config registry");
+                    removeConfigSource(pp);
+                    fireConfigChanged(new ConfigEvent(this));
+                }
+            } else if (event.getType() == IResourceChangeEvent.POST_CHANGE) {
+                IResourceDelta[] resources = event.getDelta().getAffectedChildren();
+                for (int i = 0; i < resources.length; i++) {
+                    final IResourceDelta delta = resources[i];
+                    if (delta.getResource() instanceof IProject) {
+                        final IProject project = (IProject) delta.getResource();
+                        
+                        //System.out.println("Project " + project.getName() + " changed, isOpen is " + project.isOpen());
+
+                        PluginProject pluginProject = PluginProject.get(project);
+
+                        boolean hasPluginProjectNature = false;
+                        try {
+                            hasPluginProjectNature = project.hasNature(PluginProjectNature.ID); 
+                        } catch (CoreException ex) {
+                            // yup, sometimes checked exceptions really blow
+                        }
+                        
+                        if (!project.isOpen() && pluginProject != null) {
+                            
+                            //System.out.println("  closing project: " + project.getName());
+                            removeConfigSource(pluginProject);
+                            fireConfigChanged(new ConfigEvent(this));
+                            
+                        } else if (project.isOpen() && pluginProject == null && hasPluginProjectNature) {
+                            
+                            //System.out.println("  newly opened project: " + project.getName() + ", rebuilding");
+                            
+                            // Must be run in another thread, since the workspace is locked here
+                            Runnable runnable = new Runnable() {
+                                public void run() {
+                                    PluginProject.load(project);
+                                }
+                            };
+                            Display.getDefault().asyncExec(runnable);
+                        } else {
+                            //System.out.println("  no action taken");
+                        }
+                    }
+                }
+            }
+        }
+    };
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigSource.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigSource.java
new file mode 100644
index 0000000..6a092b1
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigSource.java
@@ -0,0 +1,268 @@
+/*******************************************************************************
+ * 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.ui.internal.config;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.net.URL;
+import java.text.MessageFormat;
+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.core.runtime.IStatus;
+import org.eclipse.wst.xml.vex.ui.internal.VexPlugin;
+
+
+/**
+ * A plugin or plugin project that contributes ConfigItems. This class and all
+ * configuration items added to it must be serializable, since it is persisted
+ * across Vex invocations due to the expense of reparsing the configuration
+ * items.
+ */
+public abstract class ConfigSource implements Serializable {
+    
+    /**
+     * Adds the given item to the configuration.
+     * @param item ConfigItem to be added.
+     */
+    public void addItem(ConfigItem item) {
+        this.items.add(item);
+    }
+
+    /**
+     * Creates a configuration item and adds it to this configuration. 
+     * If the given extension point does not have a factory registered
+     * in VexPlugin, no action is taken and null is returned.
+     * 
+     * @param extensionPoint Extension point of the item to be added.
+     * @param simpleIdentifier Simple (i.e. no dots) identifier of the item.
+     * @param name Name of the item.
+     * @param configElements Array of IConfigElement objects representing the item's settings.
+     * @return The newly created ConfigItem, or null if extensionPoint is
+     * not recognized.
+     * @throws IOException
+     */
+    public ConfigItem addItem(
+            String extensionPoint, 
+            String simpleIdentifier, 
+            String name, 
+            IConfigElement[] configElements) throws IOException {
+        
+        IConfigItemFactory factory = ConfigRegistry.getInstance().getConfigItemFactory(extensionPoint);
+        if (factory != null) {
+            ConfigItem item = factory.createItem(this, configElements);
+            item.setSimpleId(simpleIdentifier);
+            item.setName(name);
+            this.addItem(item);
+            return item;
+        } else {
+            return null;
+        }
+        
+    }
+    
+    /**
+     * Removes the given item from the configuration.
+     * @param item ConfigItem to be removed.
+     */
+    public void remove(ConfigItem item) {
+        items.remove(item);
+    }
+    
+    /**
+     * Remove all items from this configuration.
+     */
+    public void removeAllItems() {
+        this.items.clear();
+    }
+    
+    /**
+     * Remove all parsed resources from this configuration.
+     */
+    public void removeAllResources() {
+        this.parsedResources.clear();
+    }
+    
+    /**
+     * Remove the resource associated with the given URI from the resource
+     * cache. The factory must handle any of the following scenarios.
+     * 
+     * <ul>
+     * <li>The URI represents the primary resource associated with a
+     * configuration item.</li>
+     * <li>The URI is a secondary resource associated with a primary
+     * resource. In this case the primary resource is removed.</li>
+     * <li>The URI has nothing to do with a configuration item,
+     * in which case no action is taken.</li>
+     * </ul>
+     * 
+     * To fully implement this method, the factory must interact with the
+     * parser and track which secondary resources are associated with
+     * which primaries.
+     * 
+     * @param uri Relative URI of the resource to remove.
+     */
+    public void removeResource(String uri) {
+        this.parsedResources.remove(uri); // TODO Respect secondary resources
+    }
+
+
+    /**
+     * Returns a list of all items in this configuration.
+     */
+    public List getAllItems() {
+        return items;
+    }
+
+    /**
+     * Returns all ConfigItems of the given type registered with this 
+     * configuration.
+     * @param type The type of ConfigItem to return.
+     */
+    public Collection getAllItems(String type) {
+        List items = new ArrayList();
+        for (Iterator it = this.items.iterator(); it.hasNext();) {
+            ConfigItem item = (ConfigItem) it.next();
+            if (item.getExtensionPointId().equals(type)) {
+                items.add(item);
+            }
+        }
+        return items;
+    }
+    
+    /**
+     * Returns the base URL of this factory. This is used to resolve 
+     * relative URLs in config items
+     */
+    public abstract URL getBaseUrl();
+    
+
+    /**
+     * Returns a particular item from the configuration. Returns null if no
+     * matching item could be found.  
+     * @param simpleId Simple ID of the item to return.
+     */
+    public ConfigItem getItem(String simpleId) {
+        for (Iterator it = this.items.iterator(); it.hasNext();) {
+            ConfigItem item = (ConfigItem) it.next();
+            if (item.getSimpleId() != null && item.getSimpleId().equals(simpleId)) {
+                return item;
+            }
+        }
+        return null;
+    }
+    
+    /**
+     * Returns the item for the resource with the given path relative
+     * to the plugin or project. May return null if no such item exists.
+     * @param resourcePath Path of the resource.
+     */
+    public ConfigItem getItemForResource(String resourcePath) {
+        for (Iterator it = this.items.iterator(); it.hasNext();) {
+            ConfigItem item = (ConfigItem) it.next();
+            if (item.getResourcePath().equals(resourcePath)) {
+                return item;
+            }
+        }
+        return null;
+    }
+    
+    /**
+     * Returns the parsed resource object for the given URI, or null of
+     * none exists.
+     * @param uri URI of the resource, relative to the base URL of this configuration.
+     */
+    public Object getParsedResource(String uri) {
+        return this.parsedResources.get(uri);
+    }
+
+    /**
+     * Returns the unique identifier of this configuration. This is the same
+     * as the ID of the plugin that defines the configuration.
+     */
+    public String getUniqueIdentifer() {
+        return this.id;
+    }
+    
+    /**
+     * Returns all ConfigItems of the given type for which isValid returns
+     * true.
+     * @param type The type of ConfigItem to return.
+     */
+    public Collection getValidItems(String type) {
+        Collection allItems = this.getAllItems(type);
+        List validItems = new ArrayList();
+        for (Iterator it = allItems.iterator(); it.hasNext();) {
+            ConfigItem item = (ConfigItem) it.next();
+            if (item.isValid()) {
+                validItems.add(item);
+            }
+        }
+        return validItems;
+    }
+    
+    /**
+     * Returns true if there are no items in this configuration.
+     */
+    public boolean isEmpty() {
+        return this.items.isEmpty();
+    }
+    
+    /**
+     * Parses all resources required by the registered items.
+     * @param problemHandler Handler for build problems. May be null.
+     */
+    public void parseResources(IBuildProblemHandler problemHandler) {
+        for (Iterator it = this.items.iterator(); it.hasNext();) {
+            ConfigItem item = (ConfigItem) it.next();
+            String uri = item.getResourcePath();
+            if (!this.parsedResources.containsKey(uri)) {
+                IConfigItemFactory factory = ConfigRegistry.getInstance().getConfigItemFactory(item.getExtensionPointId());
+                Object parsedResource;
+                try {
+                    parsedResource = factory.parseResource(this.getBaseUrl(), uri, problemHandler);
+                    this.parsedResources.put(uri, parsedResource);
+                } catch (IOException ex) {
+                    String message = MessageFormat.format(
+                            Messages.getString("ConfigSource.errorParsingUri"),
+                            new Object[] { uri });
+                    VexPlugin.getInstance().log(IStatus.ERROR, message, ex); //$NON-NLS-1$
+                }
+            }
+        }
+    }
+    
+    /**
+     * Sets the unique identifier of this configuration.
+     * @param id New identifier for this configuration.
+     */
+    public void setUniqueIdentifer(String id) {
+        this.id = id;
+    }
+    
+    //==================================================== PRIVATE
+    
+    // Globally-unique identifier of this configuration
+    // == the plugin id.
+    private String id;
+    
+    // all config items in this configuration
+    private List items = new ArrayList();
+    
+    // map String URI => parsed resource
+    private Map parsedResources = new HashMap();
+    
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigurationElement.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigurationElement.java
new file mode 100644
index 0000000..1ba36eb
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigurationElement.java
@@ -0,0 +1,129 @@
+/*******************************************************************************
+ * 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.ui.internal.config;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Lightweight implementation of the IConfigurationElement interface. This
+ * class is used by config item factories when re-creating the configuration
+ * elements corresponding to a given config item.
+ */
+public class ConfigurationElement implements IConfigElement {
+
+    /**
+     * Class constructor.
+     */
+    public ConfigurationElement() {
+    }
+
+    /**
+     * Class constructor.
+     * @param name Name of the element.
+     */
+    public ConfigurationElement(String name) {
+        this.name = name;
+    }
+    
+    /**
+     * Adds a new child to this element.
+     * @param child child to be added.
+     */
+    public void addChild(IConfigElement child) {
+        this.children.add(child);
+    }
+    
+    public String getAttribute(String name) {
+        return (String) this.attributes.get(name);
+    }
+
+    public String[] getAttributeNames() {
+        Set keys = this.attributes.keySet();
+        return (String[]) keys.toArray(new String[keys.size()]);
+    }
+
+    public IConfigElement[] getChildren() {
+        return (IConfigElement[]) this.children.toArray(new IConfigElement[this.children.size()]);
+    }
+
+    public IConfigElement[] getChildren(String name) {
+        List kids = new ArrayList();
+        for (Iterator it = this.children.iterator(); it.hasNext();) {
+            IConfigElement child = (IConfigElement) it.next();
+            if (child.getName().equals(name)) {
+                kids.add(child);
+            }
+        }
+        return (IConfigElement[]) kids.toArray(new IConfigElement[kids.size()]);
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public String getValue() {
+        return this.value;
+    }
+
+    /**
+     * Sets the given attribute. If value is null, the attribute is removed
+     * from the element.
+     * 
+     * @param name Name of the attribute.
+     * @param value Value of the attribute.
+     */
+    public void setAttribute(String name, String value) {
+        if (value == null) {
+            this.attributes.remove(name);
+        } else {
+            this.attributes.put(name, value);
+        }
+    }
+    
+    /**
+     * Sets the children of this element given an array of IConfigElement
+     * objects.
+     * @param children Children of this element.
+     */
+    public void setChildren(IConfigElement[] children) {
+        this.children.clear();
+        this.children.addAll(Arrays.asList(children));
+    }
+    
+    /**
+     * Sets the name of the element.
+     * @param name Name of the element.
+     */
+    public void setName(String name) {
+        this.name = name;
+    }
+    
+    /**
+     * Sets the value of the element.
+     * @param value Value of the element.
+     */
+    public void setValue(String value) {
+        this.value = value;
+    }
+    
+    //==================================================== PRIVATE
+
+    private String name;
+    private String value;
+    private Map attributes = new HashMap();
+    private List children = new ArrayList();
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigurationElementWrapper.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigurationElementWrapper.java
new file mode 100644
index 0000000..361a0a0
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigurationElementWrapper.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.ui.internal.config;
+
+import org.eclipse.core.runtime.IConfigurationElement;
+
+/**
+ * Wrapper for IConfigurationElement that implements IConfigElement.
+ */
+public class ConfigurationElementWrapper implements IConfigElement {
+
+    /**
+     * Class constructor.
+     * @param element Element to be wrapped.
+     */
+    public ConfigurationElementWrapper(IConfigurationElement element) {
+        this.element = element;
+    }
+    
+    public String getAttribute(String name) {
+        return this.element.getAttribute(name);
+    }
+
+    public String[] getAttributeNames() {
+        return this.element.getAttributeNames();
+    }
+
+    public IConfigElement[] getChildren() {
+        return convertArray(this.element.getChildren());
+    }
+
+    public IConfigElement[] getChildren(String name) {
+        return convertArray(this.element.getChildren(name));
+    }
+
+    public String getName() {
+        return this.element.getName();
+    }
+
+    public String getValue() {
+        return this.element.getValue();
+    }
+
+    /**
+     * Wraps each element in an array of IConfigurationElement objects with
+     * a ConfigurationElementWrapper and returns the result.
+     * @param elements Array of elements to be wrapped.
+     */
+    public static IConfigElement[] convertArray(IConfigurationElement[] elements) {
+        IConfigElement[] ret = new IConfigElement[elements.length];
+        for (int i = 0; i < elements.length; i++) {
+            ret[i] = new ConfigurationElementWrapper(elements[i]);
+        }
+        return ret;
+    }
+
+    //=================================================== PRIVATE
+    
+    private IConfigurationElement element;
+    
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigurationView.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigurationView.java
new file mode 100644
index 0000000..3e3627a
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/ConfigurationView.java
@@ -0,0 +1,134 @@
+/*******************************************************************************
+ * 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.ui.internal.config;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.jface.viewers.TreeViewer;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.ui.part.ViewPart;
+
+/**
+ * View showing all configuration items defined in Vex.
+ */
+public class ConfigurationView extends ViewPart {
+
+    public void createPartControl(Composite parent) {
+    
+        this.parentControl = parent;
+        
+        ConfigRegistry registry = ConfigRegistry.getInstance();
+        
+        registry.addConfigListener(this.configListener);
+        
+        if (registry.isConfigLoaded()) {
+            this.createTreeViewer();
+        } else {
+            this.loadingLabel = new Label(parent, SWT.NONE);
+            this.loadingLabel.setText(Messages.getString("ConfigurationView.loading")); //$NON-NLS-1$
+        }
+
+    }
+
+    public void dispose() {
+        super.dispose();
+        ConfigRegistry.getInstance().removeConfigListener(this.configListener);
+    }
+
+    public void setFocus() {
+        if (this.treeViewer != null) {
+            this.treeViewer.getTree().setFocus();
+        }
+    }
+
+    //===================================================== PRIVATE
+    
+    private Composite parentControl;
+    
+    private Label loadingLabel;
+    
+    private TreeViewer treeViewer;
+    
+    private void createTreeViewer() {
+        this.treeViewer = new TreeViewer(this.parentControl, SWT.SINGLE);
+        this.treeViewer.setContentProvider(new ContentProvider());
+        this.treeViewer.setLabelProvider(new MyLabelProvider());
+        this.treeViewer.setAutoExpandLevel(2);
+        this.treeViewer.setInput(ConfigRegistry.getInstance());
+    }
+    
+    private static class ContentProvider implements ITreeContentProvider {
+
+        public Object[] getChildren(Object parentElement) {
+            if (parentElement instanceof IConfigItemFactory) {
+                IConfigItemFactory factory = (IConfigItemFactory) parentElement;
+                List items = ConfigRegistry.getInstance().getAllConfigItems(factory.getExtensionPointId());
+                Collections.sort(items);
+                return items.toArray();
+            } else {
+                return null;
+            }
+        }
+
+        public Object getParent(Object element) {
+            if (element instanceof ConfigItem) {
+                ConfigItem item = (ConfigItem) element;
+                return ConfigRegistry.getInstance().getConfigItemFactory(item.getExtensionPointId());
+            } else {
+                return ConfigRegistry.getInstance();
+            }
+        }
+
+        public boolean hasChildren(Object element) {
+            return element instanceof IConfigItemFactory;
+        }
+
+        public Object[] getElements(Object inputElement) {
+            return ConfigRegistry.getInstance().getAllConfigItemFactories();
+        }
+
+        public void dispose() {
+        }
+
+        public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+        }
+        
+    }
+    
+    private static class MyLabelProvider extends LabelProvider {
+        
+        public String getText(Object element) {
+            if (element instanceof IConfigItemFactory) {
+                return ((IConfigItemFactory) element).getPluralName();
+            } else {
+                return ((ConfigItem) element).getName();
+            }
+        }
+    }
+
+    private IConfigListener configListener = new IConfigListener() {
+        public void configChanged(ConfigEvent e) {
+            treeViewer.refresh();
+        }
+        
+        public void configLoaded(ConfigEvent e) {
+            loadingLabel.dispose();
+            createTreeViewer();
+            parentControl.layout();
+        }
+    };
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/DoctypeFactory.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/DoctypeFactory.java
new file mode 100644
index 0000000..01744bf
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/DoctypeFactory.java
@@ -0,0 +1,107 @@
+/*******************************************************************************
+ * 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.ui.internal.config;
+
+import java.io.IOException;
+import java.net.URL;
+
+import org.eclipse.wst.xml.vex.core.internal.dom.DTDValidator;
+
+
+import com.wutka.dtd.DTDParseException;
+
+/**
+ * Factory for DocumentType objects.
+ */
+public class DoctypeFactory implements IConfigItemFactory {
+    
+    public IConfigElement[] createConfigurationElements(ConfigItem item) {
+        DocumentType doctype = (DocumentType) item;
+        ConfigurationElement doctypeElement = new ConfigurationElement(ELT_DOCTYPE);
+        doctypeElement.setAttribute(ATTR_PUBLIC_ID, doctype.getPublicId());
+        doctypeElement.setAttribute(ATTR_SYSTEM_ID, doctype.getSystemId());
+        doctypeElement.setAttribute(ATTR_DTD, doctype.getResourcePath());
+        doctypeElement.setAttribute(ATTR_OUTLINE_PROVIDER, doctype.getOutlineProvider());
+        
+        String[] names = doctype.getRootElements();
+        for (int i = 0; i < names.length; i++) {
+            String name = names[i];
+            ConfigurationElement rootElement = new ConfigurationElement(ELT_ROOT_ELEMENT);
+            rootElement.setAttribute(ATTR_NAME, name);
+            doctypeElement.addChild(rootElement);
+        }
+        
+        return new IConfigElement[] { doctypeElement };
+    }
+
+    public ConfigItem createItem(ConfigSource config, IConfigElement[] configElements) throws IOException {
+        if (configElements.length < 1) {
+            return null;
+        }
+        IConfigElement configElement = configElements[0];
+        DocumentType doctype = new DocumentType(config);
+        doctype.setPublicId(configElement.getAttribute(ATTR_PUBLIC_ID));
+        doctype.setSystemId(configElement.getAttribute(ATTR_SYSTEM_ID));
+        doctype.setResourcePath(configElement.getAttribute(ATTR_DTD));
+        doctype.setOutlineProvider(configElement.getAttribute(ATTR_OUTLINE_PROVIDER));
+
+        IConfigElement[] rootElementRefs = configElement.getChildren();
+        String[] rootElements = new String[rootElementRefs.length];
+        for (int i = 0; i < rootElementRefs.length; i++) {
+            rootElements[i] = rootElementRefs[i].getAttribute("name"); //$NON-NLS-1$
+        }
+        doctype.setRootElements(rootElements);
+
+        return doctype;
+    }
+
+    public String getExtensionPointId() {
+        return DocumentType.EXTENSION_POINT;
+    }
+
+    public String[] getFileExtensions() {
+        return EXTS;
+    }
+
+    public String getPluralName() {
+        return Messages.getString("DoctypeFactory.pluralName"); //$NON-NLS-1$
+    }
+
+    public Object parseResource(URL baseUrl, String resourcePath, IBuildProblemHandler problemHandler) throws IOException {
+        try {
+            return DTDValidator.create(new URL(baseUrl, resourcePath));
+        } catch (DTDParseException ex) {
+            if (problemHandler != null) {
+                BuildProblem problem = new BuildProblem();
+                problem.setSeverity(BuildProblem.SEVERITY_ERROR);
+                problem.setResourcePath(resourcePath);
+                problem.setMessage(ex.getMessage());
+                problem.setLineNumber(ex.getLineNumber());
+                problemHandler.foundProblem(problem);
+            }
+            throw ex;
+        }
+    }
+
+    //=================================================== PRIVATE
+    
+    private static final String[] EXTS = new String[] { "dtd" }; //$NON-NLS-1$
+
+    private static final String ELT_DOCTYPE = "doctype"; //$NON-NLS-1$
+    private static final String ATTR_OUTLINE_PROVIDER = "outlineProvider"; //$NON-NLS-1$
+    private static final String ATTR_DTD = "dtd"; //$NON-NLS-1$
+    private static final String ATTR_SYSTEM_ID = "systemId"; //$NON-NLS-1$
+    private static final String ATTR_PUBLIC_ID = "publicId"; //$NON-NLS-1$
+    
+    private static final String ELT_ROOT_ELEMENT = "rootElement"; //$NON-NLS-1$
+    private static final String ATTR_NAME = "name"; //$NON-NLS-1$
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/DoctypePropertyPage.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/DoctypePropertyPage.java
new file mode 100644
index 0000000..fb9108b
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/DoctypePropertyPage.java
@@ -0,0 +1,321 @@
+/*******************************************************************************
+ * 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.ui.internal.config;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableItem;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.ui.dialogs.PropertyPage;
+import org.eclipse.wst.xml.vex.core.internal.dom.Validator;
+import org.eclipse.wst.xml.vex.ui.internal.VexPlugin;
+
+/**
+ * Property page for .dtd files.
+ */
+public class DoctypePropertyPage extends PropertyPage {
+
+    protected Control createContents(final Composite parent) {
+
+        pane = new Composite(parent, SWT.NONE);
+
+        createPropertySheet();
+
+        configListener = new IConfigListener() {
+
+            public void configChanged(ConfigEvent e) {
+                
+                // This is fired when we open properties for a new doctype
+                // and we force it to be re-built to get a validator
+                // from which we get our list of prospective root elements.
+                
+            	String resourcePath = ((IFile) getElement()).getProjectRelativePath().toString();
+
+            	ConfigSource config = getPluginProject();
+
+            	doctype = (DocumentType) config.getItemForResource(resourcePath);
+
+                populateRootElements();
+            }
+
+            public void configLoaded(final ConfigEvent e) {
+                
+                setMessage(getTitle());
+                populateDoctype();
+                setValid(true);
+
+                try { // force an incremental build
+                    getPluginProject().writeConfigXml();
+                } catch (Exception ex) {
+                    String message = MessageFormat.format(
+                            Messages.getString("DoctypePropertyPage.errorWritingConfig"), //$NON-NLS-1$
+                            new Object[] { PluginProject.PLUGIN_XML });
+                    VexPlugin.getInstance().log(IStatus.ERROR, message, ex);
+                }
+
+            }
+        };
+
+        ConfigRegistry.getInstance().addConfigListener(configListener);
+
+        if (ConfigRegistry.getInstance().isConfigLoaded()) {
+
+            populateDoctype();
+            populateRootElements();
+
+        } else {
+
+            setValid(false);
+
+            setMessage(Messages.getString("DoctypePropertyPage.loading")); //$NON-NLS-1$
+
+        }
+
+        return pane;
+    }
+
+    private void createPropertySheet() {
+
+        GridLayout layout = new GridLayout();
+        layout.numColumns = 2;
+        pane.setLayout(layout);
+        GridData gd;
+
+        Label label;
+
+        label = new Label(pane, SWT.NONE);
+        label.setText(Messages.getString("DoctypePropertyPage.name")); //$NON-NLS-1$
+        this.nameText = new Text(pane, SWT.BORDER);
+        gd = new GridData();
+        gd.widthHint = NAME_WIDTH;
+        this.nameText.setLayoutData(gd);
+
+        label = new Label(pane, SWT.NONE);
+        label.setText(Messages.getString("DoctypePropertyPage.publicId")); //$NON-NLS-1$
+        this.publicIdText = new Text(pane, SWT.BORDER);
+        gd = new GridData();
+        gd.grabExcessHorizontalSpace = true;
+        gd.horizontalAlignment = GridData.FILL;
+        this.publicIdText.setLayoutData(gd);
+
+        label = new Label(pane, SWT.NONE);
+        label.setText(Messages.getString("DoctypePropertyPage.systemId")); //$NON-NLS-1$
+        this.systemIdText = new Text(pane, SWT.BORDER);
+        gd = new GridData();
+        gd.grabExcessHorizontalSpace = true;
+        gd.horizontalAlignment = GridData.FILL;
+        this.systemIdText.setLayoutData(gd);
+
+        final String resourcePath = ((IFile) this.getElement())
+                .getProjectRelativePath().toString();
+
+        final ConfigSource config = this.getPluginProject();
+
+        this.doctype = (DocumentType) config.getItemForResource(resourcePath);
+        if (this.doctype == null) {
+            this.doctype = new DocumentType(config);
+            this.doctype.setResourcePath(resourcePath);
+            config.addItem(this.doctype);
+        }
+
+        // Generate a simple ID for this one if necessary
+        if (this.doctype.getSimpleId() == null
+                || this.doctype.getSimpleId().length() == 0) {
+            this.doctype.setSimpleId(this.doctype.generateSimpleId());
+        }
+
+        // need to do GridLayout and GridData for this guy them fill with items
+
+        label = new Label(pane, SWT.NONE);
+        label.setText(Messages.getString("DoctypePropertyPage.rootElements")); //$NON-NLS-1$
+
+        gd = new GridData();
+        //gd.widthHint = COLUMN_1_WIDTH;
+        gd.verticalAlignment = GridData.BEGINNING;
+        gd.horizontalSpan = 2;
+        label.setLayoutData(gd);
+
+        final Composite tablePane = new Composite(pane, SWT.BORDER);
+
+        gd = new GridData(GridData.FILL_BOTH);
+        gd.heightHint = 200;
+        gd.horizontalSpan = 2;
+        tablePane.setLayoutData(gd);
+
+        final FillLayout fillLayout = new FillLayout();
+        tablePane.setLayout(fillLayout);
+
+        this.rootElementsTable = new Table(tablePane, SWT.CHECK);
+    }
+
+    /**
+     * Returns the PluginProject associated with this resource.
+     * 
+     * @return
+     */
+    public PluginProject getPluginProject() {
+        IFile file = (IFile) this.getElement();
+        return PluginProject.get(file.getProject());
+    }
+
+    public boolean performOk() {
+
+        performApply();
+
+        return super.performOk();
+    }
+
+    public void performApply() {
+
+        this.doctype.setName(this.nameText.getText());
+        this.doctype.setPublicId(this.publicIdText.getText());
+        this.doctype.setSystemId(this.systemIdText.getText());
+
+        // collect root Elements from the rootElementsTable
+
+        final TableItem[] tia = this.rootElementsTable.getItems();
+
+        final ArrayList selectedRootElements = new ArrayList();
+
+        for (int i = 0; i < tia.length; i++) {
+            if (tia[i].getChecked()) {
+                selectedRootElements.add(tia[i].getText());
+            }
+        }
+
+        final String[] selectedRootElementsArray = new String[selectedRootElements
+                .size()];
+
+        for (int i = 0; i < selectedRootElementsArray.length; i++) {
+            selectedRootElementsArray[i] = (String) selectedRootElements.get(i);
+        }
+
+        this.doctype.setRootElements(selectedRootElementsArray);
+
+        try {
+            this.getPluginProject().writeConfigXml();
+        } catch (Exception ex) {
+            String message = MessageFormat.format(
+                    Messages.getString("DoctypePropertyPage.errorWritingConfig"), //$NON-NLS-1$
+                    new Object[] { PluginProject.PLUGIN_XML });
+            VexPlugin.getInstance().log(IStatus.ERROR, message, ex);
+        }
+
+        ConfigRegistry.getInstance().fireConfigChanged(new ConfigEvent(this));
+    }
+
+    public void performDefaults() {
+
+        super.performDefaults();
+
+        populateDoctype();
+
+        populateRootElements();
+    }
+
+    public void dispose() {
+        super.dispose();
+
+        if (this.configListener != null) {
+            ConfigRegistry.getInstance().removeConfigListener(
+                    this.configListener);
+        }
+    }
+
+    //======================================================= PRIVATE
+
+    private DocumentType doctype;
+
+    private static final int NAME_WIDTH = 150;
+
+    private Composite pane;
+
+    private Text nameText;
+
+    private Text publicIdText;
+
+    private Text systemIdText;
+
+    private Table rootElementsTable;
+
+    private IConfigListener configListener;
+
+    private void populateDoctype() {
+        this.setText(this.nameText, this.doctype.getName());
+        this.setText(this.publicIdText, this.doctype.getPublicId());
+        this.setText(this.systemIdText, this.doctype.getSystemId());
+    }
+
+    /*
+     *  
+     */
+
+    private void populateRootElements() {
+
+        final String resourcePath = ((IFile) this.getElement())
+                .getProjectRelativePath().toString();
+
+        final Validator validator = (Validator) ((ConfigSource) this
+                .getPluginProject()).getParsedResource(resourcePath);
+
+        if (validator != null) {
+
+            final List list = Arrays.asList(doctype.getRootElements());
+            final Set selectedRootElements = new TreeSet(list);
+
+            rootElementsTable.removeAll();
+
+            final java.util.List l = new ArrayList(validator
+                    .getValidRootElements());
+            Collections.sort(l);
+            for (int i = 0; i < l.size(); i++) {
+
+                TableItem item1 = new TableItem(rootElementsTable, SWT.NONE);
+                item1.setText((String) l.get(i));
+
+                if (selectedRootElements.contains((String) l.get(i))) {
+                    item1.setChecked(true);
+                }
+            }
+        } else {
+
+            try {
+                this.getPluginProject().writeConfigXml();
+            } catch (Exception ex) {
+                String message = MessageFormat.format(
+                        Messages.getString("DoctypePropertyPage.errorWritingConfig"), //$NON-NLS-1$
+                        new Object[] { PluginProject.PLUGIN_XML });
+                VexPlugin.getInstance().log(IStatus.ERROR, message, ex);
+            }
+        }
+    }
+
+    private void setText(Text textBox, String s) {
+        textBox.setText(s == null ? "" : s); //$NON-NLS-1$
+    }
+}
\ No newline at end of file
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/DocumentType.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/DocumentType.java
new file mode 100644
index 0000000..68dbb06
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/DocumentType.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.ui.internal.config;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.wst.xml.vex.core.internal.dom.Validator;
+import org.eclipse.wst.xml.vex.ui.internal.editor.VexEditor;
+
+
+/**
+ * A registered document type.
+ */
+public class DocumentType extends ConfigItem implements Comparable {
+    
+    public static final String EXTENSION_POINT = "org.eclipse.wst.xml.vex.ui.doctypes"; //$NON-NLS-1$
+    
+    public DocumentType(ConfigSource config) {
+        super(config);
+    }
+    
+    /**
+     * Return a DocumentType for the given publicId. Returns null if no
+     * document type was found that matches the public ID.
+     * @param publicId Public ID for which to search.
+     */
+    public static DocumentType getDocumentType(String publicId) {
+        ConfigRegistry registry = ConfigRegistry.getInstance();
+        List doctypes = registry.getAllConfigItems(DocumentType.EXTENSION_POINT);
+        for (Iterator it = doctypes.iterator(); it.hasNext(); ) {
+            DocumentType doctype = (DocumentType) it.next();
+            if (doctype.getPublicId().equals(publicId)) {
+                return doctype;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Return a list of document types for which there is at least one
+     * registered style.
+     */
+    public static DocumentType[] getDocumentTypesWithStyles() {
+        // TODO quite inefficent, try caching results, clearing the cache upon config changes.
+        ConfigRegistry registry = ConfigRegistry.getInstance();
+        List withStyles = new ArrayList();
+        List doctypes = registry.getAllConfigItems(DocumentType.EXTENSION_POINT);
+        for (Iterator it = doctypes.iterator(); it.hasNext();) {
+            DocumentType doctype = (DocumentType) it.next();
+            if (VexEditor.findStyleForDoctype(doctype.getPublicId()) != null) {
+                withStyles.add(doctype);
+            }
+        }
+        return (DocumentType[]) withStyles.toArray(new DocumentType[withStyles.size()]);
+    }
+    
+
+    /**
+     * Returns the name of the class that generates an outline for this
+     * document type. This class must implement
+     * org.eclipse.ui.views.contentoutline.IContentOutlinePage. Normally,
+     * classes will extend net.sf.vex.editor.AbstractContentOutlinePage.
+     * Returns null if this document type was not supplied by a plugin,
+     * or if the the contentOutlinePage attribute was not set.
+     */
+    public String getOutlineProvider() {
+        return outlineProvider;
+    }
+    
+    /**
+     * Returns the public ID of the document type.
+     */
+    public String getPublicId() {
+        return publicId;
+    }
+
+    /**
+     * Returns the system ID of the document type.
+     */
+    public String getSystemId() {
+        return systemId;
+    }
+
+    public String getExtensionPointId() {
+        return EXTENSION_POINT;
+    }
+    
+    /**
+     * Sets the name of the class that defines the content outline of the
+     * document.
+     * @param contentOutlinePage Name of a class implementing IContentOutlinePage.
+     */
+    public void setOutlineProvider(String contentOutlinePage) {
+        this.outlineProvider = contentOutlinePage;
+    }
+
+    /**
+     * Sets the public ID of the document type. The public ID is the unique
+     * identifier of the document type.
+     * @param publicId new public ID of the document type.
+     */
+    public void setPublicId(String publicId) {
+        this.publicId = publicId;
+    }
+    
+    /**
+     * Sets the system ID of the document type. This is used when creating
+     * new documents but ignored otherwise.
+     * @param systemId new system ID for the document type.
+     */
+    public void setSystemId(String systemId) {
+        this.systemId = systemId;
+    }
+    
+    public Validator getValidator() {
+        return (Validator) this.getConfig().getParsedResource(this.getResourcePath());
+    }
+    
+    public boolean isValid() {
+        return super.isValid() && 
+            !isBlank(publicId) &&
+            !isBlank(systemId) &&
+            this.getValidator() != null;
+    }
+    
+    public String toString() {
+        return this.getName();
+    }
+
+    /**
+     * Returns a list of valid root elements for this document type. If
+     * no root elements have been declared, returns an empty array.
+     */
+    public String[] getRootElements() {
+        return rootElements;
+    }
+
+    /**
+     * Sets the list of valid root elements for this document type.
+     */
+    public void setRootElements(String[] rootElements) {
+        if (rootElements == null) {
+            throw new IllegalArgumentException();
+        }
+        this.rootElements = rootElements;
+    }
+
+    //==================================================== PRIVATE
+    
+    private static final String[] EMPTY_STRING_ARRAY = new String[0];
+    
+    private String publicId;
+    private String systemId;
+    private String outlineProvider;
+    private String[] rootElements = EMPTY_STRING_ARRAY;
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/DomConfigurationElement.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/DomConfigurationElement.java
new file mode 100644
index 0000000..7f8efff
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/DomConfigurationElement.java
@@ -0,0 +1,90 @@
+/*******************************************************************************
+ * 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.ui.internal.config;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.w3c.dom.Text;
+
+/**
+ * Implements IConfigurationElement against a W3C DOM Element object.
+ */
+public class DomConfigurationElement implements IConfigElement {
+
+    public DomConfigurationElement(Element element) {
+        this.element = element;
+    }
+    
+    public String getAttribute(String name) {
+        return this.element.getAttribute(name); // TODO translate from resource bundle
+    }
+
+    public String[] getAttributeNames() {
+        int n = this.element.getAttributes().getLength();
+        String[] names = new String[n];
+        for (int i = 0; i < n; i++) {
+            Node node = this.element.getAttributes().item(i);
+            names[i] = node.getLocalName();
+        }
+        return names;
+    }
+
+    public IConfigElement[] getChildren() {
+        return this.getChildren(null);
+    }
+
+    public IConfigElement[] getChildren(String name) {
+        List children = new ArrayList();
+        NodeList list = this.element.getChildNodes();
+        for (int i = 0; i < list.getLength(); i++) {
+            Node node = list.item(i);
+            if (node instanceof Element) {
+                if (name == null || name.equals(node.getNodeName())) {
+                    children.add(node);
+                }
+            }
+        }
+        
+        int n = children.size();
+        IConfigElement[] childArray = new IConfigElement[n];
+        for (int i = 0; i < n; i++) {
+            childArray[i] = new DomConfigurationElement((Element) children.get(i));
+        }
+        
+        return childArray;
+    }
+
+    public String getName() {
+        return element.getLocalName();
+    }
+
+    public String getValue() {
+        StringBuffer sb = new StringBuffer();
+        
+        NodeList list = this.element.getChildNodes();
+        for (int i = 0; i < list.getLength(); i++) {
+            Node node = list.item(i);
+            if (node instanceof Text) {
+                sb.append(node.getNodeValue());
+            }
+        }
+        return sb.toString();
+    }
+
+    //===================================================== PRIVATE
+    
+    private Element element;
+    
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/IBuildProblemHandler.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/IBuildProblemHandler.java
new file mode 100644
index 0000000..ff7f450
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/IBuildProblemHandler.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.ui.internal.config;
+
+/**
+ * Interface through which a resource parser notifies an interested party
+ * of build problems.
+ */
+public interface IBuildProblemHandler {
+
+    /**
+     * Called by the parser when a problem is found while parsing a resource.
+     * @param problem Details of the problem.
+     */
+    public void foundProblem(BuildProblem problem);
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/IConfigElement.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/IConfigElement.java
new file mode 100644
index 0000000..ba54de2
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/IConfigElement.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.ui.internal.config;
+
+
+/**
+ * Represents the XML element for a Vex config item in plugin.xml.
+ * Vex-specific replacement for the Eclipse IConfigurationElement class. 
+ * We need this because we are not supposed to implement IConfigurationElement,
+ * and in fact it changed and broke us from Eclipse 3.0 -> 3.1.
+ */
+public interface IConfigElement {
+
+    /**
+     * Returns the value of the given attribute.
+     * @param name Name of the attribute for which to return a name.
+     * @return
+     */
+    public String getAttribute(String name);
+
+    /**
+     * Returns an array of all the attributes defined by this element.
+     */
+    public String[] getAttributeNames();
+
+    /**
+     * Returns an array of the children of this element.
+     */
+    public IConfigElement[] getChildren();
+
+    /**
+     * Returns an array of the children of this element with the given name.
+     * @param name Name of children to search for.
+     */
+    public IConfigElement[] getChildren(String name);
+
+    /**
+     * Returns the name of this element.
+     */
+    public String getName();
+
+    /**
+     * Returns the value of this element.
+     */
+    public String getValue();
+}
\ No newline at end of file
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/IConfigItemFactory.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/IConfigItemFactory.java
new file mode 100644
index 0000000..d384db2
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/IConfigItemFactory.java
@@ -0,0 +1,75 @@
+/*******************************************************************************
+ * 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.ui.internal.config;
+
+import java.io.IOException;
+import java.net.URL;
+
+/**
+ * Manager of a set of configuration items and their associated resources.
+ * One concrete implementation of this interface will exist for each kind
+ * of configuration item, e.g. a DoctypeManager, StyleManager, etc.
+ * Further, each Vex plugin and Vex plugin project will have one of
+ * each kind of manager. All managers are registered with the VexPlugin
+ * class.
+ */
+public interface IConfigItemFactory {
+
+    /**
+     * Returns an array of configuration elements needed to re-create the given
+     * item. If no configuration elements are necessary, the method may return
+     * null instead of an empty array. This is essentially the inverse of
+     * the createItem method.
+     * 
+     * @param item ConfigItem for which to create configuration elements.
+     */
+    public IConfigElement[] createConfigurationElements(ConfigItem item);
+
+    /**
+     * Creates an item and adds it to the given configuration. 
+     * @param config Configuration that owns the item.
+     * @param configElements Details of the configuration item from the
+     * plugin manifest.
+     */
+    public ConfigItem createItem(ConfigSource config, IConfigElement[] configElements) throws IOException;
+    
+    /**
+     * Returns the ID of the extension point that defines this type
+     * of configuration item.
+     */
+    public String getExtensionPointId();
+    
+    /**
+     * Returns an array of file extension for resources that apply to this type
+     * of configuration item. The returned strings should <i>not</i> have 
+     * leading dots.
+     */
+    public String[] getFileExtensions();
+    
+    /**
+     * Returns the pluralized name of the type of configuration item managed
+     * by this factory. For example, "Document Types".
+     * @return
+     */
+    public String getPluralName();
+    
+    /**
+     * Parse a resource for this type of configuration item. Implementations
+     * must not fail if passed a null problem handler.
+     * 
+     * @param baseUrl Base URL of the project or plugin containing the resource.
+     * @param resourcePath Path of the resource relative to the base URL.
+     * @param problemHandler Problem handler, or null if the caller does
+     * not require build problem reporting.
+     */
+    public Object parseResource(URL baseUrl, String resourcePath, IBuildProblemHandler problemHandler) throws IOException;
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/IConfigListener.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/IConfigListener.java
new file mode 100644
index 0000000..42ecab4
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/IConfigListener.java
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * 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.ui.internal.config;
+
+import java.util.EventListener;
+
+/**
+ * Interface through which Vex notifies UI components that configuration
+ * items such as doctypes and styles have been added, removed, or changed. 
+ * Implementations of this 
+ * interface should be registered with the VexPlugin instance. All calls to
+ * implementations occur on the UI thread.
+ */
+public interface IConfigListener extends EventListener {
+	
+    /**
+     * Called when one or more configuration items are added, removed, or
+     * changed. 
+     * @param e ConfigEvent containing details of the change.
+     */
+    public void configChanged(ConfigEvent e);
+    
+    /**
+     * Called when the Vex configuration is first loaded by the ConfigLoaderJob.
+     * This method is guaranteed to be called before the first call to
+     * configChanged.
+     * @param e ConfigEvent containing details of the change.
+     */
+    public void configLoaded(ConfigEvent e);
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/Messages.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/Messages.java
new file mode 100644
index 0000000..80e8c34
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/Messages.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.ui.internal.config;
+
+import java.text.MessageFormat;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.wst.xml.vex.ui.internal.VexPlugin;
+
+/**
+ * Handler for language-specific strings in Vex.
+ */
+public class Messages {
+
+    private static ResourceBundle resources;
+
+    private Messages() {
+    }
+
+    /**
+     * Returns the language-specific string for the given key,
+     * or the key itself if not found.
+     */
+    public static String getString(String key) {
+        if (resources == null) {
+            resources = ResourceBundle.getBundle("org.eclipse.wst.xml.vex.ui.internal.config.messages"); //$NON-NLS-1$
+        }
+        
+        try {
+            return resources.getString(key);
+        } catch (MissingResourceException ex) {
+            String message = Messages.getString("Messages.cantFindResource"); //$NON-NLS-1$
+            VexPlugin.getInstance().log(IStatus.WARNING,
+                    MessageFormat.format(message, new Object[] { key }));
+            return key;
+        }
+    }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/NewPluginProjectWizard.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/NewPluginProjectWizard.java
new file mode 100644
index 0000000..6d45c1d
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/NewPluginProjectWizard.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.ui.internal.config;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IProjectDescription;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.ide.IDE;
+import org.eclipse.ui.wizards.newresource.BasicNewProjectResourceWizard;
+import org.eclipse.wst.xml.vex.ui.internal.VexPlugin;
+
+/**
+ * Wizard for creating a new Vex Plugin Project.
+ */
+public class NewPluginProjectWizard extends BasicNewProjectResourceWizard {
+
+    public void init(IWorkbench workbench, IStructuredSelection currentSelection) {
+        super.init(workbench, currentSelection);
+
+        this.setWindowTitle(Messages.getString("NewPluginProjectWizard.title")); //$NON-NLS-1$
+    }
+    
+    public boolean performFinish() {
+        
+        boolean success = super.performFinish();
+        if (success) {
+            try {
+                this.createVexPluginXml();
+                this.registerVexPluginNature();
+                PluginProject.load(this.getNewProject());
+            } catch (CoreException e) {
+                VexPlugin.getInstance().log(IStatus.ERROR, Messages.getString("NewPluginProjectWizard.createError"), e); //$NON-NLS-1$
+                success = false;
+            }
+            
+            
+        }
+        
+        return success;
+    }
+    
+    //====================================================== PRIVATE
+    
+    private void createVexPluginXml() throws CoreException {
+
+        IProject project = this.getNewProject();
+        
+        ByteArrayOutputStream baos;
+        PrintStream out;
+
+        baos = new ByteArrayOutputStream();
+        out = new PrintStream(baos);
+
+        out.println("<?xml version='1.0'?>"); //$NON-NLS-1$
+        out.println("<plugin>"); //$NON-NLS-1$
+        out.println("</plugin>"); //$NON-NLS-1$
+        out.close();
+        
+        IFile pluginXml = project.getFile(PluginProject.PLUGIN_XML);
+        pluginXml.create(new ByteArrayInputStream(baos.toByteArray()), true, null);
+        
+        // By default open the Default Text Editor for vex-plugin.xml.
+        // This isn't perfect, because the Vex icon is still shown, but
+        // it'll do until we create a custom editor.
+        IDE.setDefaultEditor(pluginXml, "org.eclipse.ui.DefaultTextEditor"); //$NON-NLS-1$
+    }
+    
+    private void registerVexPluginNature() throws CoreException {
+        IProject project = this.getNewProject();
+        IProjectDescription description = project.getDescription();
+        String[] natures = description.getNatureIds();
+        String[] newNatures = new String[natures.length + 1];
+        System.arraycopy(natures, 0, newNatures, 0, natures.length);
+        newNatures[natures.length] = PluginProjectNature.ID;
+        description.setNatureIds(newNatures);
+        project.setDescription(description, null);
+    }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/PluginProject.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/PluginProject.java
new file mode 100644
index 0000000..d91c653
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/PluginProject.java
@@ -0,0 +1,322 @@
+/*******************************************************************************
+ * 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.ui.internal.config;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.FactoryConfigurationError;
+import javax.xml.parsers.ParserConfigurationException;
+
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IncrementalProjectBuilder;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentWriter;
+import org.eclipse.wst.xml.vex.ui.internal.VexPlugin;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+/**
+ * Represents a Vex plugin project.
+ */
+public class PluginProject extends ConfigSource {
+
+    public static final String PLUGIN_XML = "vex-plugin.xml"; //$NON-NLS-1$
+    public static final String PROJECT_CONFIG_SER = ".vexConfig.ser"; //$NON-NLS-1$
+
+    /**
+     * Class constructor.
+     * @param config VexConfiguration associated with this project. 
+     */
+    protected PluginProject(IProject project) {
+        this.projectPath = project.getFullPath().toString();
+    }
+    
+    /**
+     * Remove the .vexConfig.ser state in which the project state is stored.
+     */
+    public void cleanState() throws CoreException {
+        IFile configSer = this.getProject().getFile(PROJECT_CONFIG_SER);
+        configSer.delete(true, null);
+    }
+    
+    /**
+     * Factory method that returns the plugin project for the given
+     * IProject. If the given project does not have the Vex plugin
+     * project nature, null is returned. PluginProject instances are 
+     * cached so they can be efficiently returned.
+     * 
+     * @param project IProject for which to return the PluginProject.
+     * @return
+     * @throws CoreException
+     */
+    public static PluginProject get(IProject project) {
+        
+        List sources = ConfigRegistry.getInstance().getAllConfigSources();
+        for (Iterator it = sources.iterator(); it.hasNext();) {
+            ConfigSource source = (ConfigSource) it.next();
+            if (source instanceof PluginProject) {
+                if (project.equals(((PluginProject) source).getProject())) {
+                    return (PluginProject) source;
+                }
+            }
+        }
+        return null;
+    }
+
+    public URL getBaseUrl() {
+        try {
+            return this.getProject().getLocation().toFile().toURL();
+        } catch (MalformedURLException e) {
+            throw new RuntimeException(Messages.getString("PluginProject.malformedUrl"), e); //$NON-NLS-1$
+        }
+    }
+    /**
+     * Returns the IProject associated with this plugin project.
+     */
+    public IProject getProject() {
+        return ResourcesPlugin.getWorkspace().getRoot().getProject(this.projectPath);
+    }
+    
+    /**
+     * Loads the project from it's serialized state file and registers it with
+     * the ConfigRegistry. If the serialized state cannot be loaded, a new
+     * PluginProject is created and the builder is launched.
+     */
+    public static PluginProject load(IProject project) {
+        
+        try {
+            if (!project.isOpen() || !project.hasNature(PluginProjectNature.ID)) {
+                String message = MessageFormat.format(
+                        Messages.getString("PluginProject.notPluginProject"), //$NON-NLS-1$
+                        new Object[] { project.getName() });
+                throw new IllegalArgumentException(message);
+            }
+        } catch (CoreException e) {
+            String message = MessageFormat.format(
+                    Messages.getString("PluginProject.notPluginProject"), //$NON-NLS-1$
+                    new Object[] { project.getName() });
+            throw new IllegalArgumentException(message);
+        }
+        
+        IFile serFile = project.getFile(PROJECT_CONFIG_SER);
+        
+        PluginProject pluginProject = null;
+        if (serFile.exists()) {
+            try {
+                ObjectInputStream ois = new ObjectInputStream(serFile.getContents());
+                pluginProject = (PluginProject) ois.readObject();
+            } catch (Exception ex) {
+                String message = MessageFormat.format(
+                        Messages.getString("PluginProject.loadingError"), //$NON-NLS-1$
+                        new Object[] { serFile });
+                VexPlugin.getInstance().log(IStatus.WARNING, message, ex);
+            }
+        }
+        
+        boolean rebuild = false;
+        
+        if (pluginProject == null) {
+            rebuild = true;
+            pluginProject = new PluginProject(project);
+        }
+            
+        ConfigRegistry registry = ConfigRegistry.getInstance();
+        registry.addConfigSource(pluginProject);
+        registry.fireConfigChanged(new ConfigEvent(PluginProject.class));
+
+        if (rebuild) {
+            try {
+                project.build(IncrementalProjectBuilder.FULL_BUILD, null);
+
+            } catch (Exception ex) {
+                String message = MessageFormat.format(
+                        Messages.getString("PluginProject.buildError"), //$NON-NLS-1$
+                        new Object[] { project.getName() });
+                VexPlugin.getInstance().log(IStatus.ERROR, message, ex);
+            }
+        }
+
+        return pluginProject;
+    }
+    
+    /**
+     * Re-parses the vex-plugin.xml file.
+     */
+    public void parseConfigXml() throws SAXException, IOException {
+        
+        DocumentBuilder builder;
+        try {
+            builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
+        } catch (ParserConfigurationException e) {
+            throw new RuntimeException(e);
+        } catch (FactoryConfigurationError e) {
+            throw new RuntimeException(e);
+        }
+
+        this.removeAllItems();
+
+        URL url = new URL(this.getBaseUrl(), PluginProject.PLUGIN_XML);
+        Document doc = builder.parse(url.toString());
+        
+        Element root = doc.getDocumentElement();
+        
+        this.setUniqueIdentifer(root.getAttribute("id")); //$NON-NLS-1$
+        
+        NodeList nodeList = doc.getElementsByTagName("extension"); //$NON-NLS-1$
+        
+        for (int i = 0; i < nodeList.getLength(); i++) {
+            Element element = (Element) nodeList.item(i);
+            String extPoint = element.getAttribute("point"); //$NON-NLS-1$
+            String id = element.getAttribute("id"); //$NON-NLS-1$
+            String name = element.getAttribute("name"); //$NON-NLS-1$
+            
+            List configElementList = new ArrayList();
+            NodeList childList = element.getChildNodes();
+            for (int j = 0; j < childList.getLength(); j++) {
+                Node child = childList.item(j);
+                if (child instanceof Element) {
+                    configElementList.add(child);
+                }
+            }
+            
+            IConfigElement[] configElements = new IConfigElement[configElementList.size()];
+            for (int j = 0; j < configElementList.size(); j++) {
+                configElements[j] = new DomConfigurationElement((Element) configElementList.get(j));
+            }
+            
+            this.addItem(extPoint, id, name, configElements);
+        }
+
+    }
+
+    /**
+     * Saves the state of this project into .vexConfig.ser.
+     */
+    public void saveState() throws CoreException, IOException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        ObjectOutputStream oos = new ObjectOutputStream(baos);
+        oos.writeObject(this);
+        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        IFile configSer = this.getProject().getFile(PROJECT_CONFIG_SER);
+        if (configSer.exists()) {
+            configSer.setContents(bais, true, false, null);
+        } else {
+            configSer.create(bais, true, null);
+            configSer.setDerived(true);
+        }
+
+    }
+
+    /**
+     * Writes this configuraton to the file vex-config.xml in the project.
+     */
+    public void writeConfigXml() throws CoreException, IOException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        PrintWriter out = new PrintWriter(new OutputStreamWriter(baos, "utf-8")); //$NON-NLS-1$
+
+        ConfigurationElement root = new ConfigurationElement("plugin"); //$NON-NLS-1$
+        for (Iterator it = this.getAllItems().iterator(); it.hasNext();) {
+            ConfigItem item = (ConfigItem) it.next();
+            ConfigurationElement extElement = new ConfigurationElement("extension"); //$NON-NLS-1$
+            extElement.setAttribute("id", item.getSimpleId()); //$NON-NLS-1$
+            extElement.setAttribute("name", item.getName()); //$NON-NLS-1$
+            extElement.setAttribute("point", item.getExtensionPointId()); //$NON-NLS-1$
+            IConfigItemFactory factory = ConfigRegistry.getInstance().getConfigItemFactory(item.getExtensionPointId());
+            extElement.setChildren(factory.createConfigurationElements(item));
+            root.addChild(extElement);
+        }
+        writeElement(root, out, 0);
+
+        out.close();
+        
+        InputStream inputStream = new ByteArrayInputStream(baos.toByteArray());
+        
+        IFile file = this.getProject().getFile(PLUGIN_XML);
+        if (file.exists()) {
+            file.setContents(inputStream, true, false, null);
+        } else {
+            file.create(inputStream, true, null);
+        }
+    }
+
+    //=========================================================== PRIVATE
+    
+    private String projectPath;
+    /** Filename used when serializing in a Vex plugin project */
+    public static final String SER_FILE = ".vexConfig.ser"; //$NON-NLS-1$
+
+    private static void writeElement(IConfigElement element, PrintWriter out, int level) {
+        StringBuffer elementIndent = new StringBuffer();
+        for (int i = 0; i < level; i++) {
+            elementIndent.append("  "); //$NON-NLS-1$
+        }
+        StringBuffer elementPrefix = new StringBuffer();
+        elementPrefix.append("<"); //$NON-NLS-1$
+        elementPrefix.append(element.getName());
+        
+        StringBuffer attributeIndent = new StringBuffer(elementIndent.toString());
+        for (int i = 0; i < elementPrefix.length() + 1; i++) {
+            attributeIndent.append(" "); //$NON-NLS-1$
+        }
+        
+        out.print(elementIndent.toString());
+        out.print(elementPrefix.toString());
+        String[] attributeNames = element.getAttributeNames();
+        for (int i = 0; i < attributeNames.length; i++) {
+            String attributeName = attributeNames[i];
+            if (i > 0) {
+                out.println();
+                out.print(attributeIndent);
+            } else {
+                out.print(" "); //$NON-NLS-1$
+            }
+            
+            out.print(attributeName);
+            out.print("=\""); //$NON-NLS-1$
+            out.print(DocumentWriter.escape(element.getAttribute(attributeName)));
+            out.print("\""); //$NON-NLS-1$
+        }
+        out.println(">"); //$NON-NLS-1$
+        
+        IConfigElement[] children = element.getChildren();
+        for (int i = 0; i < children.length; i++) {
+            writeElement(children[i], out, level+1);
+        }
+        
+        out.print(elementIndent);
+        out.print("</"); //$NON-NLS-1$
+        out.print(element.getName());
+        out.println(">"); //$NON-NLS-1$
+    }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/PluginProjectBuilder.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/PluginProjectBuilder.java
new file mode 100644
index 0000000..bba01a3
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/PluginProjectBuilder.java
@@ -0,0 +1,248 @@
+/*******************************************************************************
+ * 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.ui.internal.config;
+
+import java.io.IOException;
+import java.text.MessageFormat;
+import java.util.Map;
+
+
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceDelta;
+import org.eclipse.core.resources.IResourceDeltaVisitor;
+import org.eclipse.core.resources.IncrementalProjectBuilder;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.ui.IDecoratorManager;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.wst.xml.vex.ui.internal.VexPlugin;
+import org.xml.sax.SAXParseException;
+
+/**
+ * Parses and registers Vex configuration objects in a Vex Plug-in project.
+ */
+public class PluginProjectBuilder extends IncrementalProjectBuilder {
+
+    public static final String ID = "org.eclipse.wst.xml.vex.ui.pluginBuilder"; //$NON-NLS-1$
+
+    public PluginProjectBuilder() {
+    }
+    
+    protected IProject[] build(int kind, Map args, IProgressMonitor monitor)
+            throws CoreException {
+
+        IProject project = this.getProject();
+        
+        final PluginProject pluginProject = PluginProject.get(project);
+
+        if (pluginProject == null) {
+            String message = MessageFormat.format(
+                    Messages.getString("PluginProjectBuilder.notConfigSource"), //$NON-NLS-1$
+                    new Object[] { project.getName() });
+            VexPlugin.getInstance().log(IStatus.ERROR, message);
+            return null;
+        }
+        
+        boolean parseConfigXml;
+
+        IResourceDelta delta = this.getDelta(project);
+
+        if (kind == FULL_BUILD || delta == null) {
+            
+            //System.out.println("PluginProjectBuilder.build (full) starts for project " + project.getName());
+            
+            this.clean(null);
+            parseConfigXml = true;
+            
+        } else { // incremental or auto build
+
+            //System.out.println("PluginProjectBuilder.build (incremental) starts for project " + project.getName());
+            
+            parseConfigXml = (delta.findMember(new Path(PluginProject.PLUGIN_XML)) != null);
+
+            // If a resource is deleted, renamed, or moved, we'll update the
+            // config, but only if we're not going to parse it.
+            final boolean canUpdateConfig = !parseConfigXml;
+            
+            IResourceDeltaVisitor visitor = new IResourceDeltaVisitor() {
+                public boolean visit(IResourceDelta delta) throws CoreException {
+                    IResource resource = delta.getResource();
+                    String path = resource.getProjectRelativePath().toString();
+                    pluginProject.removeResource(path);
+
+                    if (delta.getKind() == IResourceDelta.REMOVED) {
+                        
+                        ConfigItem item = pluginProject.getItemForResource(path);
+                        
+                        if (item == null) {
+                            return true;
+                        }
+                        
+                        if (canUpdateConfig && (delta.getFlags() & IResourceDelta.MOVED_TO) > 0) {
+                            // Resource was moved.
+                            String newPath = delta.getMovedToPath().removeFirstSegments(1).toString();
+                            item.setResourcePath(newPath);
+                        } else {
+                            // Resource deleted, so let's nuke the item from the config
+                            pluginProject.remove(item);
+                        }
+                        
+                        try {
+                            pluginProject.writeConfigXml();
+                        } catch (Exception ex) {
+                            String message = MessageFormat.format(
+                                    Messages.getString("PluginProjectBuilder.cantSaveFile"), //$NON-NLS-1$
+                                    new Object[] { PluginProject.PLUGIN_XML });
+
+                            VexPlugin.getInstance().log(IStatus.ERROR, message, ex);
+                        }
+                        
+                        try {
+                            // If auto-build is on this is unnecessary since
+                            // another build will be triggered by us saving
+                            // vex-plugin.xml above. This is just here in case 
+                            // we're not auto-building
+                            pluginProject.saveState();
+                        } catch (Exception ex) {
+                            String message = MessageFormat.format(
+                                    Messages.getString("PluginProjectBuilder.cantSaveFile"), //$NON-NLS-1$
+                                    new Object[] { PluginProject.PROJECT_CONFIG_SER });
+                            VexPlugin.getInstance().log(IStatus.WARNING, message, ex);
+                        }
+                    }
+                    
+                    return true;
+                }
+            };
+            
+            delta.accept(visitor);
+        }
+
+        IMarker[] oldMarkers = project.findMarkers(IMarker.PROBLEM, true, IResource.DEPTH_INFINITE);
+        IResource[] markedResources = new IResource[oldMarkers.length];
+        for (int i = 0; i < markedResources.length; i++) {
+            markedResources[i] = oldMarkers[i].getResource();
+        }
+        
+        project.deleteMarkers(IMarker.PROBLEM, true, IResource.DEPTH_INFINITE);
+        this.getBuildProblemDecorator().update(markedResources);
+
+        ConfigRegistry registry = ConfigRegistry.getInstance();
+        
+        try {
+            registry.lock();
+
+            if (parseConfigXml) {
+                IResource pluginXmlResource = this.getProject().getFile(PluginProject.PLUGIN_XML);
+                try {
+                    if (pluginXmlResource.exists()) {
+                        pluginProject.parseConfigXml();
+                    } else {
+                        pluginProject.removeAllItems();
+                        String message = MessageFormat.format(
+                                Messages.getString("PluginProjectBuilder.missingFile"), //$NON-NLS-1$
+                                new Object[] { PluginProject.PLUGIN_XML });
+                        this.flagError(this.getProject(), message);
+                    }
+                } catch (SAXParseException ex) {
+                    this.flagError(pluginXmlResource, ex.getLocalizedMessage(), ex.getLineNumber());
+                } catch (Exception ex) {
+                    String message = MessageFormat.format(
+                            Messages.getString("PluginProjectBuilder.parseError"), //$NON-NLS-1$
+                            new Object[] { PluginProject.PLUGIN_XML });
+                    VexPlugin.getInstance().log(IStatus.ERROR, message, ex); //$NON-NLS-1$
+                    this.flagError(pluginXmlResource, ex.getLocalizedMessage());
+                }
+            }
+            
+            IBuildProblemHandler problemHandler = new IBuildProblemHandler() {
+                public void foundProblem(BuildProblem problem) {
+                    try {
+                        IResource resource = getProject().getFile(problem.getResourcePath());
+                        flagError(resource, problem.getMessage(), problem.getLineNumber());
+                    } catch (CoreException e) {
+                        // TODO Auto-generated catch block
+                        e.printStackTrace();
+                    }
+                }
+            };
+            
+            pluginProject.parseResources(problemHandler);
+
+            // Write new config to SER file.
+            try {
+                pluginProject.saveState();
+            } catch (IOException ex) {
+                String message = MessageFormat.format(
+                        Messages.getString("PluginProjectBuilder.cantSaveConfig"), //$NON-NLS-1$
+                        new Object[] { project.getName() });
+                VexPlugin.getInstance().log(IStatus.WARNING, message, ex);
+            }
+        } finally {
+            registry.unlock();
+        }
+
+        registry.fireConfigChanged(new ConfigEvent(this));
+        
+        return null;
+    }
+
+    
+    protected void clean(IProgressMonitor monitor) throws CoreException {
+        ConfigRegistry registry = ConfigRegistry.getInstance();
+        try {
+            registry.lock();
+            PluginProject pluginProject = PluginProject.get(this.getProject());
+            if (pluginProject != null) {
+                pluginProject.cleanState();
+                pluginProject.removeAllItems();
+                pluginProject.removeAllResources();
+                registry.fireConfigChanged(new ConfigEvent(this));
+            }
+        } finally {
+            registry.unlock();
+        }
+    }
+    
+    //======================================================== PRIVATE
+
+    private BuildProblemDecorator buildProblemDecorator;
+
+    private void flagError(IResource resource, String message) throws CoreException {
+        flagError(resource, message, -1);
+    }
+    
+    private void flagError(IResource resource, String message, int lineNumber) throws CoreException {
+        IMarker marker = resource.createMarker(IMarker.PROBLEM);
+        if (marker.exists()) {
+            marker.setAttribute(IMarker.MESSAGE, message);
+            marker.setAttribute(IMarker.PRIORITY, IMarker.PRIORITY_HIGH);
+            marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
+            if (lineNumber > 0) {
+                marker.setAttribute(IMarker.LINE_NUMBER, lineNumber);
+            }
+            this.getBuildProblemDecorator().update(resource);
+        }
+    }
+    
+    private BuildProblemDecorator getBuildProblemDecorator() {
+        if (this.buildProblemDecorator == null) {
+            IDecoratorManager dm = PlatformUI.getWorkbench().getDecoratorManager();
+            this.buildProblemDecorator = (BuildProblemDecorator) dm.getBaseLabelProvider(BuildProblemDecorator.ID);
+        }
+        return this.buildProblemDecorator;
+    }
+    
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/PluginProjectDecorator.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/PluginProjectDecorator.java
new file mode 100644
index 0000000..f570745
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/PluginProjectDecorator.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.ui.internal.config;
+
+import java.net.URL;
+
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.FileLocator;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.viewers.IDecoration;
+import org.eclipse.jface.viewers.ILabelProviderListener;
+import org.eclipse.jface.viewers.ILightweightLabelDecorator;
+import org.eclipse.wst.xml.vex.ui.internal.VexPlugin;
+
+/**
+ * Decorates Vex projects with the Vex logo.
+ */
+public class PluginProjectDecorator implements ILightweightLabelDecorator {
+
+    public void decorate(Object element, IDecoration decoration) {
+        
+        if (this.vexIcon == null) {
+            this.loadImageDescriptors();
+        }
+        
+        if (element instanceof IProject) {
+            try {
+                IProject project = (IProject) element;
+                if (project.hasNature(PluginProjectNature.ID)) {
+                    decoration.addOverlay(this.vexIcon, IDecoration.TOP_RIGHT);
+                }
+            } catch (CoreException e) {
+            }
+        }
+    }
+
+    public void addListener(ILabelProviderListener listener) {
+    }
+
+    public void dispose() {
+    }
+
+    public boolean isLabelProperty(Object element, String property) {
+        return false;
+    }
+
+    public void removeListener(ILabelProviderListener listener) {
+    }
+    
+    //======================================================== PRIVATE
+    
+    private ImageDescriptor vexIcon;
+    
+    private void loadImageDescriptors() {
+    	URL url = FileLocator.find(
+    			VexPlugin.getInstance().getBundle(),
+    			new Path("icons/vex8.gif"), //$NON-NLS-1$
+    			null); 
+        this.vexIcon = ImageDescriptor.createFromURL(url);
+    }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/PluginProjectNature.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/PluginProjectNature.java
new file mode 100644
index 0000000..73abadc
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/PluginProjectNature.java
@@ -0,0 +1,75 @@
+/*******************************************************************************
+ * 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.ui.internal.config;
+
+import org.eclipse.core.resources.ICommand;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IProjectDescription;
+import org.eclipse.core.resources.IProjectNature;
+import org.eclipse.core.runtime.CoreException;
+
+/**
+ * Project nature that defines Vex Plugin projects.
+ */
+public class PluginProjectNature implements IProjectNature {
+    
+    public static final String ID = "org.eclipse.wst.xml.vex.ui.pluginNature"; //$NON-NLS-1$
+
+    public void configure() throws CoreException {
+        this.registerBuilder();
+    }
+
+    public void deconfigure() throws CoreException {
+        //System.out.println("deconfiguring " + project.getName());
+        project.deleteMarkers(IMarker.PROBLEM, true, 1);
+    }
+
+    public IProject getProject() {
+        return this.project;
+    }
+
+    public void setProject(IProject project) {
+        this.project = project;
+    }
+
+    //====================================================== PRIVATE
+
+    private IProject project;
+    
+    
+    private void registerBuilder() throws CoreException {
+        IProjectDescription desc = project.getDescription();
+        ICommand[] commands = desc.getBuildSpec();
+        boolean found = false;
+
+        for (int i = 0; i < commands.length; ++i) {
+           if (commands[i].getBuilderName().equals(PluginProjectBuilder.ID)) {
+              found = true;
+              break;
+           }
+        }
+        if (!found) { 
+           //add builder to project
+           ICommand command = desc.newCommand();
+           command.setBuilderName(PluginProjectBuilder.ID);
+           ICommand[] newCommands = new ICommand[commands.length + 1];
+
+           // Add it before other builders.
+           System.arraycopy(commands, 0, newCommands, 1, commands.length);
+           newCommands[0] = command;
+           desc.setBuildSpec(newCommands);
+           project.setDescription(desc, null);
+        }
+
+
+    }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/Style.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/Style.java
new file mode 100644
index 0000000..2841c95
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/Style.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.ui.internal.config;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheet;
+import org.eclipse.wst.xml.vex.core.internal.layout.BoxFactory;
+
+
+/**
+ * Represents the combination of a style sheet and a box factory that defines
+ * the styling of an XML document during editing.
+ */
+public class Style extends ConfigItem {
+    
+    public static final String EXTENSION_POINT = "org.eclipse.wst.xml.vex.ui.styles"; //$NON-NLS-1$
+    
+    public Style(ConfigSource config) {
+        super(config);
+    }
+    
+    /**
+     * Adds the public ID of a document type to which the style applies. 
+     * @param publicId public ID of the document type
+     */
+    public void addDocumentType(String publicId) {
+        this.publicIds.add(publicId);
+    }
+    
+    /**
+     * Returns true if this style applies to the documents with the given type.
+     * @param publicId public ID of the document type being sought
+     */
+    public boolean appliesTo(String publicId) {
+        return this.publicIds.contains(publicId);
+    }
+    
+    /**
+     * Returns the box factory used to generate boxes for document elements.
+     */
+    public BoxFactory getBoxFactory() {
+        return boxFactory;
+    }
+
+    /**
+     * Returns a set of public IDs of all document types supported by
+     * this style.
+     */
+    public Set getDocumentTypes() {
+        return Collections.unmodifiableSet(this.publicIds);
+    }
+
+    /**
+     * Returns an array of all styles applicable to the given public Id.
+     * @param publicId Public ID for which to find styles.
+     */
+    public static Style[] getStylesForDoctype(String publicId) {
+        ConfigRegistry registry = ConfigRegistry.getInstance();
+        List styles = new ArrayList();
+        List allStyles = registry.getAllConfigItems(Style.EXTENSION_POINT);
+        for (Iterator it = allStyles.iterator(); it.hasNext(); ) {
+            Style style = (Style) it.next();
+            if (style.appliesTo(publicId)) {
+                styles.add(style);
+            }
+        }
+        return (Style[]) styles.toArray(new Style[styles.size()]);
+    }
+    
+    /**
+     * Returns the style sheet from which element styles are taken.
+     */
+    public StyleSheet getStyleSheet() {
+        return (StyleSheet) this.getConfig().getParsedResource(this.getResourcePath());
+    }
+
+    public String getExtensionPointId() {
+        return EXTENSION_POINT;
+    }
+    
+    /**
+     * Disassociates this style from all document types.
+     */
+    public void removeAllDocumentTypes() {
+        this.publicIds.clear();
+    }
+    
+    /**
+     * Removes the public ID of a document type to which the style 
+     * no longer applies. 
+     * @param publicId public ID of the document type
+     */
+    public void removeDocumentType(String publicId) {
+        this.publicIds.remove(publicId);
+    }
+    
+    /**
+     * Sets the box factory used to generate boxes for document elements.
+     * @param factory the new box factory.
+     */
+    public void setBoxFactory(BoxFactory factory) {
+        boxFactory = factory;
+    }
+
+    //===================================================== PRIVATE
+    
+
+    private BoxFactory boxFactory;
+    private Set publicIds = new HashSet();
+    
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/StyleFactory.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/StyleFactory.java
new file mode 100644
index 0000000..422c48d
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/StyleFactory.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.ui.internal.config;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheetReader;
+import org.w3c.css.sac.CSSParseException;
+
+/**
+ * Factory for style objects.
+ */
+public class StyleFactory implements IConfigItemFactory {
+
+    /**
+     * Returns all styles for a particular doctype.
+     * @param publicId Public ID of the desired doctype.
+     * @return List of Style objects.
+     */
+    public static List getStylesForDoctype(String publicId) {
+//        List result = new ArrayList();
+//        Iterator it = this.getAll().iterator();
+//        while (it.hasNext()) {
+//            Style style = (Style) it.next();
+//            if (style.getDocumentTypes().contains(publicId)) {
+//                result.add(style);
+//            }
+//        }
+//            
+//        return result;
+        return null;
+    }
+    
+    public IConfigElement[] createConfigurationElements(ConfigItem item) {
+        Style style = (Style) item;
+        ConfigurationElement element = new ConfigurationElement("style"); //$NON-NLS-1$
+        element.setAttribute("css", style.getResourcePath()); //$NON-NLS-1$
+        Set doctypes = style.getDocumentTypes();
+        for (Iterator it = doctypes.iterator(); it.hasNext();) {
+            String publicId = (String) it.next();
+            ConfigurationElement child = new ConfigurationElement("doctypeRef"); //$NON-NLS-1$
+            child.setAttribute("publicId", publicId); //$NON-NLS-1$
+            element.addChild(child);
+        }
+        return new IConfigElement[] { element };
+    }
+
+
+
+    public ConfigItem createItem(ConfigSource config, IConfigElement[] configElements) throws IOException {
+        
+        if (configElements.length < 1) {
+            return null;
+        }
+        IConfigElement configElement = configElements[0];
+        
+        Style style = new Style(config);
+        style.setResourcePath(configElement.getAttribute("css")); //$NON-NLS-1$
+        
+        IConfigElement[] doctypeRefs = configElement.getChildren();
+        
+        for (int j = 0; j < doctypeRefs.length; j++) {
+            style.addDocumentType(doctypeRefs[j].getAttribute("publicId")); //$NON-NLS-1$
+        }
+        
+        return style;
+    }
+
+    
+    public String getExtensionPointId() {
+        return Style.EXTENSION_POINT;
+    }
+    
+
+    public String[] getFileExtensions() {
+        return EXTS;
+    }
+
+    
+    public String getPluralName() {
+        return Messages.getString("StyleFactory.pluralName"); //$NON-NLS-1$
+    }
+
+    public Object parseResource(URL baseUrl, String resourcePath, IBuildProblemHandler problemHandler) throws IOException {
+        try {
+            return new StyleSheetReader().read(new URL(baseUrl, resourcePath));
+        } catch (CSSParseException ex) {
+            if (problemHandler != null) {
+                BuildProblem problem = new BuildProblem();
+                problem.setSeverity(BuildProblem.SEVERITY_ERROR);
+                problem.setResourcePath(ex.getURI());
+                problem.setMessage(ex.getMessage());
+                problem.setLineNumber(ex.getLineNumber());
+                problemHandler.foundProblem(problem);
+            }
+            throw ex;
+            
+        }
+    }
+
+    //=================================================== PRIVATE
+    
+    private static final String[] EXTS = new String[] { "css" }; //$NON-NLS-1$
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/StylePropertyPage.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/StylePropertyPage.java
new file mode 100644
index 0000000..71fd9af
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/StylePropertyPage.java
@@ -0,0 +1,249 @@
+/*******************************************************************************
+ * 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.ui.internal.config;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableItem;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.ui.dialogs.PropertyPage;
+import org.eclipse.wst.xml.vex.ui.internal.VexPlugin;
+
+/**
+ * Property page for .css files.
+ */
+public class StylePropertyPage extends PropertyPage {
+
+    protected Control createContents(Composite parent) {
+
+        pane = new Composite(parent, SWT.NONE);
+
+        createPropertySheet();
+        
+        configListener = new IConfigListener() {
+
+            public void configChanged( final ConfigEvent e) {
+                populateDoctypes();
+            }
+
+            public void configLoaded(final ConfigEvent e) {
+                setMessage( getTitle() );
+                populateStyle();
+                setValid(true);
+                
+                try { // force an incremental build 
+                    getPluginProject().writeConfigXml();
+                } catch (Exception ex) {
+                    String message = MessageFormat.format(
+                            Messages.getString("StylePropertyPage.writeError"), //$NON-NLS-1$
+                            new Object[] { PluginProject.PLUGIN_XML });
+                    VexPlugin.getInstance().log(IStatus.ERROR, message, ex);
+                }       
+
+            }
+        };
+
+        ConfigRegistry.getInstance().addConfigListener( configListener );
+
+
+        if( ConfigRegistry.getInstance().isConfigLoaded() ){
+        
+            populateStyle();  
+            populateDoctypes();
+
+        } else {
+
+            setValid(false);
+                      
+            setMessage(Messages.getString("StylePropertyPage.loading")); //$NON-NLS-1$
+            
+        }
+
+        return pane;
+       }
+       
+    private void createPropertySheet() {
+        
+        GridLayout layout = new GridLayout();
+        layout.numColumns = 2;
+        pane.setLayout(layout);
+        GridData gd;
+        
+        Label label;
+        
+        label = new Label(pane, SWT.NONE);
+        label.setText(Messages.getString("StylePropertyPage.name")); //$NON-NLS-1$
+        this.nameText = new Text(pane, SWT.BORDER);
+        gd = new GridData();
+        gd.widthHint = NAME_WIDTH;
+        this.nameText.setLayoutData(gd);
+        
+        final String resourcePath = ((IFile) this.getElement()).getProjectRelativePath().toString();
+
+        final ConfigSource config = this.getPluginProject();
+        
+        this.style = (Style) config.getItemForResource(resourcePath);
+        if (this.style == null) {
+            this.style = new Style(config);
+            this.style.setResourcePath(resourcePath);
+            config.addItem(this.style);
+        }
+        
+        // Generate a simple ID for this one if necessary
+        if (this.style.getSimpleId() == null || this.style.getSimpleId().length() == 0) {
+            this.style.setSimpleId(this.style.generateSimpleId());
+        }
+        
+        label = new Label(pane, SWT.NONE);
+        label.setText(Messages.getString("StylePropertyPage.doctypes")); //$NON-NLS-1$
+        gd = new GridData();
+        gd.horizontalSpan = 2;
+        label.setLayoutData(gd);
+
+        final Composite tablePane = new Composite(pane, SWT.BORDER );
+        
+        gd = new GridData(GridData.FILL_BOTH);
+        gd.heightHint = 200;
+        gd.horizontalSpan = 2;
+        tablePane.setLayoutData(gd);
+        
+        final FillLayout fillLayout = new FillLayout();
+        tablePane.setLayout(fillLayout);
+        
+        this.doctypesTable = new Table( tablePane, SWT.CHECK );
+       
+    }
+
+    /**
+     * Returns the PluginProject associated with this resource.
+     * @return
+     */
+    public PluginProject getPluginProject() {
+        IFile file = (IFile) this.getElement();
+        return PluginProject.get(file.getProject());
+    }
+
+    public boolean performOk() {
+    
+        performApply();
+        
+        return super.performOk();
+    }
+
+    public void performApply() {
+
+        this.style.setName(this.nameText.getText());
+        
+        List doctypeList = ConfigRegistry.getInstance().getAllConfigItems( DocumentType.EXTENSION_POINT);
+        Collections.sort( doctypeList );
+
+        final ArrayList selectedDoctypes = new ArrayList();
+        final TableItem[] tia = this.doctypesTable.getItems();
+        
+        for (int i = 0; i < tia.length; i++) {
+            if( tia[i].getChecked() ) {
+                selectedDoctypes.add( tia[i].getText());
+            }
+        }
+
+        this.style.removeAllDocumentTypes();
+
+        for (int i = 0; i < doctypeList.size(); i++) {
+            if( selectedDoctypes.contains( ((DocumentType)doctypeList.get(i)).getName() ) ) {
+                this.style.addDocumentType( ((DocumentType)doctypeList.get(i)).getPublicId() );
+            }
+        }
+  
+        try {
+            this.getPluginProject().writeConfigXml();
+        } catch (Exception e) {
+            String message = MessageFormat.format(
+                    Messages.getString("StylePropertyPage.writeError"), //$NON-NLS-1$
+                    new Object[] { PluginProject.PLUGIN_XML });
+            VexPlugin.getInstance().log(IStatus.ERROR, message, e);
+        }
+        
+        ConfigRegistry.getInstance().fireConfigChanged(new ConfigEvent(this));
+    }
+    
+    protected void performDefaults() {
+
+        super.performDefaults();
+
+        populateStyle();
+
+        populateDoctypes();
+        
+    }
+
+    public void dispose() {
+        super.dispose();
+        
+        if (this.configListener != null) {
+            ConfigRegistry.getInstance().removeConfigListener(this.configListener);
+        }
+    }
+    
+    //======================================================= PRIVATE
+
+    private Style style;
+    private static final int NAME_WIDTH = 150;
+    
+    private Composite pane;  
+    private Text nameText;
+    private Table doctypesTable;
+
+    private IConfigListener configListener;
+     
+    private void populateStyle() {
+        this.setText(this.nameText, this.style.getName());
+        
+    }
+    
+    private void populateDoctypes() {
+
+        final Set selectedDoctypes = new TreeSet( this.style.getDocumentTypes() );
+        doctypesTable.removeAll();
+        
+        List doctypeList = ConfigRegistry.getInstance().getAllConfigItems( DocumentType.EXTENSION_POINT);
+        Collections.sort( doctypeList );
+        for (int i = 0; i < doctypeList.size(); i++) {
+           
+            TableItem item1 = new TableItem( doctypesTable, SWT.NONE );
+            item1.setText( ((DocumentType) doctypeList.get(i)).getName() );
+            if( selectedDoctypes.contains( ((DocumentType)doctypeList.get(i)).getPublicId() ) ) {
+              
+                item1.setChecked(true);
+            }
+        }
+    }
+    
+    private void setText(Text textBox, String s) {
+        textBox.setText(s == null ? "" : s); //$NON-NLS-1$
+    }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/messages.properties b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/messages.properties
new file mode 100644
index 0000000..74e6197
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/messages.properties
@@ -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
+# *******************************************************************************
+
+Messages.cantFindResource=Can''t find resource string {0}
+
+StyleFactory.pluralName=Styles
+StylePropertyPage.loading=The Configuration is being loaded ... 
+StylePropertyPage.name=Name:
+StylePropertyPage.doctypes=Document Types:
+ConfigLoaderJob.loadingConfig=Loading Vex configuration
+ConfigLoaderJob.loading=Loading 
+ConfigPlugin.loadError=Unable to load {0} from project {1}
+ConfigLoaderJob.loadingError=Error loading {0}. Rebuilding from scratch.
+ConfigSource.errorParsingUri=Error parsing 
+ConfigLoaderJob.cacheError=Unable to cache configuration in {0}
+ConfigurationView.loading=Loading...
+ConfigLoaderJob.loadingProject=Loading project {0}
+ConfigLoaderJob.natureError=Error determining nature of project {0}
+DoctypeFactory.pluralName=Document Types
+DoctypePropertyPage.errorWritingConfig=Error writing {0}
+DoctypePropertyPage.loading=The Configuration is being loaded ... 
+DoctypePropertyPage.name=Name:
+DoctypePropertyPage.publicId=Public ID:
+DoctypePropertyPage.systemId=System ID:
+DoctypePropertyPage.rootElements=Root Elements:
+NewPluginProjectWizard.title=New Vex Plug-in Project
+NewPluginProjectWizard.createError=Error creating plug-in project
+PluginProject.malformedUrl=Project base URL is malformed.
+PluginProject.notPluginProject=Project {0}  is not a Vex plugin project.
+PluginProjectBuilder.notConfigSource=Internal error: project {0} is not registered as a config source.
+PluginProjectBuilder.cantSaveFile=Unable to save {0}.
+PluginProjectBuilder.missingFile=Missing {0}.
+PluginProjectBuilder.parseError=Error parsing {0}.
+PluginProjectBuilder.cantSaveConfig=Unable to save VexConfiguration for project {0}.
+PluginProject.loadingError=Error loading {0}. Rebuilding from scratch.
+PluginProject.buildError=Error building project {0}.
+StylePropertyPage.writeError=Error writing {0}.
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/messages_fr.properties b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/messages_fr.properties
new file mode 100644
index 0000000..e16b092
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/messages_fr.properties
@@ -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

+# *******************************************************************************

+Messages.cantFindResource=Ne peut pas trouver la ressource : {0}

+

+StyleFactory.pluralName=Styles

+StylePropertyPage.loading=La configuration a été chargée ... 

+StylePropertyPage.name=Nom:

+StylePropertyPage.doctypes=Types de Document:

+ConfigLoaderJob.loadingConfig=Chargement de la configuration de Vex

+ConfigLoaderJob.loading=Chargement 

+ConfigPlugin.loadError=Impossible de charger {0} du projet {1}

+ConfigLoaderJob.loadingError=Erreur au chargement {0}. Reconstuction.

+ConfigSource.errorParsingUri=Erreur d'analyse 

+ConfigLoaderJob.cacheError=Impossible de mettre en cache la configuration pour {0}

+ConfigurationView.loading=Chargement...

+ConfigLoaderJob.loadingProject=Chargement du projet {0}

+ConfigLoaderJob.natureError=Erreur lors de la détermination du projet {0}

+DoctypeFactory.pluralName=Types de Document

+DoctypePropertyPage.errorWritingConfig=Erreur à l'écriture {0}

+DoctypePropertyPage.loading=La configuration est chargée ... 

+DoctypePropertyPage.name=Nom:

+DoctypePropertyPage.publicId=Public ID:

+DoctypePropertyPage.systemId=System ID:

+DoctypePropertyPage.rootElements=Elements Racines:

+NewPluginProjectWizard.title=Nouveau projet Vex

+NewPluginProjectWizard.createError=Erreur à la création du projet Vex

+PluginProject.malformedUrl=L'URL de base du projet est incorrecte

+PluginProject.notPluginProject=Le projet {0} n'est pas un projet Vex.

+PluginProjectBuilder.notConfigSource=Erreur interne: project {0} is not registered as a config source.

+PluginProjectBuilder.cantSaveFile=Impossible de sauver le fichier {0}.

+PluginProjectBuilder.missingFile={0} Manquant.

+PluginProjectBuilder.parseError=Erreur à l'analyse {0}.

+PluginProjectBuilder.cantSaveConfig=Impossible de sauver VexConfiguration pour le projet {0}.

+PluginProject.loadingError=Erreur au chargement {0}. Reconstuction.

+PluginProject.buildError=Erreur à la construction du projet {0}.

+StylePropertyPage.writeError=Erreur à l'écriture {0}.

diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/vex-config-1.0.dtd b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/vex-config-1.0.dtd
new file mode 100644
index 0000000..2e7e701
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/config/vex-config-1.0.dtd
@@ -0,0 +1,86 @@
+<!-- 
+
+ *******************************************************************************
+ * Copyright (c) 2003, 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
+ *******************************************************************************
+
+<?xml version='1.0'?>
+<!DOCTYPE doctype PUBLIC 
+  "-//Vex//DTD Vex Configuration 1.0//EN" 
+  "http://vex.sourceforge.net/dtd/vex-config-1.0.dtd">
+
+-->
+
+<!-- 
+
+  doctype
+
+  Defines a document type. The following attributes are supported.
+  
+  publicId - Unique public identifier of the document type. Typically, the
+             the format is  -//(organization)//(name and version//EN
+             
+  dtd      - Absolute or relative URL to the related DTD file.
+  
+
+-->
+
+<!ELEMENT doctype EMPTY>
+<!ATTLIST doctype
+  publicId	CDATA	#REQUIRED
+  systemId	CDATA	#REQUIRED
+  dtd		CDATA	#REQUIRED
+>
+
+
+<!--
+
+  doctypeRef
+  
+  Defines a reference to a document type. The following attributes are
+  supported.
+  
+  publicId - Public ID of the referenced document type.
+  
+-->
+
+<!ELEMENT doctypeRef EMPTY>
+<!ATTLIST doctypeRef
+  publicId	CDATA	#REQUIRED
+>
+
+
+<!-- 
+
+  style
+  
+  Defines a presentation for a document. The following attributes are supported.
+  
+  id        - Unique identifier of the style. This should follow Java
+              package naming conventions, e.g. net.sf.vex.styles.docbook.plain 
+              
+  name      - A brief name for the style.
+   
+  css       - Absolute or relative URL to the related CSS file.
+  
+  This element contains one or more doctypeRef elements indicating the 
+  doctypes supported by this style.
+             
+-->
+
+<!ELEMENT style (doctypeRef+)>
+<!ATTLIST style
+  id		ID			#REQUIRED
+  name		CDATA		#REQUIRED
+  css		CDATA		#REQUIRED
+>
+
+
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/Association.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/Association.java
new file mode 100644
index 0000000..5d8bfce
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/Association.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.ui.internal.editor;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Data structure that uses two maps to efficiently implement a many-to-many 
+ * relationship.
+ */
+public class Association {
+
+    /**
+     * Adds a relationship between two objects.
+     * 
+     * @param left Object on the left side of the relationship.
+     * @param right Object on the right side of the relationship.
+     */
+    public void add(Object left, Object right) {
+        Collection rights = (Collection) l2r.get(left);
+        if (rights == null) {
+            rights = new ArrayList();
+            l2r.put(left, rights);
+        }
+        rights.add(right);
+        
+        Collection lefts = (Collection) r2l.get(right);
+        if (lefts == null) {
+            lefts = new ArrayList();
+            r2l.put(right, lefts);
+        }
+        lefts.add(left);
+    }
+
+    /**
+     * Returns a collection of objects on the left side of the association
+     * for a given object on the right.
+     * 
+     * @param right Object for which to return associated objects.
+     */
+    public Collection getLeftsForRight(Object right) {
+        Collection lefts = (Collection) r2l.get(right);
+        if (lefts == null) {
+            return Collections.EMPTY_LIST;
+        } else {
+            return Collections.unmodifiableCollection(lefts);
+        }
+    }
+    
+    /**
+     * Returns a collection of objects on the right side of the association
+     * for a given object on the left.
+     * 
+     * @param left Object for which to return associated objects.
+     */
+    public Collection getRightsForLeft(Object left) {
+        Collection rights = (Collection) l2r.get(left);
+        if (rights == null) {
+            return Collections.EMPTY_LIST;
+        } else {
+            return Collections.unmodifiableCollection(rights);
+        }
+    }
+    
+    /**
+     * Removes an association between two objects. Returns silently if this
+     * is not an existing relationship.
+     * 
+     * @param left Object on the left side of the relationship.
+     * @param right Object on the right side of the relationship.
+     */
+    public void remove(Object left, Object right) {
+
+        Collection rights = (Collection) l2r.get(left);
+        if (rights != null) {
+            rights.remove(right);
+            if (rights.size() == 0) {
+                l2r.remove(left);
+            }
+        }
+        
+        Collection lefts = (Collection) r2l.get(right);
+        if (lefts != null) {
+            lefts.remove(left);
+            if (lefts.size() == 0) {
+                r2l.remove(right);
+            }
+        }
+        
+    }
+
+    /**
+     * Removes all relationships for a given object on the left side.
+     * 
+     * @param left Object for which to remove a relationship.
+     */
+    public void removeLeft(Object left) {
+        
+        Collection rights = (Collection) l2r.get(left);
+        if (rights == null) {
+            return;
+        }
+
+        l2r.remove(left);
+        
+        for (Iterator it = rights.iterator(); it.hasNext(); ) {
+            Object right = it.next();
+            Collection lefts = (Collection) r2l.get(right);
+            lefts.remove(left);
+            if (lefts.isEmpty()) {
+                r2l.remove(right);
+            }
+        }
+    }
+    
+    /**
+     * Removes all relationships for a given object on the right side.
+     * 
+     * @param right Object for which to remove a relationship.
+     */
+    public void removeRight(Object right) {
+        
+        Collection lefts = (Collection) r2l.get(right);
+        if (lefts == null) {
+            return;
+        }
+
+        r2l.remove(right);
+        
+        for (Iterator it = lefts.iterator(); it.hasNext(); ) {
+            Object left = it.next();
+            Collection rights = (Collection) l2r.get(left);
+            rights.remove(right);
+            if (rights.isEmpty()) {
+                l2r.remove(left);
+            }
+        }
+    }
+
+    
+    //==================================================== PRIVATE
+    
+    // maps left => list of associated rights
+    private Map l2r = new HashMap();
+    
+    // maps right => list of associated lefts
+    private Map r2l = new HashMap();
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/ContentAssistant.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/ContentAssistant.java
new file mode 100644
index 0000000..c341c7c
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/ContentAssistant.java
@@ -0,0 +1,268 @@
+/*******************************************************************************
+ * 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.ui.internal.editor;
+
+
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.dialogs.IDialogSettings;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ControlEvent;
+import org.eclipse.swt.events.ControlListener;
+import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.events.DisposeListener;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.KeyListener;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseListener;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.events.ShellEvent;
+import org.eclipse.swt.events.ShellListener;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.swt.widgets.Tree;
+import org.eclipse.swt.widgets.TreeItem;
+import org.eclipse.wst.xml.vex.core.internal.swt.VexWidget;
+import org.eclipse.wst.xml.vex.ui.internal.VexPlugin;
+
+
+/**
+ * Class that owns the content assistant window, the popup where the user
+ * can select elements or other content to insert. This class is a singleton
+ * owned by VexPlugin. It should not be instantiated directly.
+ */
+public abstract class ContentAssistant {
+    
+    /**
+     * Class constructor.
+     */
+    public ContentAssistant() {
+    }
+    
+    /**
+     * Returns the list of actions to be displayed in this assistant.
+     * @param vexWidget VexWidget to which the actions apply.
+     */
+    public abstract IAction[] getActions(VexWidget widget);
+
+    /**
+     * Returns the title to be displayed in the assistant's title bar.
+     * @param vexWidget VexWidget for which we are displaying the assistant.
+     */
+    public abstract String getTitle(VexWidget widget);
+    
+    /**
+     * Show the content assitant window for the given editor.
+     * @param vexWidget VexWidget for which we are displaying the assistant.
+     */
+    public void show(VexWidget vexWidget) {
+
+        this.actions = this.getActions(vexWidget);
+        
+        Shell parent = vexWidget.getShell();
+        this.assistantShell = new Shell(parent, SWT.DIALOG_TRIM | SWT.RESIZE | SWT.MODELESS);
+        this.assistantShell.setText(this.getTitle(vexWidget));
+        this.assistantShell.addControlListener(this.controlListener);
+        this.assistantShell.addDisposeListener(this.disposeListener);
+        this.assistantShell.addShellListener(this.shellListener);
+        
+        GridLayout layout = new GridLayout();
+        layout.numColumns = 1;
+        this.assistantShell.setLayout(layout);
+        GridData gd;
+        
+        this.textWidget = new Text(this.assistantShell, SWT.SINGLE);
+        this.textWidget.addModifyListener(this.modifyListener);
+        
+        gd = new GridData();
+        gd.grabExcessHorizontalSpace = true;
+        gd.horizontalAlignment = GridData.FILL;
+        this.textWidget.setLayoutData(gd);
+        this.textWidget.addKeyListener(this.keyListener);
+        this.textWidget.addModifyListener(this.modifyListener);
+
+        this.treeWidget = new Tree(assistantShell, SWT.SINGLE);
+        this.treeWidget.addKeyListener(this.keyListener);
+        this.treeWidget.addMouseListener(this.mouseListener);
+        this.treeWidget.addSelectionListener(this.selectionListener);
+        
+        gd = new GridData();
+        gd.grabExcessHorizontalSpace = true;
+        gd.horizontalAlignment = GridData.FILL;
+        gd.grabExcessVerticalSpace = true;
+        gd.verticalAlignment = GridData.FILL;
+        this.treeWidget.setLayoutData(gd);
+
+        this.assistantShell.setBounds(this.getSize());
+        this.repopulateList();
+        this.mouseDown = false;
+        this.assistantShell.open();
+        
+
+    }
+    
+    //======================================================== PRIVATE
+    
+    private static final String SETTINGS_SECTION = "contentAssistant"; //$NON-NLS-1$
+    private static final String SETTINGS_X = "x"; //$NON-NLS-1$
+    private static final String SETTINGS_Y = "y"; //$NON-NLS-1$
+    private static final String SETTINGS_WIDTH = "width"; //$NON-NLS-1$
+    private static final String SETTINGS_HEIGHT = "height"; //$NON-NLS-1$
+    
+    private Shell assistantShell;
+    private Text textWidget;
+    private Tree treeWidget;
+    private IAction[] actions;
+    private boolean mouseDown;
+
+    /**
+     * Perform the action that is currently selected in the tree view, if any,
+     * and dispose the assistant shell.
+     */
+    private void doAction() {
+        TreeItem[] items = treeWidget.getSelection();
+        if (items.length > 0) {
+            IAction action = (IAction) items[0].getData();
+            action.run();
+        }
+        assistantShell.dispose();
+    }
+    
+    private IDialogSettings getSettings() {
+        IDialogSettings rootSettings = VexPlugin.getInstance().getDialogSettings();
+        IDialogSettings settings = rootSettings.getSection(SETTINGS_SECTION);
+        if (settings == null) {
+            settings = rootSettings.addNewSection(SETTINGS_SECTION);
+        }
+        return settings;
+    }
+    
+    private Rectangle getSize() {
+        IDialogSettings settings = this.getSettings();
+        int x = 100;
+        int y = 100;
+        int width = 200;
+        int height = 300;
+        if (settings.get(SETTINGS_X) != null) {
+            x = settings.getInt(SETTINGS_X);
+            y = settings.getInt(SETTINGS_Y);
+            width = settings.getInt(SETTINGS_WIDTH);
+            height = settings.getInt(SETTINGS_HEIGHT);
+        }
+        return new Rectangle(x, y, width, height);
+    }
+    
+    private void repopulateList() {
+        String prefix = this.textWidget.getText();
+        this.treeWidget.removeAll();
+        TreeItem first = null;
+        for (int i = 0; i < this.actions.length; i++) {
+            IAction action = this.actions[i];
+            if (action.getText().startsWith(prefix)) {
+                TreeItem item = new TreeItem(this.treeWidget, SWT.NONE);
+                if (first == null) {
+                    first = item;
+                }
+                item.setData(action);
+                item.setText(action.getText());
+            }
+        }
+
+        if (first != null) {
+            this.treeWidget.setSelection(new TreeItem[] { first });
+        }
+    }
+    
+    private void saveSize() {
+        IDialogSettings settings = this.getSettings();
+        Rectangle bounds = this.assistantShell.getBounds();
+        settings.put(SETTINGS_X, bounds.x);
+        settings.put(SETTINGS_Y, bounds.y);
+        settings.put(SETTINGS_WIDTH, bounds.width);
+        settings.put(SETTINGS_HEIGHT, bounds.height);
+    }
+    
+    private ControlListener controlListener = new ControlListener() {
+        public void controlMoved(ControlEvent e) {
+            saveSize();
+        }
+        public void controlResized(ControlEvent e) {
+            saveSize();
+        }
+    };
+    
+    private DisposeListener disposeListener = new DisposeListener() {
+        public void widgetDisposed(DisposeEvent e) {
+            assistantShell = null;
+        }
+    };
+    
+    private KeyListener keyListener = new KeyListener() {
+        public void keyPressed(KeyEvent e) {
+            if (e.keyCode == 13) {
+                doAction();
+            } else if (e.widget == textWidget && e.keyCode == SWT.ARROW_DOWN) {
+                treeWidget.setFocus();
+            }
+
+        }
+        public void keyReleased(KeyEvent e) {
+        }
+    };
+    
+    private ModifyListener modifyListener = new ModifyListener() {
+        public void modifyText(ModifyEvent e) {
+            repopulateList();
+        }
+    };
+    
+    private MouseListener mouseListener = new MouseListener() {
+        public void mouseDoubleClick(MouseEvent e) {
+        }
+        public void mouseDown(MouseEvent e) {
+            mouseDown = true;
+        }
+        public void mouseUp(MouseEvent e) {
+            mouseDown = false;
+        }
+        
+    };
+    
+    private SelectionListener selectionListener = new SelectionListener() {
+        public void widgetSelected(SelectionEvent e) {
+            // If selected with the mouse, we do the selected action.
+            if (mouseDown) {
+                doAction();
+            }
+        }
+        public void widgetDefaultSelected(SelectionEvent e) {
+        }
+    };
+    
+    private ShellListener shellListener = new ShellListener() {
+        public void shellActivated(ShellEvent e) {
+        }
+        public void shellClosed(ShellEvent e) {
+        }
+        public void shellDeactivated(ShellEvent e) {
+            assistantShell.dispose();
+        }
+        public void shellDeiconified(ShellEvent e) {
+        }
+        public void shellIconified(ShellEvent e) {
+        }
+    };
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DebugView.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DebugView.java
new file mode 100644
index 0000000..befb559
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DebugView.java
@@ -0,0 +1,81 @@
+/*******************************************************************************
+ * 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.ui.internal.editor;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.ui.IActionBars;
+import org.eclipse.ui.IWorkbenchPart;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.part.IPage;
+import org.eclipse.ui.part.IPageBookViewPage;
+import org.eclipse.ui.part.IPageSite;
+import org.eclipse.ui.part.PageBook;
+import org.eclipse.ui.part.PageBookView;
+
+/**
+ * A view that shows stats about the current Vex editor as a debugging aid.
+ */
+public class DebugView extends PageBookView {
+
+    protected IPage createDefaultPage(PageBook book) {
+        IPageBookViewPage page = new IPageBookViewPage() {
+            public void createControl(Composite parent) {
+                this.label = new Label(parent, SWT.NONE);
+                this.label.setText(Messages.getString("DebugView.noActiveEditor")); //$NON-NLS-1$
+            }
+            public void dispose() {
+            }
+            public Control getControl() {
+                return this.label;
+            }
+            public IPageSite getSite() {
+                return this.site;
+            }
+            public void init(IPageSite site) throws PartInitException {
+                this.site = site;
+            }
+            public void setActionBars(IActionBars actionBars) {
+            }
+            public void setFocus() {
+            }
+            
+            private IPageSite site;
+            private Label label;
+        };
+        
+        initPage(page);
+        page.createControl(getPageBook());
+        return page;
+    }
+
+    protected PageRec doCreatePage(IWorkbenchPart part) {
+        DebugViewPage page = new DebugViewPage((VexEditor) part);
+        initPage(page);
+        page.createControl(getPageBook());
+        return new PageRec(part, page);
+    }
+
+    protected void doDestroyPage(IWorkbenchPart part, PageRec pageRecord) {
+        pageRecord.page.dispose();
+    }
+
+    protected IWorkbenchPart getBootstrapPart() {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    protected boolean isImportant(IWorkbenchPart part) {
+        return (part instanceof VexEditor);
+    }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DebugViewPage.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DebugViewPage.java
new file mode 100644
index 0000000..7df63b7
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DebugViewPage.java
@@ -0,0 +1,300 @@
+/*******************************************************************************
+ * 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.ui.internal.editor;
+
+import java.lang.reflect.Field;
+
+
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.ScrolledComposite;
+import org.eclipse.swt.events.ControlEvent;
+import org.eclipse.swt.events.ControlListener;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseMoveListener;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableColumn;
+import org.eclipse.swt.widgets.TableItem;
+import org.eclipse.ui.IActionBars;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.part.IPageBookViewPage;
+import org.eclipse.ui.part.IPageSite;
+import org.eclipse.wst.xml.vex.core.internal.core.Caret;
+import org.eclipse.wst.xml.vex.core.internal.core.Rectangle;
+import org.eclipse.wst.xml.vex.core.internal.layout.Box;
+import org.eclipse.wst.xml.vex.core.internal.swt.VexWidget;
+import org.eclipse.wst.xml.vex.core.internal.widget.HostComponent;
+import org.eclipse.wst.xml.vex.core.internal.widget.VexWidgetImpl;
+
+/**
+ * Page in the debug view. 
+ */
+class DebugViewPage implements IPageBookViewPage {
+    
+    public DebugViewPage(VexEditor vexEditor) {
+        this.vexEditor = vexEditor;
+    }
+
+    public void createControl(Composite parent) {
+        
+        this.composite = new Composite(parent, SWT.NONE);
+        this.composite.setLayout(new FillLayout());
+
+        if (this.vexEditor.isLoaded()) {
+            this.createDebugPanel();
+        } else {
+            this.loadingLabel = new Label(this.composite, SWT.NONE);
+            this.loadingLabel.setText("Loading...");
+        }
+
+        this.vexEditor.getEditorSite().getSelectionProvider().addSelectionChangedListener(this.selectionListener);
+    }
+    
+    public void dispose() {
+        if (this.vexWidget != null && !this.vexWidget.isDisposed()) {
+            this.vexWidget.removeMouseMoveListener(this.mouseMoveListener);
+        }
+        this.vexEditor.getEditorSite().getSelectionProvider().removeSelectionChangedListener(this.selectionListener);
+    }
+
+    public Control getControl() {
+        return this.composite;
+    }
+
+    public IPageSite getSite() {
+        return this.site;
+    }
+
+    public void init(IPageSite site) throws PartInitException {
+        this.site = site;
+    }
+
+    public void setActionBars(IActionBars actionBars) {
+    }
+
+    public void setFocus() {
+    }
+
+    //================================================== PRIVATE
+    
+    private static final int X = 1;
+    private static final int Y = 2;
+    private static final int WIDTH = 3;
+    private static final int HEIGHT = 4;
+
+    private static Field implField;
+    private static Field rootBoxField;
+    private static Field caretField;
+    private static Field hostComponentField;
+    
+    static {
+        try {
+            implField = VexWidget.class.getDeclaredField("impl");
+            implField.setAccessible(true);
+            rootBoxField = VexWidgetImpl.class.getDeclaredField("rootBox");
+            rootBoxField.setAccessible(true);
+            caretField = VexWidgetImpl.class.getDeclaredField("caret");
+            caretField.setAccessible(true);
+            hostComponentField = VexWidgetImpl.class.getDeclaredField("hostComponent");
+            hostComponentField.setAccessible(true);
+        } catch (Exception e) {
+            // TODO: handle exception
+        }
+    }
+    
+    private IPageSite site;
+    private VexEditor vexEditor;
+    private VexWidget vexWidget;
+    private VexWidgetImpl impl;
+    private Composite composite;
+
+    private Label loadingLabel;
+
+    private Table table;
+    private TableItem documentItem;
+    private TableItem viewportItem;
+    private TableItem caretOffsetItem;
+    private TableItem caretAbsItem;
+    private TableItem caretRelItem;
+    private TableItem mouseAbsItem;
+    private TableItem mouseRelItem;
+
+    private void createDebugPanel() {
+
+        if (this.loadingLabel != null) {
+            this.loadingLabel.dispose();
+            this.loadingLabel = null;
+        }
+        
+        this.vexWidget = this.vexEditor.getVexWidget();
+        try {
+            this.impl = (VexWidgetImpl) implField.get(this.vexWidget);
+        } catch (IllegalArgumentException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        } catch (IllegalAccessException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+        
+        GridLayout layout = new GridLayout();
+        layout.numColumns = 1;
+        composite.setLayout(layout);
+        GridData gd;
+        
+        ScrolledComposite sc = new ScrolledComposite(this.composite, SWT.V_SCROLL);
+        this.table = new Table(sc, SWT.NONE);
+        this.table.setHeaderVisible(true);
+        sc.setContent(table);
+        sc.setExpandHorizontal(true);
+        sc.setExpandVertical(true);
+        gd = new GridData();
+        gd.grabExcessHorizontalSpace = true;
+        gd.horizontalAlignment = GridData.FILL;
+        sc.setLayoutData(gd);
+        
+        TableColumn column;
+        column = new TableColumn(this.table, SWT.LEFT);
+        column.setText("Item");
+        column = new TableColumn(this.table, SWT.RIGHT);
+        column.setText("X");
+        column = new TableColumn(this.table, SWT.RIGHT);
+        column.setText("Y");
+        column = new TableColumn(this.table, SWT.RIGHT);
+        column.setText("Width");
+        column = new TableColumn(this.table, SWT.RIGHT);
+        column.setText("Height");
+        
+        this.table.addControlListener(this.controlListener);
+        
+        this.documentItem = new TableItem(this.table, SWT.NONE);
+        this.documentItem.setText(0, "Document");
+        this.viewportItem = new TableItem(this.table, SWT.NONE);
+        this.viewportItem.setText(0, "Viewport");
+        this.caretOffsetItem = new TableItem(this.table, SWT.NONE);
+        this.caretOffsetItem.setText(0, "Caret Offset");
+        this.caretAbsItem = new TableItem(this.table, SWT.NONE);
+        this.caretAbsItem.setText(0, "Caret Abs.");
+        this.caretRelItem = new TableItem(this.table, SWT.NONE);
+        this.caretRelItem.setText(0, "Caret Rel.");
+        this.mouseAbsItem = new TableItem(this.table, SWT.NONE);
+        this.mouseAbsItem.setText(0, "Mouse Abs.");
+        this.mouseRelItem = new TableItem(this.table, SWT.NONE);
+        this.mouseRelItem.setText(0, "Mouse Rel.");
+        
+        Button updateButton = new Button(composite, SWT.PUSH);
+        updateButton.setText("Refresh");
+        updateButton.addSelectionListener(new SelectionListener() {
+            public void widgetSelected(SelectionEvent e) {
+                repopulate();
+            }
+            public void widgetDefaultSelected(SelectionEvent e) {
+            }
+        });
+
+        this.composite.layout();
+        
+        this.vexWidget.addMouseMoveListener(this.mouseMoveListener);
+        
+        this.repopulate();
+        
+    }
+    
+
+    private ISelectionChangedListener selectionListener = new ISelectionChangedListener() {
+        public void selectionChanged(SelectionChangedEvent event) {
+            if (vexWidget == null) {
+                createDebugPanel();
+            }
+            repopulate();
+        }
+    };
+
+    private ControlListener controlListener = new ControlListener() {
+        public void controlMoved(ControlEvent e) {
+        }
+        public void controlResized(ControlEvent e) {
+            int width = table.getSize().x;
+            int numWidth = Math.round(width * 0.125f); 
+            table.getColumn(0).setWidth(width / 2);
+            table.getColumn(1).setWidth(numWidth);
+            table.getColumn(2).setWidth(numWidth);
+            table.getColumn(3).setWidth(numWidth);
+        }
+    };
+    
+    private MouseMoveListener mouseMoveListener = new MouseMoveListener() {
+
+        public void mouseMove(MouseEvent e) {
+            Rectangle rect = new Rectangle(e.x, e.y, 0, 0);
+            Rectangle viewport = getViewport();
+            setItemFromRect(mouseAbsItem, rect);
+            setItemRel(mouseRelItem, viewport, rect);
+        }
+        
+    };
+    
+    private Rectangle getCaretBounds() {
+        Caret caret = (Caret) this.getFieldValue(caretField, this.impl);
+        return caret.getBounds();
+    }
+
+    private Rectangle getRootBoxBounds() {
+        Box rootBox = (Box) this.getFieldValue(rootBoxField, this.impl);
+        return new Rectangle(rootBox.getX(), rootBox.getY(), rootBox.getWidth(), rootBox.getHeight());
+    }
+
+    private Rectangle getViewport() {
+        HostComponent hc = (HostComponent) this.getFieldValue(hostComponentField, this.impl);
+        return hc.getViewport();
+    }
+    
+    private Object getFieldValue(Field field, Object o) {
+        try {
+            return field.get(o);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+    
+    private void repopulate() {
+        setItemFromRect(this.documentItem, this.getRootBoxBounds());
+        Rectangle viewport = this.getViewport();
+        this.caretOffsetItem.setText(1, Integer.toString(this.impl.getCaretOffset()));
+        setItemFromRect(this.viewportItem, viewport);
+        setItemFromRect(this.caretAbsItem, this.getCaretBounds());
+        setItemRel(this.caretRelItem, viewport, this.getCaretBounds());
+    }
+    
+    private static void setItemFromRect(TableItem item, Rectangle rect) {
+        item.setText(X, Integer.toString(rect.getX()));
+        item.setText(Y, Integer.toString(rect.getY()));
+        item.setText(WIDTH, Integer.toString(rect.getWidth()));
+        item.setText(HEIGHT, Integer.toString(rect.getHeight()));
+    }
+    
+    private static void setItemRel(TableItem item, Rectangle viewport, Rectangle rect) {
+        item.setText(X, Integer.toString(rect.getX() - viewport.getX()));
+        item.setText(Y, Integer.toString(rect.getY() - viewport.getY()));
+        item.setText(WIDTH, Integer.toString(rect.getWidth()));
+        item.setText(HEIGHT, Integer.toString(rect.getHeight()));
+    }
+}
\ No newline at end of file
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DefaultOutlineProvider.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DefaultOutlineProvider.java
new file mode 100644
index 0000000..4099afe
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DefaultOutlineProvider.java
@@ -0,0 +1,123 @@
+/*******************************************************************************
+ * 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.ui.internal.editor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+import org.eclipse.jface.viewers.IBaseLabelProvider;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.jface.viewers.Viewer;
+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.IWhitespacePolicy;
+import org.eclipse.wst.xml.vex.core.internal.widget.CssWhitespacePolicy;
+
+/**
+ * Default implementation of IOutlineProvider. Simply displays all block-level
+ * elements.
+ */
+public class DefaultOutlineProvider implements IOutlineProvider {
+
+    public void init(VexEditor editor) {
+        StyleSheet ss = editor.getVexWidget().getStyleSheet();
+        this.whitespacePolicy = new CssWhitespacePolicy(ss);
+        this.contentProvider = new ContentProvider();
+        this.labelProvider = new LabelProvider() {
+            public String getText(Object o) {
+                Element e = (Element) o;
+                String s = e.getText();
+                if (s.length() > 30) {
+                    s = s.substring(0, 30) + "..."; //$NON-NLS-1$
+                }
+                return e.getName() + ": " + s;  //$NON-NLS-1$
+            }
+        };
+
+    }
+
+    public ITreeContentProvider getContentProvider() {
+        return this.contentProvider;
+    }
+
+    public IBaseLabelProvider getLabelProvider() {
+        return this.labelProvider;
+    }
+
+    public Element getOutlineElement(Element child) {
+        Element element = child;
+        while (element != null) {
+            if (this.whitespacePolicy.isBlock(element)) {
+                return element;
+            }
+            element = element.getParent();
+        }
+        return element;
+    }
+
+    //====================================================== PRIVATE
+    
+    private IWhitespacePolicy whitespacePolicy;
+    private ITreeContentProvider contentProvider;
+    private IBaseLabelProvider labelProvider;
+
+    private class ContentProvider implements ITreeContentProvider {
+
+        public Object[] getChildren(Object parentElement) {
+            List blockChildren = new ArrayList();
+            Element[] children = ((Element) parentElement).getChildElements();
+            for (int i = 0; i < children.length; i++) {
+                if (whitespacePolicy.isBlock(children[i])) {
+                    blockChildren.add(children[i]);
+                }
+            }
+            return blockChildren.toArray();
+        }
+
+        public Object getParent(Object element) {
+            return ((Element) element).getParent();
+        }
+
+        public boolean hasChildren(Object o) {
+            return this.hasBlockChild((Element) o);
+        }
+
+        public Object[] getElements(Object inputElement) {
+            return new Object[] { ((Document) inputElement).getRootElement() };
+            //return this.getChildren(inputElement);
+        }
+
+        public void dispose() {
+        }
+
+        public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+            // TODO Auto-generated method stub
+
+        }
+
+        //====================================================== PRIVATE
+        
+        private boolean hasBlockChild(Element element) {
+            Element[] children = element.getChildElements();
+            for (int i = 0; i < children.length; i++) {
+                if (whitespacePolicy.isBlock(children[i])) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DocumentFileCreationPage.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DocumentFileCreationPage.java
new file mode 100644
index 0000000..ae68ad1
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DocumentFileCreationPage.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.ui.internal.editor;
+
+import java.io.InputStream;
+
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.ui.dialogs.WizardNewFileCreationPage;
+
+/**
+ * Wizard page for selecting the file name for a new Vex document.
+ */
+public class DocumentFileCreationPage extends WizardNewFileCreationPage {
+
+    /**
+     * Class constructor. Supplies a title and description to the superclass.
+     * @param pageName name of the page
+     * @param selection selection active when the wizard was started
+     */
+    public DocumentFileCreationPage(String pageName, IStructuredSelection selection) {
+        super(pageName, selection);
+        this.setTitle(Messages.getString("DocumentFileCreationPage.title")); //$NON-NLS-1$
+        this.setDescription(Messages.getString("DocumentFileCreationPage.desc")); //$NON-NLS-1$
+    }
+
+    /**
+     * Returns the initial contents of the file. The initial contents
+     * are set by the wizard via the {@link setInitialContents} method.
+     */
+    protected InputStream getInitialContents() {
+        return this.initialContents;
+    }
+
+    /**
+     * Sets the initial contents to be used when the document is created.
+     * @param initialContents initial contents for the new document.
+     */
+    public void setInitialContents(InputStream initialContents) {
+        this.initialContents = initialContents;
+    }
+    
+    private InputStream initialContents;
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DocumentOutlinePage.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DocumentOutlinePage.java
new file mode 100644
index 0000000..a9ddd90
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DocumentOutlinePage.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.ui.internal.editor;
+
+import java.text.MessageFormat;
+
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.jface.viewers.TreeViewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.TreeItem;
+import org.eclipse.ui.part.IPageSite;
+import org.eclipse.ui.part.Page;
+import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.swt.VexWidget;
+import org.eclipse.wst.xml.vex.ui.internal.VexPlugin;
+import org.eclipse.wst.xml.vex.ui.internal.config.DocumentType;
+import org.osgi.framework.Bundle;
+
+/**
+ * Outline page for documents. Determination of the outline itself is deferred
+ * to a doctype-specific implementation of IOutlineProvider.
+ */
+public class DocumentOutlinePage extends Page implements IContentOutlinePage {
+
+    public void createControl(Composite parent) {
+
+        this.composite = new Composite(parent, SWT.NONE);
+        this.composite.setLayout(new FillLayout());
+
+        if (this.vexEditor.isLoaded()) {
+            this.showTreeViewer();
+        } else {
+            this.showLabel(Messages.getString("DocumentOutlinePage.loading")); //$NON-NLS-1$
+        }
+
+    }
+
+    public void dispose() {
+        this.vexEditor.removeVexEditorListener(this.vexEditorListener);
+        this.vexEditor.getEditorSite().getSelectionProvider().removeSelectionChangedListener(this.selectionListener);
+    }
+
+    public Control getControl() {
+        return this.composite;
+    }
+
+    public void init(IPageSite pageSite) {
+        super.init(pageSite);
+        this.vexEditor = (VexEditor) pageSite.getPage().getActiveEditor();
+        this.vexEditor.addVexEditorListener(this.vexEditorListener);
+        this.vexEditor.getEditorSite().getSelectionProvider().addSelectionChangedListener(this.selectionListener);
+    }
+
+    public void setFocus() {
+        if (this.treeViewer != null) {
+            treeViewer.getControl().setFocus();
+        }
+    }
+
+    public void addSelectionChangedListener(ISelectionChangedListener listener) {
+        this.selectionProvider.addSelectionChangedListener(listener);
+    }
+
+    public ISelection getSelection() {
+        return this.selectionProvider.getSelection();
+    }
+
+    /**
+     * Returns the TreeViewer associated with this page. May return null,
+     * if VexPlugin has not yet loaded its configuration.
+     */
+    public TreeViewer getTreeViewer() {
+        return this.treeViewer;
+    }
+    
+    public void removeSelectionChangedListener(
+            ISelectionChangedListener listener) {
+        this.selectionProvider.removeSelectionChangedListener(listener);
+
+    }
+
+    public void setSelection(ISelection selection) {
+        this.selectionProvider.setSelection(selection);
+    }
+
+    //===================================================== PRIVATE
+    
+    private Composite composite;
+
+    private Label label;
+    private TreeViewer treeViewer;
+
+    private VexEditor vexEditor;
+    
+    private IOutlineProvider outlineProvider;
+
+    private SelectionProvider selectionProvider = new SelectionProvider();
+
+    private void showLabel(String message) {
+        
+        if (this.treeViewer != null) {
+            this.treeViewer.removeSelectionChangedListener(this.selectionListener);
+            this.treeViewer.getTree().dispose();
+            this.treeViewer = null;
+        }
+        
+        if (this.label == null) {
+            this.label = new Label(this.composite, SWT.NONE);
+            this.label.setText(message);
+            this.composite.layout(true);
+        }
+
+        this.label.setText(message);
+    }
+    
+    private void showTreeViewer() {
+        
+        if (this.treeViewer != null) {
+            return;
+        }
+
+        if (this.label != null) {
+            this.label.dispose();
+            this.label = null;
+        }
+        
+        this.treeViewer = new TreeViewer(this.composite, SWT.NONE);
+        this.composite.layout();
+ 
+        DocumentType doctype = this.vexEditor.getDocumentType();
+        
+        if (doctype == null) {
+            return;
+        }
+        
+        String ns = doctype.getConfig().getUniqueIdentifer();
+        Bundle bundle = Platform.getBundle(ns);
+        String providerClassName = doctype.getOutlineProvider();
+        if (bundle != null && providerClassName != null) {
+            try {
+                Class clazz = bundle.loadClass(providerClassName);
+                this.outlineProvider = (IOutlineProvider) clazz.newInstance();
+            } catch (Exception ex) {
+                String message = Messages.getString("DocumentOutlinePage.loadingError"); //$NON-NLS-1$
+                VexPlugin.getInstance().log(IStatus.WARNING, 
+                        MessageFormat.format(message, new Object[] { providerClassName, ns, ex }));
+            }
+        }
+        
+        if (this.outlineProvider == null) {
+            this.outlineProvider = new DefaultOutlineProvider();
+        }
+
+        
+        this.outlineProvider.init(this.vexEditor);
+        
+        this.treeViewer.setContentProvider(this.outlineProvider.getContentProvider());
+        this.treeViewer.setLabelProvider(this.outlineProvider.getLabelProvider());
+        this.treeViewer.setAutoExpandLevel(2);
+        
+        this.treeViewer.setInput(this.vexEditor.getVexWidget().getDocument());
+        
+        this.treeViewer.addSelectionChangedListener(this.selectionListener);
+
+    }
+
+    /**
+     * Receives selection changed events from both our TreeViewer and 
+     * the VexWidget. Generally, we use this to synchronize the selection
+     * between the two, but we also do the following...
+     * 
+     * - when a notification comes from VexWidget, we create the treeViewer 
+     *   if needed (that is, if the part was created before VexPlugin was
+     *   done loading its configuration.
+     * 
+     * - notifications from the TreeViewer are passed on to our
+     *   SelectionChangedListeners.
+     */
+    private ISelectionChangedListener selectionListener = new ISelectionChangedListener() {
+        public void selectionChanged(SelectionChangedEvent event) {
+            
+            if (event.getSource() instanceof VexWidget) {
+                
+                VexWidget vexWidget = (VexWidget) event.getSource();
+                if (vexWidget.isFocusControl() && getTreeViewer() != null) {
+                    Element element = vexWidget.getCurrentElement();
+                    Element outlineElement = outlineProvider.getOutlineElement(element);
+                    getTreeViewer().refresh(outlineElement);
+                    getTreeViewer().setSelection(new StructuredSelection(outlineElement), true);
+                }
+                
+            } else {
+                
+                // it's our tree control being selected
+                TreeViewer treeViewer = (TreeViewer) event.getSource();
+                if (treeViewer.getTree().isFocusControl()) {
+                    TreeItem[] selected = treeViewer.getTree().getSelection();
+                    if (selected.length > 0) {
+                        
+                        Element element = (Element) selected[0].getData();
+                        VexWidget vexWidget = vexEditor.getVexWidget();
+                        
+                        // Moving to the end of the element first is a cheap 
+                        // way to make sure we end up with the
+                        // caret at the top of the viewport
+                        vexWidget.moveTo(element.getEndOffset());
+                        vexWidget.moveTo(element.getStartOffset() + 1);
+
+                    }
+                }                
+            }
+        }
+    };
+    
+    private IVexEditorListener vexEditorListener = new IVexEditorListener() {
+
+        public void documentLoaded(VexEditorEvent event) {
+            showTreeViewer();
+        }
+
+        public void documentUnloaded(VexEditorEvent event) {
+            showLabel(Messages.getString("DocumentOutlinePage.reloading")); //$NON-NLS-1$
+        }
+        
+    };
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DocumentPerspective.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DocumentPerspective.java
new file mode 100644
index 0000000..d3460d4
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DocumentPerspective.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.ui.internal.editor;
+
+import org.eclipse.ui.IFolderLayout;
+import org.eclipse.ui.IPageLayout;
+import org.eclipse.ui.IPerspectiveFactory;
+
+
+/**
+ * Implements the Document perspective.
+ */
+public class DocumentPerspective implements IPerspectiveFactory {
+    
+    public void createInitialLayout(IPageLayout layout) {
+    	defineActions(layout);
+    	defineLayout(layout);
+    }
+    
+    /**
+     * Defines the initial actions for a page.  
+     */
+    public void defineActions(IPageLayout layout) {
+    	// Add "new wizards".
+    	layout.addNewWizardShortcut("org.eclipse.wst.xml.vex.ui.NewDocumentWizard");//$NON-NLS-1$
+    	layout.addNewWizardShortcut("org.eclipse.ui.wizards.new.folder");//$NON-NLS-1$
+    	layout.addNewWizardShortcut("org.eclipse.ui.wizards.new.file");//$NON-NLS-1$
+
+    	layout.addShowViewShortcut(IPageLayout.ID_RES_NAV);
+    	layout.addShowViewShortcut(IPageLayout.ID_OUTLINE);
+    	layout.addShowViewShortcut(IPageLayout.ID_PROP_SHEET);
+
+//    	layout.addActionSet(IPageLayout.ID_NAVIGATE_ACTION_SET);
+    }
+    
+    /**
+     * Defines the initial layout for a page.  
+     */
+    public void defineLayout(IPageLayout layout) {
+
+    	String editorArea = layout.getEditorArea();
+
+    	IFolderLayout topLeft = layout.createFolder("topLeft", IPageLayout.LEFT, (float)0.2, editorArea);//$NON-NLS-1$
+    	topLeft.addView(IPageLayout.ID_RES_NAV);
+        
+        IFolderLayout topRight = layout.createFolder("topRight", IPageLayout.RIGHT, (float)0.75, editorArea);//$NON-NLS-1$
+        topRight.addView(IPageLayout.ID_OUTLINE);
+        
+        IFolderLayout bottomRight = layout.createFolder("bottomRight", IPageLayout.BOTTOM, (float)0.5, "topRight");//$NON-NLS-1$ //$NON-NLS-2$
+        bottomRight.addView(IPageLayout.ID_PROP_SHEET);
+        
+        
+    }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DocumentTypeSelectionDialog.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DocumentTypeSelectionDialog.java
new file mode 100644
index 0000000..5de725e
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DocumentTypeSelectionDialog.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.ui.internal.editor;
+
+import java.text.MessageFormat;
+import java.util.Arrays;
+
+
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.ListViewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseListener;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.List;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.wst.xml.vex.ui.internal.config.DocumentType;
+
+/**
+ * Dialog presented to the user to select a document type it cannot be
+ * determined from the document being opened. 
+ */
+public class DocumentTypeSelectionDialog extends MessageDialog {
+
+    /**
+     * Class constructor.
+     * @param parentShell Parent Shell with respect to which this dialog is modal.
+     */
+    protected DocumentTypeSelectionDialog(Shell parentShell, String title, String message) {
+        super(parentShell, title, null, message, MessageDialog.QUESTION, new String[] { Messages.getString("DocumentTypeSelectionDialog.ok"), Messages.getString("DocumentTypeSelectionDialog.cancel") }, 0); //$NON-NLS-1$ //$NON-NLS-2$
+        this.setShellStyle(SWT.RESIZE);
+    }
+
+    /**
+     * Creates a new instance of the dialog. The caller must call open() on the
+     * returned dialog to prompt the user. The open() method blocks until the
+     * user has closed the window. Once open() returns, the caller should 
+     * call getDoctype() to get the selected doctype (or null if the dialog
+     * was canceled. 
+     * 
+     * @param parentShell Parent Shell of the dialog.
+     * @param publicId Public ID of the document being opened, or null
+     * if the document does not have a PUBLIC DOCTYPE declaration.
+     */
+    public static DocumentTypeSelectionDialog create(Shell parentShell, String publicId) {
+        String message;
+        
+        if (publicId == null) {
+            message = Messages.getString("DocumentTypeSelectionDialog.noDoctype"); //$NON-NLS-1$
+        } else {
+            message = Messages.getString("DocumentTypeSelectionDialog.unknownDoctype"); //$NON-NLS-1$
+        }
+
+        return new DocumentTypeSelectionDialog(parentShell, Messages.getString("DocumentTypeSelectionDialog.selectDoctype"),  //$NON-NLS-1$
+                MessageFormat.format(message, new Object[] { publicId }));
+    }
+    
+    protected Control createCustomArea(Composite parent) {
+        
+        this.typeList = new ListViewer(parent, SWT.SINGLE | SWT.H_SCROLL | SWT.V_SCROLL );
+        
+        List list = typeList.getList();
+
+        list.addMouseListener(this.mouseListener);
+
+        GridData gd = new GridData();
+        gd.grabExcessHorizontalSpace = true;
+        gd.grabExcessVerticalSpace = true;
+        gd.horizontalAlignment = GridData.FILL;
+        gd.verticalAlignment = GridData.FILL;
+        gd.heightHint = 120;
+        list.setLayoutData(gd);
+        
+        this.alwaysUseButton = new Button(parent, SWT.CHECK);
+        this.alwaysUseButton.setText(Messages.getString("DocumentTypeSelectionDialog.alwaysUse")); //$NON-NLS-1$
+        
+        DocumentType[] doctypes = DocumentType.getDocumentTypesWithStyles();
+        Arrays.sort(doctypes);
+        this.typeList.add(doctypes);
+
+        return list;
+    }
+
+    protected void buttonPressed(int buttonId) {
+        if (buttonId == 0) {
+            IStructuredSelection selection = (IStructuredSelection) this.typeList.getSelection();
+            this.doctype = (DocumentType) selection.getFirstElement();
+            this.alwaysUseThisDoctype = this.alwaysUseButton.getSelection();
+        }
+        super.buttonPressed(buttonId);
+    }
+    
+    /**
+     * Returns the document type selected by the user, or null if none
+     * was selected.
+     */
+    public DocumentType getDoctype() {
+        return this.doctype;
+    }
+
+    /**
+     * Returns true if Vex should always use this document type for the
+     * selected file.
+     */
+    public boolean alwaysUseThisDoctype() {
+        return this.alwaysUseThisDoctype;
+    }
+    
+    //======================================================= PRIVATE
+    
+    private DocumentType doctype;
+    private boolean alwaysUseThisDoctype;
+    
+    private ListViewer typeList;
+    private Button alwaysUseButton;
+    
+    private MouseListener mouseListener = new MouseListener() {
+        public void mouseDoubleClick(MouseEvent e) {
+            buttonPressed(0);
+        }
+        public void mouseDown(MouseEvent e) {
+        }
+        public void mouseUp(MouseEvent e) {
+        }
+    };
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DocumentTypeSelectionPage.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DocumentTypeSelectionPage.java
new file mode 100644
index 0000000..e69e3e7
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/DocumentTypeSelectionPage.java
@@ -0,0 +1,214 @@
+/*******************************************************************************
+ * 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.ui.internal.editor;
+
+import java.util.Arrays;
+import java.util.Set;
+
+
+import org.eclipse.jface.dialogs.IDialogSettings;
+import org.eclipse.jface.wizard.WizardPage;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Combo;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.wst.xml.vex.core.internal.dom.Validator;
+import org.eclipse.wst.xml.vex.ui.internal.VexPlugin;
+import org.eclipse.wst.xml.vex.ui.internal.config.DocumentType;
+
+/**
+ * Wizard page for selecting the document type and root element for the
+ * new document.
+ */
+public class DocumentTypeSelectionPage extends WizardPage {
+
+    private static final String SETTINGS_PUBLIC_ID = "publicId"; //$NON-NLS-1$
+    private static final String SETTINGS_ROOT_ELEMENT_PREFIX = "root."; //$NON-NLS-1$
+    
+    /**
+     * Class constructor.
+     */
+    public DocumentTypeSelectionPage() {
+        super(Messages.getString("DocumentTypeSelectionPage.pageName")); //$NON-NLS-1$
+        this.setPageComplete(false);
+        
+        IDialogSettings rootSettings = VexPlugin.getInstance().getDialogSettings();
+        this.settings = rootSettings.getSection("newDocument"); //$NON-NLS-1$
+        if (this.settings == null) {
+            this.settings = rootSettings.addNewSection("newDocument"); //$NON-NLS-1$
+        }
+        
+        this.doctypes = DocumentType.getDocumentTypesWithStyles();
+        Arrays.sort(this.doctypes);
+    }
+    
+    public void createControl(Composite parent) {
+        
+        Composite pane = new Composite(parent, SWT.NONE);
+        GridLayout layout = new GridLayout();
+        layout.numColumns = 2;
+        pane.setLayout(layout);
+        GridData gd;
+        
+        Label label = new Label(pane, SWT.NONE);
+        label.setText(Messages.getString("DocumentTypeSelectionPage.doctype")); //$NON-NLS-1$
+        
+        this.typeCombo = new Combo(pane, SWT.DROP_DOWN | SWT.READ_ONLY);
+        gd = new GridData();
+        gd.grabExcessHorizontalSpace = true;
+        gd.horizontalAlignment = GridData.FILL;
+        this.typeCombo.setLayoutData(gd);
+        this.typeCombo.addSelectionListener(typeComboSelectionListener);
+
+        label = new Label(pane, SWT.NONE);
+        label.setText(Messages.getString("DocumentTypeSelectionPage.rootElement")); //$NON-NLS-1$
+        this.setControl(pane);
+        
+        this.elementCombo = new Combo(pane, SWT.DROP_DOWN | SWT.READ_ONLY);
+        gd = new GridData();
+        gd.grabExcessHorizontalSpace = true;
+        gd.horizontalAlignment = GridData.FILL;
+        this.elementCombo.setLayoutData(gd);
+        this.elementCombo.addSelectionListener(elementComboSelectionListener);
+        
+        String publicId = this.settings.get(SETTINGS_PUBLIC_ID);
+        int initSelection = -1;
+        String[] typeNames = new String[this.doctypes.length];
+        for (int i = 0; i < this.doctypes.length; i++) {
+            typeNames[i] = this.doctypes[i].getName();
+            if (this.doctypes[i].getPublicId().equals(publicId)) {
+                initSelection = i;
+            }
+        }
+
+        this.typeCombo.setItems(typeNames);
+        
+        if (initSelection != -1) {
+            this.typeCombo.select(initSelection);
+            // calling select() does not fire the selection listener, 
+            // so we update it manually
+            this.updateElementCombo();
+        }
+
+        this.setTitle(Messages.getString("DocumentTypeSelectionPage.title")); //$NON-NLS-1$
+        this.setDescription(Messages.getString("DocumentTypeSelectionPage.desc")); //$NON-NLS-1$
+    }
+
+    /**
+     * Returns the selected document type.
+     */
+    public DocumentType getDocumentType() {
+        int i = this.typeCombo.getSelectionIndex();
+        if (i == -1) {
+            return null;
+        } else {
+            return this.doctypes[i];
+        }
+    }
+
+    /**
+     * Returns the selected name of the root element.
+     */
+    public String getRootElementName() {
+        return this.elementCombo.getText();
+    }
+    
+    /**
+     * Called from the wizard's performFinal method to save the settings for
+     * this page.
+     */
+    public void saveSettings() {
+        DocumentType doctype = this.getDocumentType();
+        if (doctype != null) {
+            this.settings.put(SETTINGS_PUBLIC_ID, doctype.getPublicId());
+            String key = SETTINGS_ROOT_ELEMENT_PREFIX + doctype.getPublicId();
+            this.settings.put(key, this.getRootElementName());
+        }
+    }
+    
+    //============================================================== PRIVATE
+    
+    private IDialogSettings settings;
+    private DocumentType[] doctypes;
+    private Combo typeCombo;
+    private Combo elementCombo;
+
+    /**
+     * Update the elementCombo to reflect elements in the currently selected
+     * type.
+     */
+    private void updateElementCombo() {
+        int index = this.typeCombo.getSelectionIndex();
+        DocumentType dt = this.doctypes[index];
+        this.elementCombo.removeAll();
+        
+        String[] roots = getDocumentType().getRootElements();
+        String selectedRoot = null;
+                       
+        if (roots.length == 0) {
+            Validator validator = dt.getValidator();
+            if (validator != null) {
+                Set set = validator.getValidRootElements();
+                roots = (String[]) set.toArray(new String[set.size()]);
+            }
+        } else {
+            selectedRoot = roots[0];
+        }
+            
+               
+        Arrays.sort(roots);
+        this.elementCombo.setItems(roots);
+
+        if (selectedRoot == null) {
+            // Restore the last used root element
+            String key = SETTINGS_ROOT_ELEMENT_PREFIX + dt.getPublicId();
+            selectedRoot = this.settings.get(key);
+        }
+        
+        this.setPageComplete(false);
+        if (selectedRoot != null) {
+            for (int i = 0; i < roots.length; i++) {
+                if (roots[i].equals(selectedRoot)) {
+                    this.elementCombo.select(i);
+                    this.setPageComplete(true);
+                    break;
+                }
+            }
+        }
+    }
+    
+    /**
+     * Sets the root element combo box when the document type combo box is selected.
+     */
+    private SelectionListener typeComboSelectionListener = new SelectionListener() {
+        public void widgetSelected(SelectionEvent e) {
+            updateElementCombo();
+        }
+        public void widgetDefaultSelected(SelectionEvent e) {
+        }
+    };
+    
+    /**
+     * When a root element is selected, mark the page as complete.
+     */
+    private SelectionListener elementComboSelectionListener = new SelectionListener() {
+        public void widgetSelected(SelectionEvent e) {
+            setPageComplete(true);
+        }
+        public void widgetDefaultSelected(SelectionEvent e) {
+        }
+    };
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/ElementPropertySource.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/ElementPropertySource.java
new file mode 100644
index 0000000..1df5c9d
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/ElementPropertySource.java
@@ -0,0 +1,164 @@
+/*******************************************************************************
+ * 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.ui.internal.editor;
+
+
+import org.eclipse.ui.views.properties.ComboBoxPropertyDescriptor;
+import org.eclipse.ui.views.properties.IPropertyDescriptor;
+import org.eclipse.ui.views.properties.IPropertySource2;
+import org.eclipse.ui.views.properties.PropertyDescriptor;
+import org.eclipse.ui.views.properties.TextPropertyDescriptor;
+import org.eclipse.wst.xml.vex.core.internal.dom.AttributeDefinition;
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentValidationException;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.dom.Validator;
+
+
+/**
+ * Property source that treats element attributes as properties.
+ */
+public class ElementPropertySource implements IPropertySource2 {
+    
+    /**
+     * Class constructor.
+     * @param element Selected element.
+     * @param validator Validator used to get attribute definitions for the element.
+     * @param multi True if multiple elements are selected. In this case
+     * the "id" attribute will not be editable.
+     */
+    public ElementPropertySource(Element element, Validator validator, boolean multi) {
+        this.element = element;
+        this.validator = validator;
+        this.multi = multi;
+    }
+
+    public Object getEditableValue() {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    public IPropertyDescriptor[] getPropertyDescriptors() {
+        
+        // note that elements from DocumentFragments don't have access
+        // to their original document, so we get it from the VexWidget
+        AttributeDefinition[] attrDefs = this.validator.getAttributeDefinitions(this.element.getName());
+        IPropertyDescriptor[] pds = new IPropertyDescriptor[attrDefs.length];
+        for (int i = 0; i < attrDefs.length; i++) {
+            AttributeDefinition def = attrDefs[i];
+            if (this.multi && def.getName().equals(ATTR_ID)) {
+                pds[i] = new PropertyDescriptor(def.getName(), def.getName());
+            } else if (def.isFixed()) {
+                pds[i] = new PropertyDescriptor(def.getName(), def.getName());
+            } else if (def.getType() == AttributeDefinition.Type.ENUMERATION) {
+                pds[i] = new ComboBoxPropertyDescriptor(def.getName(), def.getName(), this.getEnumValues(def));
+            } else {
+                pds[i] = new TextPropertyDescriptor(def.getName(), def.getName());
+            }
+        }
+        return pds;
+    }
+
+    public Object getPropertyValue(Object id) {
+        
+        if (this.multi && id.equals(ATTR_ID)) {
+            return Messages.getString("ElementPropertySource.multiple"); //$NON-NLS-1$
+        }
+            
+        // note that elements from DocumentFragments don't have access
+        // to their original document, so we get it from the VexWidget
+        AttributeDefinition def = this.validator.getAttributeDefinition(this.element.getName(), (String) id);
+        String value = this.element.getAttribute((String) id);
+        if (value == null) {
+            value = def.getDefaultValue();
+            if (value == null) {
+                value = ""; //$NON-NLS-1$
+            }
+        }
+        
+        if (def.getType() == AttributeDefinition.Type.ENUMERATION) {
+            String[] values = this.getEnumValues(def);
+            for (int i = 0; i < values.length; i++) {
+                if (values[i].equals(value)) {
+                    return new Integer(i);
+                }
+            }
+            return new Integer(0); // TODO: if the actual value is not
+            // in the list, we should probably
+            // add it
+        } else {
+            return value;
+        }
+    }
+
+    public boolean isPropertySet(Object id) {
+        // TODO Auto-generated method stub
+        return false;
+    }
+
+    public void resetPropertyValue(Object id) {
+        // TODO Auto-generated method stub
+        
+    }
+
+    public void setPropertyValue(Object id, Object value) {
+
+        try {
+            // note that elements from DocumentFragments don't have access
+            // to their original document, so we get it from the VexWidget
+            AttributeDefinition def = this.validator.getAttributeDefinition(
+                    this.element.getName(), (String) id);
+
+            if (def.getType() == AttributeDefinition.Type.ENUMERATION) {
+                int i = ((Integer) value).intValue();
+                String s = this.getEnumValues(def)[i];
+                if (!def.isRequired() && s.equals("")) { //$NON-NLS-1$
+                    this.element.removeAttribute(def.getName());
+                } else {
+                    this.element.setAttribute(def.getName(), s);
+                }
+            } else {
+                String s = (String) value;
+                if (s.equals("")) { //$NON-NLS-1$
+                    this.element.removeAttribute(def.getName());
+                } else {
+                    this.element.setAttribute(def.getName(), s);
+                }
+            }
+        } catch (DocumentValidationException e) {
+        }
+    }
+
+    public boolean isPropertyResettable(Object id) {
+        // TODO Auto-generated method stub
+        return true;
+    }
+
+    //======================================== PRIVATE
+    
+    private static final String ATTR_ID = "id"; //$NON-NLS-1$
+    
+    private Element element;
+    private Validator validator;
+    private boolean multi;
+    
+    private String[] getEnumValues(AttributeDefinition def) {
+        String[] values = def.getValues();
+        if (def.isRequired()) {
+            return values;
+        } else {
+            String[] values2 = new String[values.length+1];
+            values2[0] = ""; //$NON-NLS-1$
+            System.arraycopy(values, 0, values2, 1, values.length);
+            return values2;
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/IOutlineProvider.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/IOutlineProvider.java
new file mode 100644
index 0000000..096f24b
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/IOutlineProvider.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.ui.internal.editor;
+
+
+import org.eclipse.jface.viewers.IBaseLabelProvider;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+
+/**
+ * Implemented by objects that can provide a document outline.
+ */
+public interface IOutlineProvider {
+
+    /**
+     * Initialize this outline provider. This method is guaranteed to be called
+     * befor any other in this class. The document has been fully created
+     * by the time this method is called, so it is acceptable to access
+     * the Vex Widget and its associated stylesheet and document.
+     * 
+     * @param editor VexEditor with which this outline page is associated.
+     */
+    public void init(VexEditor editor);
+    
+    /**
+     * Returns the content provider that supplies elements representing
+     * the document outline. 
+     */
+    public ITreeContentProvider getContentProvider();
+
+    /**
+     * Returns the label provider for the outline.
+     */
+    public IBaseLabelProvider getLabelProvider();
+
+    /**
+     * Returns the outline element closest to the given child. If
+     * <code>child</code> is an outline element, it is returned directly. 
+     * @param child Element for which to find the outline element.
+     */
+    public Element getOutlineElement(Element child);
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/IVexEditorListener.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/IVexEditorListener.java
new file mode 100644
index 0000000..3e3a9fb
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/IVexEditorListener.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * 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.ui.internal.editor;
+
+/**
+ * Event interface through which VexEditor events are published.
+ */
+public interface IVexEditorListener {
+
+    /**
+     * Called after the editor has successfully loaded a document.
+     */
+    public void documentLoaded(VexEditorEvent event);
+    
+    /**
+     * Called before the editor unloads a document. Note that the editor may
+     * be disposing of the corresponding VexWidget, so any registered 
+     * listeners on the widget should unregister in this event. 
+     */
+    public void documentUnloaded(VexEditorEvent event);
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/InsertAssistant.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/InsertAssistant.java
new file mode 100644
index 0000000..f557ac2
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/InsertAssistant.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.ui.internal.editor;
+
+
+import org.eclipse.jface.action.IAction;
+import org.eclipse.wst.xml.vex.core.internal.swt.VexWidget;
+
+
+/**
+ * Content assistant that shows valid elements to be inserted at the current
+ * point.
+ */
+public class InsertAssistant extends ContentAssistant {
+    
+    public IAction[] getActions(VexWidget vexWidget) {
+        return vexWidget.getValidInsertActions();
+    }
+
+    public String getTitle(VexWidget vexWidget) {
+        return Messages.getString("InsertAssistant.title"); //$NON-NLS-1$
+    }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/Messages.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/Messages.java
new file mode 100644
index 0000000..8b7ce39
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/Messages.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.ui.internal.editor;
+
+import java.text.MessageFormat;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.wst.xml.vex.ui.internal.VexPlugin;
+
+/**
+ * Handler for language-specific strings in Vex.
+ */
+public class Messages {
+
+    private static ResourceBundle resources;
+
+    private Messages() {
+    }
+
+    /**
+     * Returns the language-specific string for the given key,
+     * or the key itself if not found.
+     */
+    public static String getString(String key) {
+        if (resources == null) {
+            resources = ResourceBundle.getBundle("org.eclipse.wst.xml.vex.ui.internal.editor.messages"); //$NON-NLS-1$
+        }
+        
+        try {
+            return resources.getString(key);
+        } catch (MissingResourceException ex) {
+            String message = Messages.getString("Messages.cantFindResource"); //$NON-NLS-1$
+            VexPlugin.getInstance().log(IStatus.WARNING,
+                    MessageFormat.format(message, new Object[] { key }));
+            return key;
+        }
+    }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/MorphAssistant.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/MorphAssistant.java
new file mode 100644
index 0000000..5bd1e5b
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/MorphAssistant.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * 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.ui.internal.editor;
+
+import java.text.MessageFormat;
+
+
+import org.eclipse.jface.action.IAction;
+import org.eclipse.wst.xml.vex.core.internal.swt.VexWidget;
+
+
+/**
+ * Content assistant that shows valid elements to be inserted at the current
+ * point.
+ */
+public class MorphAssistant extends ContentAssistant {
+    
+    public IAction[] getActions(VexWidget vexWidget) {
+        return vexWidget.getValidMorphActions();
+    }
+
+    public String getTitle(VexWidget vexWidget) {
+        String message = Messages.getString("ChangeElementAction.dynamic.label"); //$NON-NLS-1$
+        String name = vexWidget.getCurrentElement().getName();
+        return MessageFormat.format(message, new Object[] { name });
+    }
+
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/NewDocumentWizard.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/NewDocumentWizard.java
new file mode 100644
index 0000000..df5c096
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/NewDocumentWizard.java
@@ -0,0 +1,202 @@
+/*******************************************************************************
+ * 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.ui.internal.editor;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.text.MessageFormat;
+
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.ui.IEditorDescriptor;
+import org.eclipse.ui.IEditorRegistry;
+import org.eclipse.ui.IFileEditorMapping;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.ide.IDE;
+import org.eclipse.ui.internal.registry.EditorDescriptor;
+import org.eclipse.ui.internal.registry.EditorRegistry;
+import org.eclipse.ui.internal.registry.FileEditorMapping;
+import org.eclipse.ui.wizards.newresource.BasicNewResourceWizard;
+import org.eclipse.wst.xml.vex.core.internal.dom.Document;
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentWriter;
+import org.eclipse.wst.xml.vex.core.internal.dom.RootElement;
+import org.eclipse.wst.xml.vex.core.internal.widget.CssWhitespacePolicy;
+import org.eclipse.wst.xml.vex.ui.internal.VexPlugin;
+import org.eclipse.wst.xml.vex.ui.internal.config.Style;
+
+/**
+ * Wizard for creating a new Vex document.
+ */
+public class NewDocumentWizard extends BasicNewResourceWizard {
+
+    public void addPages() {
+        this.typePage = new DocumentTypeSelectionPage();
+        this.filePage = new DocumentFileCreationPage("filePage", this.getSelection()); //$NON-NLS-1$
+        addPage(typePage);
+        addPage(filePage);
+    }
+
+    public void init(
+        IWorkbench workbench,
+        IStructuredSelection currentSelection) {
+
+        super.init(workbench, currentSelection);
+        this.setWindowTitle(Messages.getString("NewDocumentWizard.title")); //$NON-NLS-1$
+    }
+
+    public boolean performFinish() {
+        try {
+            RootElement root = new RootElement(this.typePage.getRootElementName());
+            Document doc = new Document(root);
+            doc.setPublicID(this.typePage.getDocumentType().getPublicId());
+            doc.setSystemID(this.typePage.getDocumentType().getSystemId());
+            
+            
+            Style style = VexEditor.findStyleForDoctype(doc.getPublicID());
+            if (style == null) {
+                MessageDialog.openError(this.getShell(), Messages.getString("NewDocumentWizard.noStyles.title"), Messages.getString("NewDocumentWizard.noStyles.message")); //$NON-NLS-1$ //$NON-NLS-2$
+                return false;
+                // TODO: don't allow selection of types with no stylesheets
+            }
+            
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            DocumentWriter writer = new DocumentWriter();
+            writer.setWhitespacePolicy(new CssWhitespacePolicy(style.getStyleSheet()));
+            writer.write(doc, baos);
+            baos.close();
+            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+            
+            filePage.setInitialContents(bais);
+            IFile file = filePage.createNewFile();
+            IDE.setDefaultEditor(file, VexEditor.ID);
+            this.selectAndReveal(file);
+
+            registerEditorForFilename("*." + file.getFileExtension(), VexEditor.ID); //$NON-NLS-1$
+            
+            // Open editor on new file.
+            IWorkbenchWindow dw = getWorkbench().getActiveWorkbenchWindow();
+            if (dw != null) {
+                IWorkbenchPage page = dw.getActivePage();
+                if (page != null) {
+                    IDE.openEditor(page, file, true);
+                }
+            }
+
+            this.typePage.saveSettings();
+
+            return true;
+            
+        } catch (Exception ex) {
+            String message = MessageFormat.format(
+                    Messages.getString("NewDocumentWizard.errorLoading.message"),
+                    new Object[] { filePage.getFileName(), ex.getMessage() });
+            VexPlugin.getInstance().log(IStatus.ERROR, message, ex);
+            MessageDialog.openError(this.getShell(), Messages.getString("NewDocumentWizard.errorLoading.title"), "Unable to create " + filePage.getFileName()); //$NON-NLS-1$ //$NON-NLS-2$
+            return false;
+        }
+    }
+    
+    //=========================================================== PRIVATE
+    
+    private DocumentTypeSelectionPage typePage;
+    private DocumentFileCreationPage filePage;
+    
+    
+    /**
+     * Register an editor to use for files with the given filename.
+     * 
+     * NOTE: this method uses internal, undocumented Eclipse functionality.
+     * It may therefore break in a future version of Eclipse.
+     * 
+     * @param fileName Filename to be registered. Use the form "*.ext" to register
+     * all files with a given extension.
+     * @param editorId ID of the editor to use for the given filename.
+     */
+    private static void registerEditorForFilename(String fileName, String editorId) {
+        
+        EditorDescriptor ed = getEditorDescriptor(editorId);
+        if (ed == null) {
+            return;
+        }
+        
+        IEditorRegistry reg = PlatformUI.getWorkbench().getEditorRegistry();
+        EditorRegistry ereg = (EditorRegistry) reg;
+        FileEditorMapping[] mappings = (FileEditorMapping[]) ereg.getFileEditorMappings();
+        FileEditorMapping mapping = null;
+        for (int i = 0; i < mappings.length; i++) {
+            IFileEditorMapping fem = mappings[i];
+            if (fem.getLabel().equals(fileName)) {
+                mapping = (FileEditorMapping) fem;
+                break;
+            }
+        }
+        
+        if (mapping != null) {
+            // found mapping for fileName
+            // make sure it includes our editor
+            IEditorDescriptor[] editors = mapping.getEditors();
+            for (int i = 0; i < editors.length; i++) {
+                if (editors[i].getId().equals(editorId)) {
+                    // already mapped
+                    return;
+                }
+            }
+            
+            // editor not in the list, so add it
+            mapping.addEditor(ed);
+            ereg.setFileEditorMappings(mappings);
+            ereg.saveAssociations();
+
+        } else {
+            // no mapping found for the filename
+            // let's add one
+            String name = null;
+            String ext = null;
+            int iDot = fileName.lastIndexOf('.');
+            if (iDot == -1) {
+                name = fileName;
+            } else {
+                name = fileName.substring(0, iDot);
+                ext = fileName.substring(iDot + 1);
+            }
+            
+            mapping = new FileEditorMapping(name, ext);
+            FileEditorMapping[] newMappings = new FileEditorMapping[mappings.length + 1];
+            mapping.addEditor(ed);
+
+            System.arraycopy(mappings, 0, newMappings, 0, mappings.length);
+            newMappings[mappings.length] = mapping;
+            ereg.setFileEditorMappings(newMappings);
+            ereg.saveAssociations();
+        }
+        
+    }
+    
+    /**
+     * Return the IEditorDescriptor for the given editor ID.
+     */
+    private static EditorDescriptor getEditorDescriptor(String editorId) {
+		EditorRegistry reg = (EditorRegistry) PlatformUI.getWorkbench().getEditorRegistry();
+		IEditorDescriptor[] editors = reg.getSortedEditorsFromPlugins();
+		for (int i = 0; i < editors.length; i++) {
+		    if (editors[i].getId().equals(editorId)) {
+		        return (EditorDescriptor) editors[i];
+		    }
+		}
+		return null;
+    }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/SelectionProvider.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/SelectionProvider.java
new file mode 100644
index 0000000..f2ea175
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/SelectionProvider.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.ui.internal.editor;
+
+
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.ISelectionProvider;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.wst.xml.vex.core.internal.core.ListenerList;
+
+/**
+ * Implementation of ISelectionProvider. This class is also an
+ * ISelectionChangedListener; any events received by selectionChanged are
+ * relayed to registered listeners.
+ */
+public class SelectionProvider implements ISelectionProvider,
+        ISelectionChangedListener {
+
+    public void addSelectionChangedListener(ISelectionChangedListener listener) {
+        this.listeners.add(listener);
+    }
+
+    /**
+     * Fire a SelectionChangedEvent to all registered listeners.
+     * @param e Event to be passed to the listeners' selectionChanged method.
+     */
+    public void fireSelectionChanged(SelectionChangedEvent e) {
+        this.selection = e.getSelection();
+        this.listeners.fireEvent("selectionChanged", e); //$NON-NLS-1$
+    }
+    
+    public ISelection getSelection() {
+        return this.selection;
+    }
+
+    public void removeSelectionChangedListener(
+            ISelectionChangedListener listener) {
+        this.listeners.remove(listener);
+    }
+
+    public void setSelection(ISelection selection) {
+        this.selection = selection;
+    }
+
+    public void selectionChanged(SelectionChangedEvent event) {
+        this.fireSelectionChanged(event);
+    }
+
+    //===================================================== PRIVATE
+
+    ISelection selection;
+    private ListenerList listeners = new ListenerList(ISelectionChangedListener.class, SelectionChangedEvent.class);
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/VexActionBarContributor.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/VexActionBarContributor.java
new file mode 100644
index 0000000..742a3d7
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/VexActionBarContributor.java
@@ -0,0 +1,310 @@
+/*******************************************************************************
+ * 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.ui.internal.editor;
+
+
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.action.GroupMarker;
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.action.IMenuListener;
+import org.eclipse.jface.action.IMenuManager;
+import org.eclipse.jface.action.MenuManager;
+import org.eclipse.jface.action.Separator;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.ui.IActionBars;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.ISelectionListener;
+import org.eclipse.ui.IWorkbenchActionConstants;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchPart;
+import org.eclipse.ui.actions.ActionFactory;
+import org.eclipse.ui.part.EditorActionBarContributor;
+import org.eclipse.wst.xml.vex.core.internal.action.ActionUtils;
+import org.eclipse.wst.xml.vex.core.internal.action.DeleteColumnAction;
+import org.eclipse.wst.xml.vex.core.internal.action.DeleteRowAction;
+import org.eclipse.wst.xml.vex.core.internal.action.InsertColumnAfterAction;
+import org.eclipse.wst.xml.vex.core.internal.action.InsertColumnBeforeAction;
+import org.eclipse.wst.xml.vex.core.internal.action.InsertRowAboveAction;
+import org.eclipse.wst.xml.vex.core.internal.action.InsertRowBelowAction;
+import org.eclipse.wst.xml.vex.core.internal.action.MoveColumnLeftAction;
+import org.eclipse.wst.xml.vex.core.internal.action.MoveColumnRightAction;
+import org.eclipse.wst.xml.vex.core.internal.action.MoveRowDownAction;
+import org.eclipse.wst.xml.vex.core.internal.action.MoveRowUpAction;
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentValidationException;
+import org.eclipse.wst.xml.vex.core.internal.swt.VexWidget;
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+import org.eclipse.wst.xml.vex.ui.internal.action.InsertElementAction;
+import org.eclipse.wst.xml.vex.ui.internal.action.VexActionAdapter;
+import org.eclipse.wst.xml.vex.ui.internal.config.Style;
+
+/**
+ * Contribute actions on behalf of the VexEditor.
+ */
+public class VexActionBarContributor extends EditorActionBarContributor {
+    
+    public void dispose() {
+    }
+ 
+    public MenuManager getContextMenuManager() {
+        return this.contextMenuManager;
+    }
+
+    public VexEditor getVexEditor() {
+        return (VexEditor) activeEditor;
+    }
+    
+    public VexWidget getVexWidget() {
+        if (activeEditor != null) {
+            return ((VexEditor) activeEditor).getVexWidget();
+        } else {
+            return null;
+        }
+    }
+    
+    public void init(IActionBars bars, IWorkbenchPage page) {
+        super.init(bars, page);
+
+        this.contextMenuManager = new MenuManager();
+        this.contextMenuManager.setRemoveAllWhenShown(true);
+        this.contextMenuManager.addMenuListener(this.contextMenuListener);
+
+        page.addSelectionListener(this.selectionListener);
+        
+        this.globalCopyAction = ActionFactory.COPY.create(page.getWorkbenchWindow());
+        this.globalCutAction = ActionFactory.CUT.create(page.getWorkbenchWindow());
+        this.globalDeleteAction = ActionFactory.DELETE.create(page.getWorkbenchWindow());
+        this.globalPasteAction = ActionFactory.PASTE.create(page.getWorkbenchWindow());
+        this.globalRedoAction = ActionFactory.REDO.create(page.getWorkbenchWindow());
+        this.globalUndoAction = ActionFactory.UNDO.create(page.getWorkbenchWindow());
+    }
+
+    
+    public void setActiveEditor(IEditorPart activeEditor) {
+        
+        // This can occur if we have an error loading the editor,
+        // in which case Eclipse provides its own part
+        if (!(activeEditor instanceof VexEditor)) {
+            return;
+        }
+        
+        this.activeEditor = activeEditor;
+
+        this.getActionBars().setGlobalActionHandler(
+            ActionFactory.COPY.getId(),
+            this.copyAction);
+            
+        this.getActionBars().setGlobalActionHandler(
+            ActionFactory.CUT.getId(),
+            this.cutAction);
+            
+        this.getActionBars().setGlobalActionHandler(
+            ActionFactory.DELETE.getId(),
+            this.deleteAction);
+            
+        this.getActionBars().setGlobalActionHandler(
+            ActionFactory.PASTE.getId(),
+            this.pasteAction);
+        
+        this.getActionBars().setGlobalActionHandler(
+            ActionFactory.REDO.getId(),
+            this.redoAction);
+        
+        this.getActionBars().setGlobalActionHandler(
+            ActionFactory.SELECT_ALL.getId(),
+            this.selectAllAction);
+
+        this.getActionBars().setGlobalActionHandler(
+            ActionFactory.UNDO.getId(),
+            this.undoAction);
+
+        this.enableActions();
+    }
+    
+    //===================================================== PRIVATE
+    
+    private IEditorPart activeEditor;
+
+    private IAction globalCopyAction;
+    private IAction globalCutAction;
+    private IAction globalDeleteAction;
+    private IAction globalPasteAction;
+    private IAction globalRedoAction;
+    private IAction globalUndoAction;
+    
+    private IAction copyAction = new CopyAction();
+    private IAction cutAction = new CutAction();
+    private IAction deleteAction = new DeleteAction();
+    private IAction pasteAction = new PasteAction();
+    private IAction redoAction = new RedoAction();
+    private IAction selectAllAction = new SelectAllAction();
+    private IAction undoAction = new UndoAction();
+
+    private MenuManager contextMenuManager;
+    private IMenuListener contextMenuListener = new IMenuListener() {
+
+        public void menuAboutToShow(IMenuManager manager) {
+            
+            boolean showTableActions = false;
+            IVexWidget vexWidget = getVexWidget();
+            if (vexWidget != null) {
+                showTableActions = ActionUtils.getSelectedTableRows(vexWidget).getRows() != null;
+            }
+            
+            manager.add(globalUndoAction);
+            manager.add(globalRedoAction);
+            manager.add(new Separator());
+            manager.add(new VexActionAdapter(getVexEditor(), new InsertElementAction()));
+            
+            if (showTableActions) {
+                manager.add(new Separator());
+                manager.add(new RowMenuManager());
+                manager.add(new ColumnMenuManager());
+            }
+            
+            manager.add(new Separator());
+            manager.add(globalCutAction);
+            manager.add(globalCopyAction);
+            manager.add(globalPasteAction);
+            manager.add(new Separator());
+            manager.add(globalDeleteAction);
+            manager.add(new Separator());
+            manager.add(new StyleMenuManager());
+            manager.add(new GroupMarker(IWorkbenchActionConstants.MB_ADDITIONS));
+        }
+        
+    };
+    
+    private void enableActions() {
+        VexWidget widget = this.getVexWidget();
+        this.copyAction.setEnabled(widget != null && widget.hasSelection());
+        this.cutAction.setEnabled(widget != null && widget.hasSelection());
+        this.deleteAction.setEnabled(widget != null && widget.hasSelection());
+        this.redoAction.setEnabled(widget != null && widget.canRedo());
+        this.undoAction.setEnabled(widget != null && widget.canUndo());
+    }
+    
+    private ISelectionListener selectionListener = new ISelectionListener() {
+        public void selectionChanged(IWorkbenchPart part, ISelection selection) {
+            enableActions();
+        }
+    };
+    
+    private class CopyAction extends Action {
+        public void run() {
+            getVexWidget().copySelection();
+        }
+    };
+    
+    
+    private class CutAction extends Action {
+        public void run() {
+            getVexWidget().cutSelection();
+        }
+    }
+    
+    private class DeleteAction extends Action {
+        public void run() {
+            getVexWidget().deleteSelection();
+        }
+    };
+    
+    private class PasteAction extends Action {
+        public void run() {
+            try {
+                getVexWidget().paste();
+            } catch (DocumentValidationException e) {
+                // TODO Auto-generated catch block
+                e.printStackTrace();
+            }    
+        }
+    };
+
+    private class SelectAllAction extends Action {
+        public void run() {
+            getVexWidget().selectAll();
+        }
+    };
+
+    private class SetStyleAction extends Action {
+        public SetStyleAction(Style style) {
+            super(style.getName(), IAction.AS_RADIO_BUTTON);
+            this.style = style;
+        }
+        public void run() {
+            getVexEditor().setStyle(style);
+        }
+        private Style style;
+    }
+    
+    private class StyleMenuManager extends MenuManager {
+        public StyleMenuManager() {
+            super(Messages.getString("VexActionBarContributor.styleMenu.name")); //$NON-NLS-1$
+            final Action noItemsAction = new Action(Messages.getString("VexActionBarContributor.noValidItems")) { public void run() { } }; //$NON-NLS-1$
+            noItemsAction.setEnabled(false);
+            noItemsAction.setChecked(true);
+            this.add(noItemsAction);
+            this.addMenuListener(new IMenuListener() {
+                public void menuAboutToShow(IMenuManager manager) {
+                    manager.removeAll();
+                    String publicId = getVexWidget().getDocument().getPublicID();
+                    Style[] styles = Style.getStylesForDoctype(publicId);
+                    for (int i = 0; i < styles.length; i++) {
+                        Action action = new SetStyleAction(styles[i]);
+                        if (styles[i] == getVexEditor().getStyle()) {
+                            action.setChecked(true);
+                        }
+                        manager.add(action);
+                    }
+                }
+            });
+        }
+    }
+    
+    private class RowMenuManager extends MenuManager {
+        public RowMenuManager() {
+            super(Messages.getString("VexActionBarContributor.rowMenu.name")); //$NON-NLS-1$
+            this.add(new VexActionAdapter(getVexEditor(), new InsertRowAboveAction()));
+            this.add(new VexActionAdapter(getVexEditor(), new InsertRowBelowAction()));
+            this.add(new VexActionAdapter(getVexEditor(), new DeleteRowAction()));
+            this.add(new VexActionAdapter(getVexEditor(), new MoveRowUpAction()));
+            this.add(new VexActionAdapter(getVexEditor(), new MoveRowDownAction()));
+        }
+    }
+    
+    private class ColumnMenuManager extends MenuManager {
+        public ColumnMenuManager() {
+            super(Messages.getString("VexActionBarContributor.columnMenu.name")); //$NON-NLS-1$
+            this.add(new VexActionAdapter(getVexEditor(), new InsertColumnBeforeAction()));
+            this.add(new VexActionAdapter(getVexEditor(), new InsertColumnAfterAction()));
+            this.add(new VexActionAdapter(getVexEditor(), new DeleteColumnAction()));
+            this.add(new VexActionAdapter(getVexEditor(), new MoveColumnLeftAction()));
+            this.add(new VexActionAdapter(getVexEditor(), new MoveColumnRightAction()));
+        }
+    }
+    
+    private class RedoAction extends Action {
+        public void run() {
+            if (getVexWidget().canRedo()) {
+                getVexWidget().redo();
+            }
+        }
+    };
+    
+    private class UndoAction extends Action {
+        public void run() {
+            if (getVexWidget().canUndo()) {
+                getVexWidget().undo();
+            }
+        }
+    }
+    
+    
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/VexActionDelegate.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/VexActionDelegate.java
new file mode 100644
index 0000000..9f79838
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/VexActionDelegate.java
@@ -0,0 +1,122 @@
+/*******************************************************************************
+ * 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.ui.internal.editor;
+
+import java.text.MessageFormat;
+import java.util.HashMap;
+import java.util.Map;
+
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.IWorkbenchWindowActionDelegate;
+import org.eclipse.wst.xml.vex.core.internal.action.IVexAction;
+import org.eclipse.wst.xml.vex.core.internal.action.RemoveElementAction;
+import org.eclipse.wst.xml.vex.core.internal.swt.VexWidget;
+import org.eclipse.wst.xml.vex.core.internal.widget.IVexWidget;
+import org.eclipse.wst.xml.vex.ui.internal.VexPlugin;
+import org.eclipse.wst.xml.vex.ui.internal.action.ChangeElementAction;
+
+/**
+ * An IWorkbenchWindowActionDelegate that defers to to an instance of IVexAction.
+ * The IDs of actions in plugin.xml using this delegate must be the classnames 
+ * of actions implementing IVexAction. Such classes must have a no-args constructor. 
+ */
+public class VexActionDelegate implements IWorkbenchWindowActionDelegate {
+
+    private static Map actions = new HashMap();
+    
+    public VexActionDelegate() {
+    }
+
+    public void dispose() {
+    }
+
+    public void init(IWorkbenchWindow window) {
+        this.window = window;
+    }
+
+    public void run(IAction action) {
+        IVexAction vexAction = getAction(action.getId());
+        if (vexAction == null) {
+            return;
+        }
+
+        VexWidget vexWidget = this.getVexWidget();
+        if (vexWidget != null) {
+            vexWidget.setFocus();
+            vexAction.run(vexWidget);
+        }
+    }
+
+    public void selectionChanged(IAction action, ISelection selection) {
+
+        IVexAction vexAction = getAction(action.getId());
+        boolean enabled;
+
+        if (vexAction == null) {
+            enabled = false;
+        } else {
+            IVexWidget vexWidget = this.getVexWidget();
+            if (vexWidget == null) {
+                enabled = false;
+            } else {
+                enabled = vexAction.isEnabled(vexWidget);
+                
+                if (action.getId().equals(ChangeElementAction.class.getName())) {
+                    String elementName = vexWidget.getCurrentElement().getName();
+                    String message = Messages.getString("ChangeElementAction.dynamic.label"); //$NON-NLS-1$
+                    action.setText(MessageFormat.format(message, new Object[] { elementName }));
+                }
+
+                if (action.getId().equals(RemoveElementAction.class.getName())) {
+                    String elementName = vexWidget.getCurrentElement().getName();
+                    String message = Messages.getString("RemoveElementAction.dynamic.label"); //$NON-NLS-1$
+                    action.setText(MessageFormat.format(message, new Object[] { elementName }));
+                }
+            }
+        }
+        
+        action.setEnabled(enabled);
+    }
+
+    
+    private IWorkbenchWindow window;
+    
+    private VexWidget getVexWidget() {
+        IEditorPart editor = this.window.getActivePage().getActiveEditor();
+        if (editor != null && editor instanceof VexEditor) {
+            return ((VexEditor) editor).getVexWidget();
+        } else {
+            return null;
+        }
+    }
+
+    private static IVexAction getAction(String actionId) {
+        try {
+            IVexAction action = (IVexAction) actions.get(actionId);
+            if (action == null) {
+                action = (IVexAction) Class.forName(actionId).newInstance();
+                actions.put(actionId, action);
+            }
+            return action;
+        } catch (Exception ex) {
+            String message = MessageFormat.format(
+                    Messages.getString("VexActionDelegate.errorLoadingAction"),   //$NON-NLS-1$
+                    new Object[] { actionId, ex.getMessage() });
+            VexPlugin.getInstance().log(IStatus.ERROR, message, ex);
+            return null;
+        }
+    }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/VexEditor.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/VexEditor.java
new file mode 100644
index 0000000..b9ff330
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/VexEditor.java
@@ -0,0 +1,987 @@
+/*******************************************************************************
+ * 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.ui.internal.editor;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URL;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IResourceChangeEvent;
+import org.eclipse.core.resources.IResourceChangeListener;
+import org.eclipse.core.resources.IResourceDelta;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.preferences.IPreferencesService;
+import org.eclipse.core.runtime.preferences.InstanceScope;
+import org.eclipse.jface.action.MenuManager;
+import org.eclipse.jface.commands.ActionHandler;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorSite;
+import org.eclipse.ui.IFileEditorInput;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.contexts.IContextService;
+import org.eclipse.ui.dialogs.SaveAsDialog;
+import org.eclipse.ui.editors.text.ILocationProvider;
+import org.eclipse.ui.handlers.IHandlerService;
+import org.eclipse.ui.part.EditorPart;
+import org.eclipse.ui.part.FileEditorInput;
+import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
+import org.eclipse.ui.views.properties.IPropertySheetPage;
+import org.eclipse.ui.views.properties.IPropertySource;
+import org.eclipse.ui.views.properties.IPropertySourceProvider;
+import org.eclipse.ui.views.properties.PropertySheetPage;
+import org.eclipse.wst.xml.vex.core.internal.action.DeleteColumnAction;
+import org.eclipse.wst.xml.vex.core.internal.action.DeleteRowAction;
+import org.eclipse.wst.xml.vex.core.internal.action.DuplicateSelectionAction;
+import org.eclipse.wst.xml.vex.core.internal.action.InsertColumnAfterAction;
+import org.eclipse.wst.xml.vex.core.internal.action.InsertColumnBeforeAction;
+import org.eclipse.wst.xml.vex.core.internal.action.InsertRowAboveAction;
+import org.eclipse.wst.xml.vex.core.internal.action.InsertRowBelowAction;
+import org.eclipse.wst.xml.vex.core.internal.action.MoveColumnLeftAction;
+import org.eclipse.wst.xml.vex.core.internal.action.MoveColumnRightAction;
+import org.eclipse.wst.xml.vex.core.internal.action.MoveRowDownAction;
+import org.eclipse.wst.xml.vex.core.internal.action.MoveRowUpAction;
+import org.eclipse.wst.xml.vex.core.internal.action.NextTableCellAction;
+import org.eclipse.wst.xml.vex.core.internal.action.PasteTextAction;
+import org.eclipse.wst.xml.vex.core.internal.action.PreviousTableCellAction;
+import org.eclipse.wst.xml.vex.core.internal.action.RemoveElementAction;
+import org.eclipse.wst.xml.vex.core.internal.action.RestoreLastSelectionAction;
+import org.eclipse.wst.xml.vex.core.internal.action.SplitAction;
+import org.eclipse.wst.xml.vex.core.internal.action.SplitItemAction;
+import org.eclipse.wst.xml.vex.core.internal.core.ListenerList;
+import org.eclipse.wst.xml.vex.core.internal.dom.Document;
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentReader;
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentWriter;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.dom.IWhitespacePolicy;
+import org.eclipse.wst.xml.vex.core.internal.dom.IWhitespacePolicyFactory;
+import org.eclipse.wst.xml.vex.core.internal.dom.Validator;
+import org.eclipse.wst.xml.vex.core.internal.swt.VexWidget;
+import org.eclipse.wst.xml.vex.core.internal.widget.CssWhitespacePolicy;
+import org.eclipse.wst.xml.vex.ui.internal.VexPlugin;
+import org.eclipse.wst.xml.vex.ui.internal.action.ChangeElementAction;
+import org.eclipse.wst.xml.vex.ui.internal.action.InsertElementAction;
+import org.eclipse.wst.xml.vex.ui.internal.action.VexActionAdapter;
+import org.eclipse.wst.xml.vex.ui.internal.config.ConfigEvent;
+import org.eclipse.wst.xml.vex.ui.internal.config.ConfigRegistry;
+import org.eclipse.wst.xml.vex.ui.internal.config.DocumentType;
+import org.eclipse.wst.xml.vex.ui.internal.config.IConfigListener;
+import org.eclipse.wst.xml.vex.ui.internal.config.Style;
+import org.osgi.service.prefs.BackingStoreException;
+import org.osgi.service.prefs.Preferences;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+/**
+ * Editor for editing XML file using the VexWidget.
+ */
+public class VexEditor extends EditorPart {
+
+    /**
+     * ID of this editor extension.
+     */
+    public static final String ID = "org.eclipse.wst.xml.vex.ui.internal.editor.VexEditor"; //$NON-NLS-1$
+    
+    /**
+     * Class constructor.
+     */
+    public VexEditor() {
+        this.debugging = VexPlugin.getInstance().isDebugging() &&
+            "true".equalsIgnoreCase(Platform.getDebugOption(VexPlugin.ID + "/debug/layout")); //$NON-NLS-1$ //$NON-NLS-2$
+    }
+
+    /**
+     * Add a VexEditorListener to the notification list.
+     * @param listener VexEditorListener to be added. 
+     */
+    public void addVexEditorListener(IVexEditorListener listener) {
+        this.vexEditorListeners.add(listener);
+    }
+    
+    public void dispose() {
+        super.dispose();
+        
+        if (this.parentControl != null) {
+            // createPartControl was called, so we must de-register from config events
+            ConfigRegistry.getInstance().removeConfigListener(this.configListener);
+        }
+        
+        if (getEditorInput() instanceof IFileEditorInput) {
+            ResourcesPlugin.getWorkspace().removeResourceChangeListener(this.resourceChangeListener);
+        }
+        
+    }
+
+    public void doSave(IProgressMonitor monitor) {
+
+        IEditorInput input = this.getEditorInput();
+        OutputStream os = null;
+        try {
+            this.resourceChangeListener.setSaving(true);
+            DocumentWriter writer = new DocumentWriter();
+            writer.setWhitespacePolicy(new CssWhitespacePolicy(this.style.getStyleSheet()));
+            
+            if (input instanceof IFileEditorInput) {
+                ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                writer.write(this.doc, baos);
+                baos.close();
+                ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+                ((IFileEditorInput) input).getFile().setContents(bais, false, false, monitor);
+            } else {
+                os = new FileOutputStream(((ILocationProvider) input).getPath(input).toFile());
+                writer.write(this.doc, os);
+            }
+            
+            this.savedUndoDepth = this.vexWidget.getUndoDepth();
+            this.firePropertyChange(EditorPart.PROP_DIRTY);
+        
+        } catch (Exception ex) {
+            monitor.setCanceled(true);
+            String title = Messages.getString("VexEditor.errorSaving.title"); //$NON-NLS-1$
+            String message = MessageFormat.format(
+                    Messages.getString("VexEditor.errorSaving.message"), //$NON-NLS-1$
+                    new Object[] { input.getName(), ex.getMessage() });
+            MessageDialog.openError(this.getEditorSite().getShell(), title, message);
+            VexPlugin.getInstance().log(IStatus.ERROR, message, ex);
+        } finally {
+            if (os != null) {
+                try {
+                    os.close();
+                } catch (IOException e) {
+                }
+            }
+            this.resourceChangeListener.setSaving(false);
+        }
+    }
+
+    public void doSaveAs() {
+        SaveAsDialog dlg = new SaveAsDialog(this.getSite().getShell());
+        int result = dlg.open();
+        if (result == Window.OK) {
+            IPath path = dlg.getResult();
+            try {
+                this.resourceChangeListener.setSaving(true);
+                ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                DocumentWriter writer = new DocumentWriter();
+                writer.setWhitespacePolicy(new CssWhitespacePolicy(this.style.getStyleSheet()));
+                writer.write(this.doc, baos);
+                baos.close();
+                
+
+                ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+                IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(path);
+                file.create(bais, false, null);
+
+                IFileEditorInput input = new FileEditorInput(file);
+                this.setInput(input);
+                this.savedUndoDepth = this.vexWidget.getUndoDepth();
+
+                this.firePropertyChange(EditorPart.PROP_DIRTY);
+                this.firePropertyChange(EditorPart.PROP_INPUT);
+                this.firePropertyChange(EditorPart.PROP_TITLE);
+
+            } catch (Exception ex) {
+                String title = Messages.getString("VexEditor.errorSaving.title"); //$NON-NLS-1$
+                String message = MessageFormat.format(
+                        Messages.getString("VexEditor.errorSaving.message"), //$NON-NLS-1$
+                        new Object[] { path, ex.getMessage() });
+                MessageDialog.openError(this.getEditorSite().getShell(), title, message);
+                VexPlugin.getInstance().log(IStatus.ERROR, message, ex);
+            } finally {
+                this.resourceChangeListener.setSaving(false);
+            }
+        }
+        
+    }
+    
+    /**
+     * Return a reasonable style for the given doctype.
+     * 
+     * @param publicId Public ID for which to return the style.
+     */
+    public static Style findStyleForDoctype(String publicId) {
+        
+        IPreferencesService preferences = Platform.getPreferencesService();
+        String key = getStylePreferenceKey(publicId);
+        String preferredStyleId = preferences.getString(VexPlugin.ID, key, null, null);
+        
+        Preferences prefs = new InstanceScope().getNode(VexPlugin.ID);
+        preferredStyleId = prefs.get(key, null);
+        
+        Style firstStyle = null;
+        ConfigRegistry registry = ConfigRegistry.getInstance();
+        List styles = registry.getAllConfigItems(Style.EXTENSION_POINT);
+        for (Iterator it = styles.iterator(); it.hasNext(); ) {
+            Style style = (Style) it.next();
+            if (style.appliesTo(publicId)) {
+                if (firstStyle == null) {
+                    firstStyle = style;
+                }
+                if (style.getUniqueId().equals(preferredStyleId)) {
+                    return style;
+                }
+            }
+        }
+        return firstStyle;
+    }
+    
+    
+    /**
+     * Returns the DocumentType associated with this editor.
+     */
+    public DocumentType getDocumentType() {
+        return this.doctype;
+    }
+
+    /**
+     * Returns the Style currently associated with the editor. May be null.
+     */
+    public Style getStyle() {
+        return style;
+    }
+
+    /**
+     * Returns the VexWidget that implements this editor.
+     */
+    public VexWidget getVexWidget() {
+        return this.vexWidget;
+    }
+    
+    public void gotoMarker(IMarker marker) {
+        // TODO Auto-generated method stub
+        
+    }
+
+    public void init(IEditorSite site, IEditorInput input) throws PartInitException {
+
+        this.setSite(site);
+        this.setInput(input);
+        
+        this.getEditorSite().setSelectionProvider(this.selectionProvider);
+        this.getEditorSite().getSelectionProvider().addSelectionChangedListener(selectionChangedListener);
+        
+        if (input instanceof IFileEditorInput) {
+            ResourcesPlugin.getWorkspace().addResourceChangeListener(this.resourceChangeListener, IResourceChangeEvent.POST_CHANGE);
+        }
+    }
+    
+    private void loadInput() {
+
+        if (this.vexWidget != null) {
+            this.vexEditorListeners.fireEvent("documentUnloaded", new VexEditorEvent(this)); //$NON-NLS-1$
+        }
+
+        this.loaded = false;
+        
+        IEditorInput input = this.getEditorInput();
+        
+        try {
+            long start = System.currentTimeMillis();
+            
+            IPath inputPath = null;
+            
+            if (input instanceof IFileEditorInput) {
+                inputPath = ((IFileEditorInput) input).getFile().getRawLocation();
+            } else if (input instanceof ILocationProvider) {
+                // Yuck, this a crappy way for Eclipse to do this
+                // How about an exposed IJavaFileEditorInput, pleeze?
+                inputPath = ((ILocationProvider) input).getPath(input);
+            } else {
+                String msg = MessageFormat.format(
+                        Messages.getString("VexEditor.unknownInputClass"), //$NON-NLS-1$
+                        new Object[] { input.getClass() });
+                this.showLabel(msg);
+                return;
+            }
+            
+            URL url = inputPath.toFile().toURL();
+            
+            DocumentReader reader = new DocumentReader();
+            reader.setDebugging(this.debugging);
+            reader.setEntityResolver(entityResolver);
+            reader.setWhitespacePolicyFactory(wsFactory);
+            this.doctype = null;
+            this.doc = reader.read(url);
+            
+            if (this.debugging) {
+                long end = System.currentTimeMillis();
+                System.out.println("Parsed document in " + (end-start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$
+            }
+            
+            // this.doctype is set either by wsPolicyFactory or entityResolver
+            // this.style is set by wsPolicyFactory
+            // Otherwise, a PartInitException would have been thrown by now
+            
+            Validator validator = this.doctype.getValidator();
+            if (validator != null) {
+                this.doc.setValidator(validator);
+                if (this.debugging) {
+                    long end = System.currentTimeMillis();
+                    System.out.println("Got validator in " + (end-start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$
+                }
+            }
+
+            this.showVexWidget();
+            
+            this.vexWidget.setDebugging(debugging);
+            this.vexWidget.setDocument(this.doc, this.style.getStyleSheet());
+            
+            if (this.updateDoctypeDecl) {
+                this.doc.setPublicID(this.doctype.getPublicId());
+                this.doc.setSystemID(this.doctype.getSystemId());
+                this.doSave(null);
+            }
+
+            this.loaded = true;
+            this.savedUndoDepth = this.vexWidget.getUndoDepth();
+            firePropertyChange(EditorPart.PROP_DIRTY);
+            this.wasDirty = isDirty();
+
+            this.vexEditorListeners.fireEvent("documentLoaded", new VexEditorEvent(this)); //$NON-NLS-1$
+
+        } catch (SAXParseException ex) {
+            
+            if (ex.getException() instanceof NoRegisteredDoctypeException) {
+                // TODO doc did not have document type and the user 
+                // declined to select another one. Should fail silently.
+                String msg;
+                NoRegisteredDoctypeException ex2 = (NoRegisteredDoctypeException) ex.getException();
+                if (ex2.getPublicId() == null) {
+                    msg = Messages.getString("VexEditor.noDoctype"); //$NON-NLS-1$
+                } else {
+                    msg = MessageFormat.format(
+                            Messages.getString("VexEditor.unknownDoctype"), //$NON-NLS-1$
+                            new Object[] {  ex2.getPublicId() });
+                }
+                this.showLabel(msg);
+            } else if (ex.getException() instanceof NoStyleForDoctypeException) {
+                String msg = MessageFormat.format(
+                        Messages.getString("VexEditor.noStyles"), //$NON-NLS-1$
+                        new Object[] { this.doctype.getPublicId() });
+                this.showLabel(msg);
+            } else {
+                String file = ex.getSystemId();
+                if (file == null) {
+                    file = input.getName();
+                }
+                
+                String msg = MessageFormat.format(
+                        Messages.getString("VexEditor.parseError"), //$NON-NLS-1$
+                        new Object[] {  new Integer(ex.getLineNumber()), file, ex.getLocalizedMessage() });
+                
+                this.showLabel(msg);
+
+                VexPlugin.getInstance().log(IStatus.ERROR, msg, ex);
+            }
+            
+        } catch (Exception ex) {
+            
+            String msg = MessageFormat.format(
+                    Messages.getString("VexEditor.unexpectedError"), //$NON-NLS-1$
+                    new Object[] {  input.getName() });
+            
+            VexPlugin.getInstance().log(IStatus.ERROR, msg, ex);
+            
+            this.showLabel(msg);
+        }
+    }
+
+    public boolean isDirty() {
+        if (this.vexWidget != null) {
+            return this.savedUndoDepth != this.vexWidget.getUndoDepth();
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Returns true if this editor has finished loading its document.
+     */
+    public boolean isLoaded() {
+        return this.loaded;
+    }
+    
+    public boolean isSaveAsAllowed() {
+        return true;
+    }
+
+    public void createPartControl(Composite parent) {
+        
+        this.parentControl = parent;
+        
+        ConfigRegistry registry = ConfigRegistry.getInstance();
+        
+        registry.addConfigListener(this.configListener);
+        
+        if (registry.isConfigLoaded()) {
+            this.loadInput();
+        } else {
+            this.showLabel(Messages.getString("VexEditor.loading")); //$NON-NLS-1$
+        }
+    }
+
+    /**
+     * Remove a VexEditorListener from the notification list.
+     * @param listener VexEditorListener to be removed. 
+     */
+    public void removeVexEditorListener(IVexEditorListener listener) {
+        this.vexEditorListeners.remove(listener);
+    }
+    
+
+    public void setFocus() {
+        if (this.vexWidget != null) {
+            this.vexWidget.setFocus();
+            setStatus(getLocation());
+        }
+    }
+
+    protected void setInput(IEditorInput input) {
+        super.setInput(input);
+        this.setPartName(input.getName());
+        this.setContentDescription(input.getName());
+        this.setTitleToolTip(input.getToolTipText());
+    }
+    
+    public void setStatus(String text) {
+        //this.statusLabel.setText(text);
+        this.getEditorSite().getActionBars().getStatusLineManager().setMessage(text);
+    }
+
+    /**
+     * Sets the style for this editor.
+     * @param style Style to use.
+     */
+    public void setStyle(Style style) {
+        this.style = style;
+        if (this.vexWidget != null) {
+            this.vexWidget.setStyleSheet(style.getStyleSheet());
+            Preferences prefs = new InstanceScope().getNode(VexPlugin.ID);
+            String key = getStylePreferenceKey(this.doc.getPublicID());
+            prefs.put(key, style.getUniqueId());
+            try {
+                prefs.flush();
+            } catch (BackingStoreException e) {
+                VexPlugin.getInstance().log(IStatus.ERROR, Messages.getString("VexEditor.errorSavingStylePreference"), e); //$NON-NLS-1$
+            }
+        }
+    }
+
+    //========================================================= PRIVATE
+
+    private boolean debugging;
+
+    private Composite parentControl;
+    private Label loadingLabel;
+
+    private boolean loaded;
+    private DocumentType doctype;
+    private Document doc;
+    private Style style;
+
+    private VexWidget vexWidget;
+    
+    private int savedUndoDepth;
+    private boolean wasDirty;
+    //private Label statusLabel;
+    
+    // This is true if the document's doctype decl is missing or unrecognized
+    // AND the user selected a new document type
+    // AND the user wants to always use the doctype for this document
+    private boolean updateDoctypeDecl;
+    
+    private ListenerList vexEditorListeners = new ListenerList(IVexEditorListener.class, VexEditorEvent.class);
+
+    private SelectionProvider selectionProvider = new SelectionProvider();
+    
+    /**
+     * Returns the preference key used to access the style ID for documents
+     * with the same public ID as the current document.
+     */
+    private static String getStylePreferenceKey(String publicId) {
+        return publicId + ".style"; //$NON-NLS-1$
+    }
+    
+    private void showLabel(String message) {
+        if (this.loadingLabel == null) {
+            if (this.vexWidget != null) {
+                this.vexWidget.dispose();
+                this.vexWidget = null;
+            }
+            this.loadingLabel = new Label(this.parentControl, SWT.WRAP);
+        }
+        this.loadingLabel.setText(message);
+        this.parentControl.layout(true);
+    }
+    
+    private void showVexWidget() {
+        
+        if (this.vexWidget != null) {
+            return;
+        }
+        
+        if (this.loadingLabel != null) {
+            this.loadingLabel.dispose();
+            this.loadingLabel = null;
+        }
+        
+        GridLayout layout = new GridLayout();
+        layout.numColumns = 1;
+        layout.verticalSpacing = 0;
+        layout.marginHeight = 0;
+        layout.marginWidth = 0;
+        this.parentControl.setLayout(layout);
+        GridData gd;
+        
+        //StatusPanel statusPanel = new StatusPanel(this.parentControl);
+        
+//        Composite statusPanel = new Composite(this.parentControl, SWT.NONE);
+//        statusPanel.setLayout(new GridLayout());
+//        gd = new GridData();
+//        gd.grabExcessHorizontalSpace = true;
+//        gd.horizontalAlignment = GridData.FILL;
+//        statusPanel.setLayoutData(gd);
+        
+//        this.statusLabel = new Label(statusPanel, SWT.NONE);
+//        gd = new GridData();
+//        gd.grabExcessHorizontalSpace = true;
+//        gd.horizontalAlignment = GridData.FILL;
+//        this.statusLabel.setLayoutData(gd);
+        
+        gd = new GridData();
+        gd.grabExcessHorizontalSpace = true;
+        gd.grabExcessVerticalSpace = true;
+        gd.horizontalAlignment = GridData.FILL;
+        gd.verticalAlignment = GridData.FILL;
+        
+        this.vexWidget = new VexWidget(this.parentControl, SWT.V_SCROLL);
+        gd = new GridData();
+        gd.grabExcessHorizontalSpace = true;
+        gd.grabExcessVerticalSpace = true;
+        gd.horizontalAlignment = GridData.FILL;
+        gd.verticalAlignment = GridData.FILL;
+        this.vexWidget.setLayoutData(gd);
+        
+        VexActionBarContributor contributor = (VexActionBarContributor)
+            this.getEditorSite().getActionBarContributor();
+        
+        MenuManager menuMgr = contributor.getContextMenuManager();
+        this.getSite().registerContextMenu(menuMgr, this.vexWidget);
+        this.vexWidget.setMenu(menuMgr.createContextMenu(this.vexWidget));
+        
+        this.savedUndoDepth = this.vexWidget.getUndoDepth();
+        
+        // new for scopes
+        IContextService cs = (IContextService) this.getSite().getService(IContextService.class);
+        cs.activateContext("org.eclipse.wst.xml.vex.ui.VexEditorContext");
+        
+        IHandlerService hs = (IHandlerService) this.getSite().getService(IHandlerService.class);
+        
+        hs.activateHandler(
+        		"org.eclipse.wst.xml.vex.ui.action.ChangeElementAction",
+        		new ActionHandler(new VexActionAdapter(this, new ChangeElementAction())));
+        
+        hs.activateHandler("org.eclipse.wst.xml.vex.ui.action.DeleteColumnAction",
+        		new ActionHandler(new VexActionAdapter(this, new DeleteColumnAction())));
+        
+        hs.activateHandler("org.eclipse.wst.xml.vex.ui.action.DeleteRowAction",
+        		new ActionHandler(new VexActionAdapter(this, new DeleteRowAction())));
+        
+        hs.activateHandler("org.eclipse.wst.xml.vex.ui.action.DuplicateSelectionAction",
+        		new ActionHandler(new VexActionAdapter(this, new DuplicateSelectionAction())));
+        
+        hs.activateHandler("org.eclipse.wst.xml.vex.ui.action.InsertColumnAfterAction",
+        		new ActionHandler(new VexActionAdapter(this, new InsertColumnAfterAction())));
+        
+        hs.activateHandler("org.eclipse.wst.xml.vex.ui.action.InsertColumnBeforeAction",
+        		new ActionHandler(new VexActionAdapter(this, new InsertColumnBeforeAction())));
+        		
+        hs.activateHandler("net.sf.vex.editor.action.InsertElementAction",
+        		new ActionHandler(new VexActionAdapter(this, new InsertElementAction())));
+        
+        hs.activateHandler("org.eclipse.wst.xml.vex.ui.action.InsertRowAboveAction",
+        		new ActionHandler(new VexActionAdapter(this, new InsertRowAboveAction())));
+        
+        hs.activateHandler("org.eclipse.wst.xml.vex.ui.action.InsertRowBelowAction",
+        		new ActionHandler(new VexActionAdapter(this, new InsertRowBelowAction())));
+        
+        hs.activateHandler("org.eclipse.wst.xml.vex.ui.action.MoveColumnLeftAction",
+        		new ActionHandler(new VexActionAdapter(this, new MoveColumnLeftAction())));
+        
+        hs.activateHandler("org.eclipse.wst.xml.vex.ui.action.MoveColumnRightAction",
+        		new ActionHandler(new VexActionAdapter(this, new MoveColumnRightAction())));
+        
+        hs.activateHandler("org.eclipse.wst.xml.vex.ui.action.MoveRowDownAction",
+        		new ActionHandler(new VexActionAdapter(this, new MoveRowDownAction())));
+        
+        hs.activateHandler("org.eclipse.wst.xml.vex.ui.action.MoveRowUpAction",
+        		new ActionHandler(new VexActionAdapter(this, new MoveRowUpAction())));
+        
+        hs.activateHandler("org.eclipse.wst.xml.vex.ui.action.NextTableCellAction",
+        		new ActionHandler(new VexActionAdapter(this, new NextTableCellAction())));
+        
+        hs.activateHandler("org.eclipse.wst.xml.vex.ui.action.PasteTextAction",
+        		new ActionHandler(new VexActionAdapter(this, new PasteTextAction())));
+        
+        hs.activateHandler("org.eclipse.wst.xml.vex.ui.action.PreviousTableCellAction",
+        		new ActionHandler(new VexActionAdapter(this, new PreviousTableCellAction())));
+        
+        hs.activateHandler("org.eclipse.wst.xml.vex.ui.action.RemoveElementAction",
+        		new ActionHandler(new VexActionAdapter(this, new RemoveElementAction())));
+        
+        hs.activateHandler("org.eclipse.wst.xml.vex.ui.action.RestoreLastSelectionAction",
+        		new ActionHandler(new VexActionAdapter(this, new RestoreLastSelectionAction())));
+        
+        hs.activateHandler("org.eclipse.wst.xml.vex.ui.action.SplitAction",
+        		new ActionHandler(new VexActionAdapter(this, new SplitAction())));
+        
+        hs.activateHandler("org.eclipse.wst.xml.vex.ui.action.SplitItemAction",
+        		new ActionHandler(new VexActionAdapter(this, new SplitItemAction())));        
+        
+
+        this.vexWidget.addSelectionChangedListener(this.selectionProvider);
+
+        this.parentControl.layout(true);
+
+    }
+
+    private void handleResourceChanged(IResourceDelta delta) {
+        
+        if (delta.getKind() == IResourceDelta.CHANGED) {
+            if ((delta.getFlags() & IResourceDelta.CONTENT) != 0) {
+                this.handleResourceContentChanged();
+            }
+        } else if (delta.getKind() == IResourceDelta.REMOVED) {
+            if ((delta.getFlags() & IResourceDelta.MOVED_TO) != 0) {
+                IPath toPath = delta.getMovedToPath();
+                IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(toPath);
+                this.setInput(new FileEditorInput(file));
+            } else {
+                if (!this.isDirty()) {
+                    this.getEditorSite().getPage().closeEditor(this, false);
+                } else {
+                    this.handleResourceDeleted();
+                }
+            }
+        }
+        
+    }
+    
+    
+    private void handleResourceContentChanged() {
+
+        if (!this.isDirty()) {
+            this.loadInput();
+        } else {
+            
+            String message = MessageFormat.format(
+                    Messages.getString("VexEditor.docChanged.message"), //$NON-NLS-1$
+                    new Object[] {  this.getEditorInput().getName() });
+
+            MessageDialog dlg = new MessageDialog(this.getSite().getShell(),
+                    Messages.getString("VexEditor.docChanged.title"), //$NON-NLS-1$
+                    null,
+                    message,
+                    MessageDialog.QUESTION,
+                    new String[] { 
+                        Messages.getString("VexEditor.docChanged.discard"), //$NON-NLS-1$
+                        Messages.getString("VexEditor.docChanged.overwrite") }, //$NON-NLS-1$
+                    1);
+                    
+            int result = dlg.open();
+            
+            if (result == 0) { // Discard my changes
+                this.loadInput();
+            } else { // Overwrite other changes
+                this.doSave(null);
+            }
+        }
+    }
+
+    
+    private void handleResourceDeleted() {
+
+        String message = MessageFormat.format(
+                Messages.getString("VexEditor.docDeleted.message"), //$NON-NLS-1$
+                new Object[] {  this.getEditorInput().getName() });
+
+        MessageDialog dlg = new MessageDialog(this.getSite().getShell(),
+                Messages.getString("VexEditor.docDeleted.title"), //$NON-NLS-1$
+                null,
+                message,
+                MessageDialog.QUESTION,
+                new String[] { 
+                    Messages.getString("VexEditor.docDeleted.discard"), //$NON-NLS-1$ 
+                    Messages.getString("VexEditor.docDeleted.save") }, //$NON-NLS-1$
+                1);
+        
+        int result = dlg.open();
+        
+        if (result == 0) { // Discard
+            
+            this.getEditorSite().getPage().closeEditor(this, false);
+            
+        } else { // Save
+            
+            this.doSaveAs();
+            
+            // Check if they saved or not. If not, close the editor
+            if (!this.getEditorInput().exists()) {
+                this.getEditorSite().getPage().closeEditor(this, false);
+            }
+        }
+    }
+
+    
+    // Listen for stylesheet changes and respond appropriately
+    private IConfigListener configListener = new IConfigListener() {
+
+        public void configChanged(ConfigEvent e) {
+            if (style != null) {
+                ConfigRegistry registry = ConfigRegistry.getInstance();
+                String currId = style.getUniqueId();
+                Style newStyle = (Style) registry.getConfigItem(Style.EXTENSION_POINT, currId);
+                if (newStyle == null) {
+                    // Oops, style went bye-bye
+                    // Let's just hold on to it in case it comes back later
+                } else {
+                    vexWidget.setStyleSheet(newStyle.getStyleSheet());
+                    style = newStyle;
+                }
+            }
+        }
+        
+        public void configLoaded(ConfigEvent e) {
+            loadInput();
+        }
+    };
+    
+    private ISelectionChangedListener selectionChangedListener = new ISelectionChangedListener() {
+        public void selectionChanged(SelectionChangedEvent event) {
+            if (isDirty() != wasDirty) {
+                firePropertyChange(EditorPart.PROP_DIRTY);
+                wasDirty = isDirty();
+            }
+            setStatus(getLocation());
+        }
+    };
+
+    
+    private EntityResolver entityResolver = new EntityResolver() {
+        public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
+            
+            //System.out.println("### Resolving publicId " + publicId + ", systemId " + systemId);
+
+            if (doctype == null) {
+                //
+                // If doctype hasn't already been set, this must be the doctype
+                // decl.
+                //
+                if (publicId != null) {
+                    doctype = DocumentType.getDocumentType(publicId);
+                }
+                
+                if (doctype == null) {
+                    DocumentTypeSelectionDialog dlg = DocumentTypeSelectionDialog.create(getSite().getShell(), publicId);
+                    dlg.open();
+                    doctype = dlg.getDoctype();
+                    updateDoctypeDecl = dlg.alwaysUseThisDoctype();
+                    
+                    if (doctype == null) {
+                        throw new NoRegisteredDoctypeException(publicId); 
+                    }
+                }
+                
+                URL url = doctype.getResourceUrl();
+                
+                if (url == null) {
+                    String message = MessageFormat.format(
+                            Messages.getString("VexEditor.noUrlForDoctype"), //$NON-NLS-1$
+                            new Object[] { publicId });
+                    throw new RuntimeException(message);
+                }
+                
+                return new InputSource(url.toString());
+            } else {
+                return null;
+            }
+        }
+    };
+    
+    private IWhitespacePolicyFactory wsFactory = new IWhitespacePolicyFactory() {
+        public IWhitespacePolicy getPolicy(String publicId) {
+            
+            if (doctype == null) {
+                DocumentTypeSelectionDialog dlg = DocumentTypeSelectionDialog.create(getSite().getShell(), publicId);
+                dlg.open();
+                doctype = dlg.getDoctype();
+                updateDoctypeDecl = dlg.alwaysUseThisDoctype();
+                
+                if (doctype == null) {
+                    throw new NoRegisteredDoctypeException(null); 
+                }
+            }
+            
+            style = VexEditor.findStyleForDoctype(doctype.getPublicId());
+            if (style == null) {
+                throw new NoStyleForDoctypeException(doctype);
+            }
+            
+            return new CssWhitespacePolicy(style.getStyleSheet());
+        }
+        
+    };
+
+    private class ResourceChangeListener implements IResourceChangeListener {
+        
+        public void resourceChanged(IResourceChangeEvent event) {
+            
+            if (this.saving) {
+                return;
+            }
+            
+            IPath path = ((IFileEditorInput) getEditorInput()).getFile().getFullPath();
+            final IResourceDelta delta = event.getDelta().findMember(path);
+            if (delta != null) {
+                Display.getDefault().asyncExec(new Runnable() {
+                    public void run() {
+                        handleResourceChanged(delta);
+                    }
+                });
+            }
+        }
+        
+        public void setSaving(boolean saving) {
+            this.saving = saving;
+        }
+        
+        // Set to true so we can ignore change events while we're saving.
+        private boolean saving;
+    };
+    
+    private ResourceChangeListener resourceChangeListener = new ResourceChangeListener();
+    
+    //
+    // wsFactory communicates failures back to init() through the XML parser
+    // by throwing one of these exceptions
+    //
+    
+    /**
+     * Indicates that no document type is registered for the public ID
+     * in the document, or that the document does not have a PUBLIC 
+     * DOCTYPE decl, in which case publicId is null.
+     */
+    private class NoRegisteredDoctypeException extends RuntimeException {
+        public NoRegisteredDoctypeException(String publicId) {
+            this.publicId = publicId;
+        }
+        
+        public String getPublicId() {
+            return this.publicId;
+        }
+        
+        private String publicId;
+    }
+
+    /**
+     * Indicates that the document was matched to a registered doctype,
+     * but that the given doctype does not have a matching style.
+     */
+    private class NoStyleForDoctypeException extends RuntimeException {
+        
+        public NoStyleForDoctypeException(DocumentType doctype) {
+            this.doctype = doctype;
+        }
+        
+        public DocumentType getDoctype() {
+            return this.doctype;
+        }
+        
+        private DocumentType doctype;
+    }
+    
+    
+    private String getLocation() {
+        List path = new ArrayList();
+        Element element = this.vexWidget.getCurrentElement();
+        while (element != null) {
+            path.add(element.getName());
+            element = element.getParent();
+        }
+        Collections.reverse(path);
+        StringBuffer sb = new StringBuffer(path.size() * 15);
+        for (int i = 0; i < path.size(); i++) {
+            sb.append("/"); //$NON-NLS-1$
+            sb.append(path.get(i));
+        }
+        return sb.toString();
+    }
+
+    
+    public Object getAdapter(Class adapter) {
+        
+        if (adapter == IContentOutlinePage.class) {
+            
+            return new DocumentOutlinePage();
+            
+        } else if (adapter == IPropertySheetPage.class) {
+            PropertySheetPage page = new PropertySheetPage();
+            page.setPropertySourceProvider(new IPropertySourceProvider() {
+                public IPropertySource getPropertySource(Object object) {
+                    if (object instanceof Element) {
+                        IStructuredSelection sel = (IStructuredSelection) vexWidget.getSelection();
+                        boolean multi = (sel != null && sel.size() > 1);
+                        Validator validator = vexWidget.getDocument().getValidator();
+                        return new ElementPropertySource((Element) object, validator, multi);
+                    } else {
+                        return null;
+                    }
+                }
+            });
+            return page;
+        } else {
+            return super.getAdapter(adapter);
+        }
+    }
+
+
+
+}
+
+
+
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/VexEditorEvent.java b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/VexEditorEvent.java
new file mode 100644
index 0000000..f40cfd3
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/VexEditorEvent.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.ui.internal.editor;
+
+import java.util.EventObject;
+
+/**
+ * Event object published through the IVexEditorListener interface.
+ */
+public class VexEditorEvent extends EventObject {
+
+    /**
+     * Class constructor.
+     * @param source VexEditor that originated this event.
+     */
+    public VexEditorEvent(VexEditor source) {
+        super(source);
+    }
+    
+    /**
+     * Returns the VexEditor that originated this event.
+     */
+    public VexEditor getVexEditor() {
+        return (VexEditor) this.getSource();
+    }
+}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/messages.properties b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/messages.properties
new file mode 100644
index 0000000..7e03ee1
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/messages.properties
@@ -0,0 +1,103 @@
+#
+# Language-specific strings in the vex-editor plugin
+#
+
+
+#
+# Actions
+#
+# NOTE: THESE MUST BE KEPT IN SYNC WITH THE SAME BLOCK IN plugin.properties
+#
+# Note: when there are multiple labels for an action, the one named
+# Xxx.label is the command listed in the key bindings preference page.
+#
+ChangeElementAction.label=Change Element
+ChangeElementAction.dynamic.label=&Change <{0}> to...
+ChangeElementAction.menu.label=&Change Element...
+DeleteColumnAction.label=Delete Column
+DebugView.noActiveEditor=No Vex editor is currently active.
+DeleteRowAction.label=Delete Row
+DuplicateSelectionAction.label=&Duplicate Selection
+InsertColumnAfterAction.label=Insert Column After
+InsertColumnBeforeAction.label=Insert Column Before
+InsertElementAction.label=Insert Element
+InsertAssistant.title=Insert Element
+InsertElementAction.contextmenu.label=Insert Element...
+InsertElementAction.mainmenu.label=&Element...
+InsertRowAboveAction.label=Insert Row Above
+InsertRowBelowAction.label=Insert Row Below
+MoveColumnLeftAction.label=Move Column Left
+MoveColumnRightAction.label=Move Column Right
+MoveRowDownAction.label=Move Row Down
+MoveRowUpAction.label=Move Row Up
+NextTableCellAction.label=Next Table Cell
+PasteTextAction.label=Paste Text
+PreviousTableCellAction.label=Previous Table Cell
+RemoveElementAction.label=Remove Element
+RemoveElementAction.dynamic.label=Remove <{0}>
+RestoreLastSelectionAction.label=Restore Last Selection
+SplitAction.label=Split Block Element
+SplitItemAction.label=Split Item
+
+#
+# End of Actions
+#
+
+DebugView.noActiveEditor=No Vex editor is currently active.
+
+DocumentFileCreationPage.title=File
+DocumentFileCreationPage.desc=Enter a file name for the new document.
+
+DocumentOutlinePage.loadingError=Exception while loading {0} from bundle {1}: {2}
+DocumentOutlinePage.loading=Loading...
+DocumentOutlinePage.reloading=Reloading...
+
+Messages.cantFindResource=Can''t find resource string {0}
+
+DocumentTypeSelectionDialog.ok=OK
+DocumentTypeSelectionDialog.cancel=Cancel
+DocumentTypeSelectionDialog.unknownDoctype=Vex does not recognize the document type ''{0}''. To open the document with a different document type, select one of the registered document types below.
+DocumentTypeSelectionDialog.selectDoctype=Select Document Type
+DocumentTypeSelectionDialog.noDoctype=Vex cannot determine a document type from the selected document. Please select a document type from the following list.
+DocumentTypeSelectionDialog.alwaysUse=Always use this document type for the selected document.
+
+DocumentTypeSelectionPage.title=Document Type
+DocumentTypeSelectionPage.desc=Select a document type and root element for the new document.
+DocumentTypeSelectionPage.doctype=Document Type:
+DocumentTypeSelectionPage.pageName=Select Type
+DocumentTypeSelectionPage.rootElement=Root Element:
+
+ElementPropertySource.multiple=(multiple)
+
+NewDocumentWizard.title=New Vex Document
+NewDocumentWizard.noStyles.title=Error
+NewDocumentWizard.noStyles.message=No styles for the given document type.
+NewDocumentWizard.errorLoading.message=Error loading {0}: {1}
+NewDocumentWizard.errorLoading.title=Error
+
+VexActionBarContributor.styleMenu.name=Style
+VexActionBarContributor.noValidItems=No valid items
+VexActionBarContributor.rowMenu.name=Row
+VexActionBarContributor.columnMenu.name=Column
+
+VexActionDelegate.errorLoadingAction=Error loading action {0}: {1}
+
+VexEditor.errorSaving.title=Error
+VexEditor.errorSaving.message=Error saving {0}: {1}
+VexEditor.unknownInputClass=Unable to open inputs of type {0}. Please log a bug report at http://krasnay.ca/bugzilla.
+VexEditor.noDoctype=Cannot determine a document type from the selected document.
+VexEditor.unknownDoctype=No registered document types for public ID {0}.
+VexEditor.noStyles=No registered styles for documents with public ID {0}.
+VexEditor.parseError=Error at line {0} of file {1}: {2}
+VexEditor.unexpectedError=Unexpected error opening {0}. Please log a bug report at http://krasnay.ca/bugzilla.
+VexEditor.loading=Loading...
+VexEditor.errorSavingStylePreference=Error saving style preference
+VexEditor.docChanged.title=Document Changed
+VexEditor.docChanged.message=The file {0} has been changed from outside Vex. Would you like to discard your changes or overwrite the external changes?
+VexEditor.docChanged.discard=Discard My Changes
+VexEditor.docChanged.overwrite=Overwrite Other Changes
+VexEditor.docDeleted.title=Document Deleted
+VexEditor.docDeleted.message=The file {0} has been deleted from the workspace. Would you like to discard your changes or save to a new document?
+VexEditor.docDeleted.discard=Discard
+VexEditor.docDeleted.save=Save
+VexEditor.noUrlForDoctype=No URL defined for doctype {0}
diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/messages_fr.properties b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/messages_fr.properties
new file mode 100644
index 0000000..872a25e
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/src/org/eclipse/wst/xml/vex/ui/internal/editor/messages_fr.properties
@@ -0,0 +1,102 @@
+#

+# Language-specific strings in the vex-editor plugin

+#

+

+

+#

+# Actions

+#

+# NOTE: THESE MUST BE KEPT IN SYNC WITH THE SAME BLOCK IN plugin.properties

+#

+# Note: when there are multiple labels for an action, the one named

+# Xxx.label is the command listed in the key bindings preference page.

+#

+ChangeElementAction.label=Changer l'élément

+ChangeElementAction.dynamic.label=&Changer <{0}> en...

+ChangeElementAction.menu.label=&Changer l'élément...

+DeleteColumnAction.label=Detruire la colonne

+DebugView.noActiveEditor=Pas d'éditeur Vex actif

+DeleteRowAction.label=Détruire la ligne

+DuplicateSelectionAction.label=&Dupliquer la sélection

+InsertColumnAfterAction.label=Insérer une colonne après

+InsertColumnBeforeAction.label=Insérer une colonne avant

+InsertElementAction.label=Insérer un élément

+InsertAssistant.title=Insérer un élément

+InsertElementAction.contextmenu.label=Insérer l'élément...

+InsertElementAction.mainmenu.label=&Elément...

+InsertRowAboveAction.label=Insérer une ligne au dessus

+InsertRowBelowAction.label=Insérer une ligne en dessous

+MoveColumnLeftAction.label=Déplacer la colonne à gauche

+MoveColumnRightAction.label=Déplacer la colonne à droite

+MoveRowDownAction.label=Déplacer la ligne en dessous

+MoveRowUpAction.label=Déplacer la ligne au dessus

+NextTableCellAction.label=Prochaine cellule du tableau

+PasteTextAction.label=Copier le texte

+PreviousTableCellAction.label=Cellule précédente du tableau

+RemoveElementAction.label=Supprimer l'élément

+RemoveElementAction.dynamic.label=Supprimer <{0}>

+RestoreLastSelectionAction.label=Restaurer la sélection précédente

+SplitAction.label=Couper le bloc

+SplitItemAction.label=Couper l'item

+

+#

+# End of Actions

+#

+

+DebugView.noActiveEditor=Pas d'éditeur Vex actif.

+

+DocumentFileCreationPage.title=Fichier

+DocumentFileCreationPage.desc=Entrer un nom de fichier pour le nouveau document.

+

+DocumentOutlinePage.loadingError=Exception au chargement de {0} du bundle {1}: {2}

+DocumentOutlinePage.loading=Chargement...

+DocumentOutlinePage.reloading=Rechargement...

+

+Messages.cantFindResource=Ne peut pas trouver la chaine de la ressource {0}

+

+DocumentTypeSelectionDialog.ok=Accepter

+DocumentTypeSelectionDialog.cancel=Abandonner

+DocumentTypeSelectionDialog.unknownDoctype=Vex ne connait pas le type de document '{0}'. Pour ouvrir le document avec un autre type de document, sélectionner un des types enregistrés ci-dessous.

+DocumentTypeSelectionDialog.selectDoctype=Sélectionner un type de document

+DocumentTypeSelectionDialog.noDoctype=Vex ne peut pas déterminer le type de document pour le document sélectionné. Sélectionner un des types de document dans la liste suivante.

+DocumentTypeSelectionDialog.alwaysUse=Toujours utiliser ce type de document pour le document sélectionné.

+Always use this document type for the selected document.

+

+DocumentTypeSelectionPage.title=Type de document

+DocumentTypeSelectionPage.desc=Sélectionner le type de document et la racine pour le nouveau document.

+DocumentTypeSelectionPage.doctype=Type de document:

+DocumentTypeSelectionPage.pageName=Sélectionner le type

+DocumentTypeSelectionPage.rootElement=Elément racine:

+

+ElementPropertySource.multiple=(multiple)

+

+NewDocumentWizard.title=Nouveau Document Vex

+NewDocumentWizard.noStyles.title=Erreur

+NewDocumentWizard.noStyles.message=Pas de styles pour le type de document donné

+NewDocumentWizard.errorLoading.message=Erreur de chargement {0}: {1}

+NewDocumentWizard.errorLoading.title=Erreur

+

+VexActionBarContributor.styleMenu.name=Style

+VexActionBarContributor.noValidItems=Pas d'article valide

+

+VexActionDelegate.errorLoadingAction=Erreur sur l'action de chargement {0}: {1}

+

+VexEditor.errorSaving.title=Erreur

+VexEditor.errorSaving.message=Erreur de sauvegarde {0}: {1}

+VexEditor.unknownInputClass=Impssible d'ouvrir les entrées de type {0}. Envoyer un rapport de bogue à http://krasnay.ca/bugzilla SVP.

+VexEditor.noDoctype=Impossible de déterminer le type de document pour le document sélectionné.

+VexEditor.unknownDoctype=Pas de type de document enregistré pour la clé publique : {0}.

+VexEditor.noStyles=Pas de style enregistré pour la clé publique : {0}.

+VexEditor.parseError=Erreur à la ligne {0} du fichier {1}: {2}

+VexEditor.unexpectedError=Erreur inconnue à l'ouverture de {0}.  Envoyer un rapport de bogue à http://krasnay.ca/bugzilla SVP.

+VexEditor.loading=En chargement...

+VexEditor.errorSavingStylePreference=Erreur à la sauvegarde des préférence de style

+VexEditor.docChanged.title=Document modifié

+VexEditor.docChanged.message=Le fichier {0} a été modifié en dehors de Vex. Voulez vous perdre vos modifications ou écraser les modifications extérieures?

+VexEditor.docChanged.discard=Perdre mes modifications

+VexEditor.docChanged.overwrite=Ecraser les autres

+VexEditor.docDeleted.title=Document supprimé

+VexEditor.docDeleted.message=Le fichier {0} a été supprimé de l'espace de travail. Voulez vous predre vos modifications ou enregistrer un nouveau document? 

+VexEditor.docDeleted.discard=Perdre

+VexEditor.docDeleted.save=Enregistrer

+VexEditor.noUrlForDoctype=Pas d'URL défini pour le doctype {0}

diff --git a/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/vex16.gif b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/vex16.gif
new file mode 100644
index 0000000..a32605a
--- /dev/null
+++ b/sourceediting/plugins/org.eclipse.wst.xml.vex.ui/vex16.gif
Binary files differ
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/.classpath b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/.classpath
new file mode 100644
index 0000000..64c5e31
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/>
+	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/.project b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/.project
new file mode 100644
index 0000000..f8d75db
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/.project
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>org.eclipse.wst.xml.vex.core.tests</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>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.pde.PluginNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/.settings/org.eclipse.jdt.core.prefs b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..68dd593
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,7 @@
+#Wed Oct 01 02:29:28 GMT 2008
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5
+org.eclipse.jdt.core.compiler.compliance=1.5
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.5
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/META-INF/MANIFEST.MF b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..e13cc59
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/META-INF/MANIFEST.MF
@@ -0,0 +1,13 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: VEX Core Tests
+Bundle-SymbolicName: org.eclipse.wst.xml.vex.core.tests
+Bundle-Version: 0.5.0.qualifier
+Bundle-Activator: org.eclipse.wst.xml.vex.core.tests.VEXCoreTestPlugin
+Bundle-Vendor: Eclipse.org
+Require-Bundle: org.eclipse.ui,
+ org.eclipse.core.runtime,
+ org.eclipse.wst.xml.vex.core;bundle-version="0.5.0",
+ org.junit;bundle-version="3.8.1"
+Bundle-RequiredExecutionEnvironment: J2SE-1.5
+Bundle-ActivationPolicy: lazy
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/build.properties b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/build.properties
new file mode 100644
index 0000000..34d2e4d
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/build.properties
@@ -0,0 +1,4 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/CssTest.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/CssTest.java
new file mode 100644
index 0000000..2d6ce17
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/CssTest.java
@@ -0,0 +1,773 @@
+/*******************************************************************************
+ * 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.net.URL;
+
+import org.eclipse.wst.xml.vex.core.internal.core.Color;
+import org.eclipse.wst.xml.vex.core.internal.core.DisplayDevice;
+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.StyleSheetReader;
+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.RootElement;
+
+import junit.framework.TestCase;
+
+/**
+ * Test the <code>net.sf.vex.css</code> package.
+ */
+public class CssTest extends TestCase {
+    
+    protected void setUp() throws Exception {
+        super.setUp();
+        DisplayDevice.setCurrent(new TestDisplayDevice(90, 90));
+    }
+    
+    /*
+    public void testAll() throws Exception {
+	Element aElement = new Element("A");
+	Element bElement = new Element("B");
+	Element cElement = new Element("C");
+	Document doc = new Document(aElement);
+	doc.insertElement(1, bElement);
+	doc.insertElement(2, cElement);
+
+	StyleSheet ss = parseStyleSheetResource("test1.css");
+	Styles styles = ss.get(aElement);
+
+	assertProperty(styles, "name", "A", LexicalUnit.SAC_IDENT);
+	
+    }
+    */
+
+
+    public void testBorderColor() throws Exception {
+	StyleSheet ss = parseStyleSheetResource("test2.css");
+	Styles styles;
+	Color red = new Color(255, 0, 0);
+	Color green = new Color(0, 128, 0);
+	Color blue = new Color(0, 0, 255);
+	Color white = new Color(255, 255, 255);
+
+	styles = ss.getStyles(new Element("borderColor1"));
+	assertEquals(red, styles.getBorderTopColor());
+	assertEquals(red, styles.getBorderLeftColor());
+	assertEquals(red, styles.getBorderRightColor());
+	assertEquals(red, styles.getBorderBottomColor());
+
+	styles = ss.getStyles(new Element("borderColor2"));
+	assertEquals(red, styles.getBorderTopColor());
+	assertEquals(green, styles.getBorderLeftColor());
+	assertEquals(green, styles.getBorderRightColor());
+	assertEquals(red, styles.getBorderBottomColor());
+
+	styles = ss.getStyles(new Element("borderColor3"));
+	assertEquals(red, styles.getBorderTopColor());
+	assertEquals(green, styles.getBorderLeftColor());
+	assertEquals(green, styles.getBorderRightColor());
+	assertEquals(blue, styles.getBorderBottomColor());
+
+	styles = ss.getStyles(new Element("borderColor4"));
+	assertEquals(red, styles.getBorderTopColor());
+	assertEquals(green, styles.getBorderRightColor());
+	assertEquals(blue, styles.getBorderBottomColor());
+	assertEquals(white, styles.getBorderLeftColor());
+
+    }
+    
+    public void testBorderStyle() throws Exception {
+	StyleSheet ss = parseStyleSheetResource("test2.css");
+	Styles styles;
+
+	styles = ss.getStyles(new Element("borderStyle1"));
+	assertEquals(CSS.SOLID, styles.getBorderTopStyle());
+	assertEquals(CSS.SOLID, styles.getBorderLeftStyle());
+	assertEquals(CSS.SOLID, styles.getBorderRightStyle());
+	assertEquals(CSS.SOLID, styles.getBorderBottomStyle());
+
+	styles = ss.getStyles(new Element("borderStyle2"));
+	assertEquals(CSS.SOLID, styles.getBorderTopStyle());
+	assertEquals(CSS.DOTTED, styles.getBorderLeftStyle());
+	assertEquals(CSS.DOTTED, styles.getBorderRightStyle());
+	assertEquals(CSS.SOLID, styles.getBorderBottomStyle());
+
+	styles = ss.getStyles(new Element("borderStyle3"));
+	assertEquals(CSS.SOLID, styles.getBorderTopStyle());
+	assertEquals(CSS.DOTTED, styles.getBorderLeftStyle());
+	assertEquals(CSS.DOTTED, styles.getBorderRightStyle());
+	assertEquals(CSS.DASHED, styles.getBorderBottomStyle());
+
+	styles = ss.getStyles(new Element("borderStyle4"));
+	assertEquals(CSS.SOLID, styles.getBorderTopStyle());
+	assertEquals(CSS.DOTTED, styles.getBorderRightStyle());
+	assertEquals(CSS.DASHED, styles.getBorderBottomStyle());
+	assertEquals(CSS.OUTSET, styles.getBorderLeftStyle());
+
+    }
+    
+    public void testBorderWidth() throws Exception {
+	StyleSheet ss = parseStyleSheetResource("test2.css");
+	Styles styles;
+
+	styles = ss.getStyles(new Element("borderWidth1"));
+	assertEquals(1, styles.getBorderTopWidth());
+	assertEquals(1, styles.getBorderLeftWidth());
+	assertEquals(1, styles.getBorderRightWidth());
+	assertEquals(1, styles.getBorderBottomWidth());
+
+	styles = ss.getStyles(new Element("borderWidth2"));
+	assertEquals(1, styles.getBorderTopWidth());
+	assertEquals(2, styles.getBorderLeftWidth());
+	assertEquals(2, styles.getBorderRightWidth());
+	assertEquals(1, styles.getBorderBottomWidth());
+
+	styles = ss.getStyles(new Element("borderWidth3"));
+	assertEquals(1, styles.getBorderTopWidth());
+	assertEquals(2, styles.getBorderLeftWidth());
+	assertEquals(2, styles.getBorderRightWidth());
+	assertEquals(3, styles.getBorderBottomWidth());
+
+	styles = ss.getStyles(new Element("borderWidth4"));
+	assertEquals(1, styles.getBorderTopWidth());
+	assertEquals(2, styles.getBorderRightWidth());
+	assertEquals(3, styles.getBorderBottomWidth());
+	assertEquals(4, styles.getBorderLeftWidth());
+
+    }
+    
+    public void testDefaults() throws Exception {
+	StyleSheet ss = parseStyleSheetResource("test2.css");
+	Styles styles = ss.getStyles(new Element("defaults"));
+
+	assertEquals(15.0f, styles.getFontSize(), 0.1);
+
+	assertNull(styles.getBackgroundColor());
+
+	assertEquals(new Color(0, 0, 0), styles.getBorderBottomColor());
+	assertEquals(CSS.NONE, styles.getBorderBottomStyle());
+	assertEquals(0, styles.getBorderBottomWidth());
+
+	assertEquals(new Color(0, 0, 0), styles.getBorderLeftColor());
+	assertEquals(CSS.NONE, styles.getBorderLeftStyle());
+	assertEquals(0, styles.getBorderLeftWidth());
+
+	assertEquals(new Color(0, 0, 0), styles.getBorderRightColor());
+	assertEquals(CSS.NONE, styles.getBorderRightStyle());
+	assertEquals(0, styles.getBorderRightWidth());
+
+	assertEquals(new Color(0, 0, 0), styles.getBorderTopColor());
+	assertEquals(CSS.NONE, styles.getBorderTopStyle());
+	assertEquals(0, styles.getBorderTopWidth());
+
+	assertEquals(new Color(0, 0, 0), styles.getColor());
+	assertEquals(CSS.INLINE, styles.getDisplay());
+
+	assertEquals(0, styles.getMarginBottom().get(10));
+	assertEquals(0, styles.getMarginLeft().get(10));
+	assertEquals(0, styles.getMarginRight().get(10));
+	assertEquals(0, styles.getMarginTop().get(10));
+
+	assertEquals(0, styles.getPaddingBottom().get(10));
+	assertEquals(0, styles.getPaddingLeft().get(10));
+	assertEquals(0, styles.getPaddingRight().get(10));
+	assertEquals(0, styles.getPaddingTop().get(10));
+    }
+
+    /**
+     * Check the correct properties are inherited by default.
+     */
+    public void testDefaultInheritance() throws Exception {
+	RootElement simple = new RootElement("simple");
+	Element defaults = new Element("defaults");
+	Document doc = new Document(simple);
+	doc.insertElement(1, defaults);
+
+	StyleSheet ss = parseStyleSheetResource("test2.css");
+	Styles styles = ss.getStyles(defaults);
+
+	assertEquals(12.5f, styles.getFontSize(), 0.1);
+
+	assertNull(styles.getBackgroundColor());
+
+	assertEquals(new Color(0, 128, 0), styles.getBorderBottomColor());
+	assertEquals(CSS.NONE, styles.getBorderBottomStyle());
+	assertEquals(0, styles.getBorderBottomWidth());
+
+	assertEquals(new Color(0, 128, 0), styles.getBorderLeftColor());
+	assertEquals(CSS.NONE, styles.getBorderLeftStyle());
+	assertEquals(0, styles.getBorderLeftWidth());
+
+	assertEquals(new Color(0, 128, 0), styles.getBorderRightColor());
+	assertEquals(CSS.NONE, styles.getBorderRightStyle());
+	assertEquals(0, styles.getBorderRightWidth());
+
+	assertEquals(new Color(0, 128, 0), styles.getBorderTopColor());
+	assertEquals(CSS.NONE, styles.getBorderTopStyle());
+	assertEquals(0, styles.getBorderTopWidth());
+
+	assertEquals(new Color(0, 128, 0), styles.getColor());
+	assertEquals(CSS.INLINE, styles.getDisplay());
+
+	assertEquals(0, styles.getMarginBottom().get(10));
+	assertEquals(0, styles.getMarginLeft().get(10));
+	assertEquals(0, styles.getMarginRight().get(10));
+	assertEquals(0, styles.getMarginTop().get(10));
+
+	assertEquals(0, styles.getPaddingBottom().get(10));
+	assertEquals(0, styles.getPaddingLeft().get(10));
+	assertEquals(0, styles.getPaddingRight().get(10));
+	assertEquals(0, styles.getPaddingTop().get(10));
+    }
+
+    public void testExpandBorder() throws Exception {
+	StyleSheet ss = parseStyleSheetResource("test2.css");
+	Styles styles;
+
+	styles = ss.getStyles(new Element("expandBorder"));
+	assertEquals(2, styles.getBorderBottomWidth());
+	assertEquals(CSS.SOLID, styles.getBorderBottomStyle());
+	assertEquals(new Color(255, 0, 0), styles.getBorderBottomColor());
+	assertEquals(2, styles.getBorderLeftWidth());
+	assertEquals(CSS.SOLID, styles.getBorderLeftStyle());
+	assertEquals(new Color(255, 0, 0), styles.getBorderLeftColor());
+	assertEquals(2, styles.getBorderRightWidth());
+	assertEquals(CSS.SOLID, styles.getBorderRightStyle());
+	assertEquals(new Color(255, 0, 0), styles.getBorderRightColor());
+	assertEquals(2, styles.getBorderTopWidth());
+	assertEquals(CSS.SOLID, styles.getBorderTopStyle());
+	assertEquals(new Color(255, 0, 0), styles.getBorderTopColor());
+	
+	styles = ss.getStyles(new Element("expandBorderBottom"));
+	assertEquals(2, styles.getBorderBottomWidth());
+	assertEquals(CSS.SOLID, styles.getBorderBottomStyle());
+	assertEquals(new Color(255, 0, 0), styles.getBorderBottomColor());
+	assertEquals(0, styles.getBorderLeftWidth());
+	assertEquals(CSS.NONE, styles.getBorderLeftStyle());
+	assertEquals(new Color(0, 0, 0), styles.getBorderLeftColor());
+	assertEquals(0, styles.getBorderRightWidth());
+	assertEquals(CSS.NONE, styles.getBorderRightStyle());
+	assertEquals(new Color(0, 0, 0), styles.getBorderRightColor());
+	assertEquals(0, styles.getBorderTopWidth());
+	assertEquals(CSS.NONE, styles.getBorderTopStyle());
+	assertEquals(new Color(0, 0, 0), styles.getBorderTopColor());
+	
+	styles = ss.getStyles(new Element("expandBorderLeft"));
+	assertEquals(0, styles.getBorderBottomWidth());
+	assertEquals(CSS.NONE, styles.getBorderBottomStyle());
+	assertEquals(new Color(0, 0, 0), styles.getBorderBottomColor());
+	assertEquals(2, styles.getBorderLeftWidth());
+	assertEquals(CSS.SOLID, styles.getBorderLeftStyle());
+	assertEquals(new Color(255, 0, 0), styles.getBorderLeftColor());
+	assertEquals(0, styles.getBorderRightWidth());
+	assertEquals(CSS.NONE, styles.getBorderRightStyle());
+	assertEquals(new Color(0, 0, 0), styles.getBorderRightColor());
+	assertEquals(0, styles.getBorderTopWidth());
+	assertEquals(CSS.NONE, styles.getBorderTopStyle());
+	assertEquals(new Color(0, 0, 0), styles.getBorderTopColor());
+	
+	styles = ss.getStyles(new Element("expandBorderRight"));
+	assertEquals(0, styles.getBorderBottomWidth());
+	assertEquals(CSS.NONE, styles.getBorderBottomStyle());
+	assertEquals(new Color(0, 0, 0), styles.getBorderBottomColor());
+	assertEquals(0, styles.getBorderLeftWidth());
+	assertEquals(CSS.NONE, styles.getBorderLeftStyle());
+	assertEquals(new Color(0, 0, 0), styles.getBorderLeftColor());
+	assertEquals(2, styles.getBorderRightWidth());
+	assertEquals(CSS.SOLID, styles.getBorderRightStyle());
+	assertEquals(new Color(255, 0, 0), styles.getBorderRightColor());
+	assertEquals(0, styles.getBorderTopWidth());
+	assertEquals(CSS.NONE, styles.getBorderTopStyle());
+	assertEquals(new Color(0, 0, 0), styles.getBorderTopColor());
+	
+	styles = ss.getStyles(new Element("expandBorderTop"));
+	assertEquals(0, styles.getBorderBottomWidth());
+	assertEquals(CSS.NONE, styles.getBorderBottomStyle());
+	assertEquals(new Color(0, 0, 0), styles.getBorderBottomColor());
+	assertEquals(0, styles.getBorderLeftWidth());
+	assertEquals(CSS.NONE, styles.getBorderLeftStyle());
+	assertEquals(new Color(0, 0, 0), styles.getBorderLeftColor());
+	assertEquals(0, styles.getBorderRightWidth());
+	assertEquals(CSS.NONE, styles.getBorderRightStyle());
+	assertEquals(new Color(0, 0, 0), styles.getBorderRightColor());
+	assertEquals(2, styles.getBorderTopWidth());
+	assertEquals(CSS.SOLID, styles.getBorderTopStyle());
+	assertEquals(new Color(255, 0, 0), styles.getBorderTopColor());
+
+	styles = ss.getStyles(new Element("expandBorder1"));
+	assertEquals(2, styles.getBorderBottomWidth());
+	assertEquals(CSS.SOLID, styles.getBorderBottomStyle());
+	assertEquals(new Color(0, 0, 0), styles.getBorderBottomColor());
+	
+	styles = ss.getStyles(new Element("expandBorder2"));
+	assertEquals(0, styles.getBorderBottomWidth());
+	assertEquals(CSS.NONE, styles.getBorderBottomStyle());
+	assertEquals(new Color(255, 0, 0), styles.getBorderBottomColor());
+	
+	styles = ss.getStyles(new Element("expandBorder3"));
+	assertEquals(0, styles.getBorderBottomWidth());
+	assertEquals(CSS.NONE, styles.getBorderBottomStyle());
+	assertEquals(new Color(0, 0, 0), styles.getBorderBottomColor());
+	
+	styles = ss.getStyles(new Element("expandBorder4"));
+	assertEquals(3, styles.getBorderBottomWidth());
+	assertEquals(CSS.SOLID, styles.getBorderBottomStyle());
+	assertEquals(new Color(255, 0, 0), styles.getBorderBottomColor());
+	
+	styles = ss.getStyles(new Element("expandBorder5"));
+	assertEquals(3, styles.getBorderBottomWidth());
+	assertEquals(CSS.SOLID, styles.getBorderBottomStyle());
+	assertEquals(new Color(0, 0, 0), styles.getBorderBottomColor());
+	
+	styles = ss.getStyles(new Element("expandBorder6"));
+	assertEquals(0, styles.getBorderBottomWidth());
+	assertEquals(CSS.NONE, styles.getBorderBottomStyle());
+	assertEquals(new Color(255, 0, 0), styles.getBorderBottomColor());
+	
+    }
+    
+    public void testExpandMargins() throws Exception {
+        StyleSheet ss = parseStyleSheetResource("test2.css");
+        
+        Styles styles = ss.getStyles(new Element("margin1"));
+        assertEquals(10, styles.getMarginTop().get(67));
+        assertEquals(10, styles.getMarginLeft().get(67));
+        assertEquals(10, styles.getMarginRight().get(67));
+        assertEquals(10, styles.getMarginBottom().get(67));
+        
+        styles = ss.getStyles(new Element("margin2"));
+        assertEquals(10, styles.getMarginTop().get(67));
+        assertEquals(20, styles.getMarginLeft().get(67));
+        assertEquals(20, styles.getMarginRight().get(67));
+        assertEquals(10, styles.getMarginBottom().get(67));
+        
+        styles = ss.getStyles(new Element("margin3"));
+        assertEquals(10, styles.getMarginTop().get(67));
+        assertEquals(20, styles.getMarginLeft().get(67));
+        assertEquals(20, styles.getMarginRight().get(67));
+        assertEquals(30, styles.getMarginBottom().get(67));
+        
+        styles = ss.getStyles(new Element("margin4"));
+        assertEquals(10, styles.getMarginTop().get(67));
+        assertEquals(20, styles.getMarginRight().get(67));
+        assertEquals(30, styles.getMarginBottom().get(67));
+        assertEquals(40, styles.getMarginLeft().get(67));
+    }
+
+    public void testExtras() throws Exception {
+	StyleSheet ss = parseStyleSheetResource("test2.css");
+	Styles styles = ss.getStyles(new Element("extras"));
+
+	assertEquals(new Color(0, 255, 0), styles.getBackgroundColor());
+
+	assertEquals(new Color(128, 0, 0), styles.getBorderBottomColor());
+	assertEquals(CSS.SOLID, styles.getBorderBottomStyle());
+
+	assertEquals(new Color(0, 0, 128), styles.getBorderLeftColor());
+	assertEquals(CSS.DASHED, styles.getBorderLeftStyle());
+
+	assertEquals(new Color(128, 128, 0), styles.getBorderRightColor());
+	assertEquals(CSS.DOTTED, styles.getBorderRightStyle());
+
+	assertEquals(new Color(128, 0, 128), styles.getBorderTopColor());
+	assertEquals(CSS.DOUBLE, styles.getBorderTopStyle());
+
+	assertEquals(new Color(255, 0, 0), styles.getColor());
+	assertEquals(CSS.INLINE, styles.getDisplay());
+    }
+
+    public void testExtras2() throws Exception {
+	StyleSheet ss = parseStyleSheetResource("test2.css");
+	Styles styles = ss.getStyles(new Element("extras2"));
+
+	assertEquals(new Color(192, 192, 192), styles.getBackgroundColor());
+
+	assertEquals(new Color(0, 128, 128), styles.getBorderBottomColor());
+	assertEquals(CSS.NONE, styles.getBorderBottomStyle());
+	assertEquals(0, styles.getBorderBottomWidth());
+
+	assertEquals(new Color(255, 255, 255), styles.getBorderLeftColor());
+	assertEquals(CSS.GROOVE, styles.getBorderLeftStyle());
+
+	assertEquals(new Color(255, 255, 0), styles.getBorderRightColor());
+	assertEquals(CSS.RIDGE, styles.getBorderRightStyle());
+
+	assertEquals(CSS.INSET, styles.getBorderTopStyle());
+    }
+
+    /**
+     * Test the symbolic font sizes.
+     */
+    public void testFontSize() throws Exception {
+
+	StyleSheet ss = parseStyleSheetResource("test2.css");
+	Styles styles;
+
+	styles = ss.getStyles(new Element("medium"));
+	assertEquals(15.0f, styles.getFontSize(), 0.1);
+
+	styles = ss.getStyles(new Element("small"));
+	assertEquals(12.5f, styles.getFontSize(), 0.1);
+
+	styles = ss.getStyles(new Element("xsmall"));
+	assertEquals(10.4f, styles.getFontSize(), 0.1);
+
+	styles = ss.getStyles(new Element("xxsmall"));
+	assertEquals(8.7f, styles.getFontSize(), 0.1);
+
+	styles = ss.getStyles(new Element("large"));
+	assertEquals(18.0f, styles.getFontSize(), 0.1);
+
+	styles = ss.getStyles(new Element("xlarge"));
+	assertEquals(21.6f, styles.getFontSize(), 0.1);
+
+	styles = ss.getStyles(new Element("xxlarge"));
+	assertEquals(25.9, styles.getFontSize(), 0.1);
+
+	styles = ss.getStyles(new Element("smaller"));
+	assertEquals(12.5f, styles.getFontSize(), 0.1);
+
+	styles = ss.getStyles(new Element("font100pct"));
+	assertEquals(15.0f, styles.getFontSize(), 0.1);
+	
+	styles = ss.getStyles(new Element("font80pct"));
+	assertEquals(12.0f, styles.getFontSize(), 0.1);
+	
+	styles = ss.getStyles(new Element("font120pct"));
+	assertEquals(18.0f, styles.getFontSize(), 0.1);
+
+    }
+
+    public void testForcedInheritance() throws Exception {
+	RootElement simple = new RootElement("simple");
+	Element inherit = new Element("inherit");
+	Document doc = new Document(simple);
+	doc.insertElement(1, inherit);
+
+	StyleSheet ss = parseStyleSheetResource("test2.css");
+	Styles styles = ss.getStyles(inherit);
+
+	assertEquals(12.5f, styles.getFontSize(), 0.1);
+
+	assertEquals(new Color(0, 255, 255), styles.getBackgroundColor());
+
+	assertEquals(new Color(0, 0, 0), styles.getBorderBottomColor());
+	assertEquals(CSS.SOLID, styles.getBorderBottomStyle());
+	assertEquals(1, styles.getBorderBottomWidth());
+
+	assertEquals(new Color(0, 0, 255), styles.getBorderLeftColor());
+	assertEquals(CSS.DASHED, styles.getBorderLeftStyle());
+	assertEquals(3, styles.getBorderLeftWidth());
+
+	assertEquals(new Color(255, 0, 255), styles.getBorderRightColor());
+	assertEquals(CSS.DOTTED, styles.getBorderRightStyle());
+	assertEquals(5, styles.getBorderRightWidth());
+
+	assertEquals(new Color(128, 128, 128), styles.getBorderTopColor());
+	assertEquals(CSS.DOUBLE, styles.getBorderTopStyle());
+	assertEquals(1, styles.getBorderTopWidth());
+
+	assertEquals(new Color(0, 128, 0), styles.getColor());
+	assertEquals(CSS.BLOCK, styles.getDisplay());
+
+	assertEquals(3543, styles.getMarginBottom().get(10));
+	assertEquals(0, styles.getMarginLeft().get(10));
+	assertEquals(125, styles.getMarginRight().get(10));
+	assertEquals(75, styles.getMarginTop().get(10));
+
+	assertEquals(450, styles.getPaddingBottom().get(10));
+	assertEquals(4252, styles.getPaddingLeft().get(10));
+	assertEquals(120, styles.getPaddingRight().get(10));
+	assertEquals(19, styles.getPaddingTop().get(10));
+    }
+
+    public void testImportant() throws Exception {
+        StyleSheet ss = parseStyleSheetResource("testImportant.css");
+        Element a = new Element("a");
+        Styles styles = ss.getStyles(a);
+        
+        Color black = new Color(0, 0, 0);
+        //Color white = new Color(255, 255, 255);
+        //Color red = new Color(255, 0, 0);
+        Color blue = new Color(0, 0, 255);
+        
+        assertEquals(black, styles.getBackgroundColor());
+        assertEquals(black, styles.getColor());
+        assertEquals(blue, styles.getBorderTopColor());
+        
+    }
+    
+    public void testMarginInheritance() throws Exception {
+        StyleSheet ss = parseStyleSheetResource("test2.css");
+        Element root = new Element("margin1");
+        Element child = new Element("defaults");
+        child.setParent(root);
+        Styles styles = ss.getStyles(child);
+        
+        assertEquals(0, styles.getMarginTop().get(67));
+        assertEquals(0, styles.getMarginLeft().get(67));
+        assertEquals(0, styles.getMarginRight().get(67));
+        assertEquals(0, styles.getMarginBottom().get(67));
+    }
+
+    public void testSimple() throws Exception {
+	StyleSheet ss = parseStyleSheetResource("test2.css");
+	Styles styles = ss.getStyles(new Element("simple"));
+
+	assertEquals(12.5f, styles.getFontSize(), 0.1);
+
+	assertEquals(new Color(0, 255, 255), styles.getBackgroundColor());
+
+	assertEquals(new Color(0, 0, 0), styles.getBorderBottomColor());
+	assertEquals(CSS.SOLID, styles.getBorderBottomStyle());
+	assertEquals(1, styles.getBorderBottomWidth());
+
+	assertEquals(new Color(0, 0, 255), styles.getBorderLeftColor());
+	assertEquals(CSS.DASHED, styles.getBorderLeftStyle());
+	assertEquals(3, styles.getBorderLeftWidth());
+
+	assertEquals(new Color(255, 0, 255), styles.getBorderRightColor());
+	assertEquals(CSS.DOTTED, styles.getBorderRightStyle());
+	assertEquals(5, styles.getBorderRightWidth());
+
+	assertEquals(new Color(128, 128, 128), styles.getBorderTopColor());
+	assertEquals(CSS.DOUBLE, styles.getBorderTopStyle());
+	assertEquals(1, styles.getBorderTopWidth());
+
+	assertEquals(new Color(0, 128, 0), styles.getColor());
+	assertEquals(CSS.BLOCK, styles.getDisplay());
+
+	assertEquals(3543, styles.getMarginBottom().get(10));
+	assertEquals(0, styles.getMarginLeft().get(10));
+	assertEquals(125, styles.getMarginRight().get(10));
+	assertEquals(75, styles.getMarginTop().get(10));
+
+	assertEquals(450, styles.getPaddingBottom().get(10));
+	assertEquals(4252, styles.getPaddingLeft().get(10));
+	assertEquals(120, styles.getPaddingRight().get(10));
+	assertEquals(19, styles.getPaddingTop().get(10));
+
+    }
+
+
+    /**
+     * Confirm our assumptions about the structure of lexical units.
+     */
+    /*
+    public void testLexicalUnits() throws Exception {
+	Element aElement = new Element("A");
+	StyleSheet ss = parseStyleSheetResource("testLexicalUnits.css");
+	Styles styles = ss.get(aElement);
+
+	LexicalUnit lu;
+	LexicalUnit lu2;
+
+	System.out.println("DEBUG: styles for element A");
+	dumpStyles(styles);
+
+	// TEST: how to access color specified in "rgb(R,G,B)" format
+	lu = styles.get(CSS.COLOR);
+	assertEquals(LexicalUnit.SAC_RGBCOLOR, lu.getLexicalUnitType());
+	assertEquals("color", lu.getFunctionName());
+	lu2 = lu.getParameters();
+	assertNotNull(lu2);
+	assertEquals(LexicalUnit.SAC_INTEGER, lu2.getLexicalUnitType());
+	assertEquals(255, lu2.getIntegerValue());
+	lu2 = lu2.getNextLexicalUnit();
+	assertEquals(LexicalUnit.SAC_OPERATOR_COMMA, lu2.getLexicalUnitType());
+	lu2 = lu2.getNextLexicalUnit();
+	assertEquals(LexicalUnit.SAC_INTEGER, lu2.getLexicalUnitType());
+	assertEquals(255, lu2.getIntegerValue());
+	lu2 = lu2.getNextLexicalUnit();
+	assertEquals(LexicalUnit.SAC_OPERATOR_COMMA, lu2.getLexicalUnitType());
+	lu2 = lu2.getNextLexicalUnit();
+	assertEquals(LexicalUnit.SAC_INTEGER, lu2.getLexicalUnitType());
+	assertEquals(255, lu2.getIntegerValue());
+	lu2 = lu2.getNextLexicalUnit();
+	assertNull(lu2);
+
+	// TEST: color specified in "#RGB" format is accessed the same way
+	lu = styles.get(CSS.BACKGROUND_COLOR);
+	assertEquals(LexicalUnit.SAC_RGBCOLOR, lu.getLexicalUnitType());
+	assertEquals("color", lu.getFunctionName());
+	lu2 = lu.getParameters();
+	assertNotNull(lu2);
+	assertEquals(LexicalUnit.SAC_INTEGER, lu2.getLexicalUnitType());
+	assertEquals(0, lu2.getIntegerValue());
+	lu2 = lu2.getNextLexicalUnit();
+	assertEquals(LexicalUnit.SAC_OPERATOR_COMMA, lu2.getLexicalUnitType());
+	lu2 = lu2.getNextLexicalUnit();
+	assertEquals(LexicalUnit.SAC_INTEGER, lu2.getLexicalUnitType());
+	assertEquals(0, lu2.getIntegerValue());
+	lu2 = lu2.getNextLexicalUnit();
+	assertEquals(LexicalUnit.SAC_OPERATOR_COMMA, lu2.getLexicalUnitType());
+	lu2 = lu2.getNextLexicalUnit();
+	assertEquals(LexicalUnit.SAC_INTEGER, lu2.getLexicalUnitType());
+	assertEquals(0, lu2.getIntegerValue());
+	lu2 = lu2.getNextLexicalUnit();
+	assertNull(lu2);
+
+	// TEST: color specified in "rgb(R%,G%,B%)" is accessed as SAC_PERCENTAGE
+	lu = styles.get(CSS.BORDER_BOTTOM);
+	assertEquals(LexicalUnit.SAC_RGBCOLOR, lu.getLexicalUnitType());
+	assertEquals("color", lu.getFunctionName());
+	lu2 = lu.getParameters();
+	assertNotNull(lu2);
+	assertEquals(LexicalUnit.SAC_PERCENTAGE, lu2.getLexicalUnitType());
+	assertEquals(10f, lu2.getFloatValue(), 0.001);
+	lu2 = lu2.getNextLexicalUnit();
+	assertEquals(LexicalUnit.SAC_OPERATOR_COMMA, lu2.getLexicalUnitType());
+	lu2 = lu2.getNextLexicalUnit();
+	assertEquals(LexicalUnit.SAC_PERCENTAGE, lu2.getLexicalUnitType());
+	assertEquals(20f, lu2.getFloatValue(), 0.001);
+	lu2 = lu2.getNextLexicalUnit();
+	assertEquals(LexicalUnit.SAC_OPERATOR_COMMA, lu2.getLexicalUnitType());
+	lu2 = lu2.getNextLexicalUnit();
+	assertEquals(LexicalUnit.SAC_PERCENTAGE, lu2.getLexicalUnitType());
+	assertEquals(30f, lu2.getFloatValue(), 0.001);
+	lu2 = lu2.getNextLexicalUnit();
+	assertNull(lu2);
+
+	// TEST: color incompletely specified, e.g. "rgb(10,20)" is rejected
+	// by the parser
+	lu = styles.get(CSS.BORDER_LEFT);
+	assertNull(lu);
+
+	// TEST: color incorrectly specified, e.g. "10", "foo", or 
+	// "rgb(larry,curly,moe)" is passed by the parser
+	lu = styles.get(CSS.BORDER_RIGHT); // "10" is not a valid lexical unit
+	assertNull(lu);
+	
+	lu = styles.get(CSS.BORDER_TOP); // "rgb(larry, curly, moe)"
+	assertNull(lu); 
+
+	styles = ss.get(new Element("B"));
+	lu = styles.get(CSS.BACKGROUND_COLOR); // "foo"
+	assertEquals(LexicalUnit.SAC_IDENT, lu.getLexicalUnitType());
+	assertEquals("foo", lu.getStringValue());
+
+	lu = styles.get(CSS.COLOR); // "10px"
+	assertEquals(LexicalUnit.SAC_PIXEL, lu.getLexicalUnitType());
+	//assertEquals(10, lu.getIntegerValue()); // NOTE: not an int!
+	assertEquals(10f, lu.getFloatValue(), 0.001);
+    }
+
+
+    public void testExpandMargins() throws Exception {
+	Element aElement = new Element("A");
+	Element bElement = new Element("B");
+	Element cElement = new Element("C");
+	Element dElement = new Element("D");
+	Element eElement = new Element("E");
+	Element fElement = new Element("F");
+	Element gElement = new Element("G");
+	Document doc = new Document(new Element("root"));
+	doc.insertElement(1, aElement);
+	doc.insertElement(3, cElement);
+	doc.insertElement(5, cElement);
+	doc.insertElement(7, cElement);
+
+	StyleSheet ss = parseStyleSheetResource("expansion.css");
+	Styles styles;
+
+	// single margin call expands
+	styles = ss.get(aElement);
+	assertProperty(styles, CSS.MARGIN_BOTTOM, 1.0f, LexicalUnit.SAC_INCH);
+	assertProperty(styles, CSS.MARGIN_LEFT, 1.0f, LexicalUnit.SAC_INCH);
+	assertProperty(styles, CSS.MARGIN_RIGHT, 1.0f, LexicalUnit.SAC_INCH);
+	assertProperty(styles, CSS.MARGIN_TOP, 1.0f, LexicalUnit.SAC_INCH);
+
+	// more-specific overrides; shorthand comes first
+	styles = ss.get(bElement);
+	assertProperty(styles, CSS.MARGIN_BOTTOM, 0.5f, LexicalUnit.SAC_INCH);
+	assertProperty(styles, CSS.MARGIN_LEFT, 0.5f, LexicalUnit.SAC_INCH);
+	assertProperty(styles, CSS.MARGIN_RIGHT, 0.5f, LexicalUnit.SAC_INCH);
+	assertProperty(styles, CSS.MARGIN_TOP, 0.5f, LexicalUnit.SAC_INCH);
+	
+	// more-specific overrides; shorthand comes last
+	styles = ss.get(cElement);
+	assertProperty(styles, CSS.MARGIN_BOTTOM, 0.5f, LexicalUnit.SAC_INCH);
+	assertProperty(styles, CSS.MARGIN_LEFT, 0.5f, LexicalUnit.SAC_INCH);
+	assertProperty(styles, CSS.MARGIN_RIGHT, 0.5f, LexicalUnit.SAC_INCH);
+	assertProperty(styles, CSS.MARGIN_TOP, 0.5f, LexicalUnit.SAC_INCH);
+
+	// second shorthand overrides first
+	styles = ss.get(dElement);
+	assertProperty(styles, CSS.MARGIN_BOTTOM, 0.25f, LexicalUnit.SAC_INCH);
+	assertProperty(styles, CSS.MARGIN_LEFT, 0.25f, LexicalUnit.SAC_INCH);
+	assertProperty(styles, CSS.MARGIN_RIGHT, 0.25f, LexicalUnit.SAC_INCH);
+	assertProperty(styles, CSS.MARGIN_TOP, 0.25f, LexicalUnit.SAC_INCH);
+	
+	// expanding two values for margins
+	styles = ss.get(eElement);
+	assertProperty(styles, CSS.MARGIN_BOTTOM, 1.0f, LexicalUnit.SAC_INCH);
+	assertProperty(styles, CSS.MARGIN_LEFT, 2.0f, LexicalUnit.SAC_INCH);
+	assertProperty(styles, CSS.MARGIN_RIGHT, 2.0f, LexicalUnit.SAC_INCH);
+	assertProperty(styles, CSS.MARGIN_TOP, 1.0f, LexicalUnit.SAC_INCH);
+	
+	// expanding three values for margins
+	styles = ss.get(fElement);
+	assertProperty(styles, CSS.MARGIN_BOTTOM, 3.0f, LexicalUnit.SAC_INCH);
+	assertProperty(styles, CSS.MARGIN_LEFT, 2.0f, LexicalUnit.SAC_INCH);
+	assertProperty(styles, CSS.MARGIN_RIGHT, 2.0f, LexicalUnit.SAC_INCH);
+	assertProperty(styles, CSS.MARGIN_TOP, 1.0f, LexicalUnit.SAC_INCH);
+	
+	// expanding four values for margins
+	styles = ss.get(gElement);
+	assertProperty(styles, CSS.MARGIN_TOP, 1.0f, LexicalUnit.SAC_INCH);
+	assertProperty(styles, CSS.MARGIN_RIGHT, 2.0f, LexicalUnit.SAC_INCH);
+	assertProperty(styles, CSS.MARGIN_BOTTOM, 3.0f, LexicalUnit.SAC_INCH);
+	assertProperty(styles, CSS.MARGIN_LEFT, 4.0f, LexicalUnit.SAC_INCH);
+	
+    }
+
+
+    public static void assertProperty(Styles styles, 
+				      String name, 
+				      float value,
+				      int lexicalUnitType) {
+
+	LexicalUnit lu = styles.get(name);
+	assertEquals(lexicalUnitType, lu.getLexicalUnitType());
+	assertEquals(value, lu.getFloatValue(), 0.001f);
+    }
+
+    public static void assertProperty(Styles styles, 
+				      String name, 
+				      String value,
+				      int lexicalUnitType) {
+
+	LexicalUnit lu = styles.get(name);
+	assertEquals(lexicalUnitType, lu.getLexicalUnitType());
+	assertEquals(value, lu.getStringValue());
+    }
+
+    public static void dumpStyles(Styles styles) {
+	java.util.Iterator iter = styles.getPropertyNames().iterator();
+	while (iter.hasNext()) {
+	    String name = (String) iter.next();
+	    System.out.println(name + ": " + styles.get(name));
+	}
+    }
+    */ 
+
+    private StyleSheet parseStyleSheetResource(String resource) 
+	throws java.io.IOException {
+        
+        URL url = this.getClass().getResource(resource);
+        StyleSheetReader reader = new StyleSheetReader();
+        return reader.read(url);
+
+    }
+}
+
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/PropertyTest.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/PropertyTest.java
new file mode 100644
index 0000000..b53eb40
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/PropertyTest.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.css;
+
+import org.eclipse.wst.xml.vex.core.internal.core.DisplayDevice;
+import org.eclipse.wst.xml.vex.core.internal.css.BorderStyleProperty;
+import org.eclipse.wst.xml.vex.core.internal.css.BorderWidthProperty;
+import org.eclipse.wst.xml.vex.core.internal.css.CSS;
+import org.eclipse.wst.xml.vex.core.internal.css.IProperty;
+import org.eclipse.wst.xml.vex.core.internal.css.Styles;
+import org.w3c.css.sac.LexicalUnit;
+
+import junit.framework.TestCase;
+
+public class PropertyTest extends TestCase {
+
+    /**
+     * From CSS2.1 section 8.5.3
+     */
+    public void testBorderStyleProperty() throws Exception {
+        Styles styles = new Styles();
+        Styles parentStyles = new Styles();
+        IProperty prop = new BorderStyleProperty(CSS.BORDER_TOP_STYLE);
+        
+        // Inheritance
+        parentStyles.put(CSS.BORDER_TOP_STYLE, CSS.DASHED);
+        assertEquals(CSS.NONE, prop.calculate(null, parentStyles, styles));
+        assertEquals(CSS.DASHED, prop.calculate(TestLU.INHERIT, parentStyles, styles)); // not inherited
+
+        // Regular values
+        assertEquals(CSS.NONE, prop.calculate(TestLU.createIdent(CSS.NONE), parentStyles, styles));
+        assertEquals(CSS.HIDDEN, prop.calculate(TestLU.createIdent(CSS.HIDDEN), parentStyles, styles));
+        assertEquals(CSS.DOTTED, prop.calculate(TestLU.createIdent(CSS.DOTTED), parentStyles, styles));
+        assertEquals(CSS.DASHED, prop.calculate(TestLU.createIdent(CSS.DASHED), parentStyles, styles));
+        assertEquals(CSS.SOLID, prop.calculate(TestLU.createIdent(CSS.SOLID), parentStyles, styles));
+        assertEquals(CSS.DOUBLE, prop.calculate(TestLU.createIdent(CSS.DOUBLE), parentStyles, styles));
+        assertEquals(CSS.GROOVE, prop.calculate(TestLU.createIdent(CSS.GROOVE), parentStyles, styles));
+        assertEquals(CSS.RIDGE, prop.calculate(TestLU.createIdent(CSS.RIDGE), parentStyles, styles));
+        assertEquals(CSS.INSET, prop.calculate(TestLU.createIdent(CSS.INSET), parentStyles, styles));
+        assertEquals(CSS.OUTSET, prop.calculate(TestLU.createIdent(CSS.OUTSET), parentStyles, styles));
+        
+        // Invalid token
+        assertEquals(CSS.NONE, prop.calculate(TestLU.createIdent(CSS.BOLD), parentStyles, styles));
+        
+        // Wrong type
+        assertEquals(CSS.NONE, prop.calculate(TestLU.createString(CSS.HIDDEN), parentStyles, styles));
+    }
+
+
+    /**
+     * From CSS2.1 section 8.5.1
+     */
+    public void testBorderWidthProperty() throws Exception {
+        
+        Styles styles = new Styles();
+        Styles parentStyles = new Styles();
+        DisplayDevice.setCurrent(new DummyDisplayDevice(50, 100));
+        IProperty prop = new BorderWidthProperty(CSS.BORDER_TOP_WIDTH, CSS.BORDER_TOP_STYLE, IProperty.AXIS_VERTICAL);
+
+        styles.put(CSS.FONT_SIZE, new Float(12));
+        styles.put(CSS.BORDER_TOP_STYLE, CSS.SOLID);
+        
+        // Inheritance
+        parentStyles.put(CSS.BORDER_TOP_WIDTH, new Integer(27));
+        assertEquals(new Integer(3), prop.calculate(null, parentStyles, styles));
+        assertEquals(new Integer(27), prop.calculate(TestLU.INHERIT, parentStyles, styles)); // not inherited
+
+        // Regular values
+        assertEquals(new Integer(20), prop.calculate(TestLU.createFloat(LexicalUnit.SAC_INCH, 0.2f), parentStyles, styles));
+        
+        // Invalid token
+        assertEquals(new Integer(3), prop.calculate(TestLU.createIdent(CSS.BOLD), parentStyles, styles));
+        
+        // Wrong type
+        assertEquals(new Integer(3), prop.calculate(TestLU.createString(CSS.HIDDEN), parentStyles, styles));
+        
+        // Corresponding style is "none" or "hidden"
+        styles.put(CSS.BORDER_TOP_STYLE, CSS.NONE);
+        assertEquals(new Integer(0), prop.calculate(TestLU.createFloat(LexicalUnit.SAC_INCH, 0.2f), parentStyles, styles));
+        styles.put(CSS.BORDER_TOP_STYLE, CSS.HIDDEN);
+        assertEquals(new Integer(0), prop.calculate(TestLU.createFloat(LexicalUnit.SAC_INCH, 0.2f), parentStyles, styles));
+        
+        // check that we use the proper PPI 
+        styles.put(CSS.BORDER_LEFT_STYLE, CSS.SOLID);
+        prop = new BorderWidthProperty(CSS.BORDER_LEFT_WIDTH, CSS.BORDER_LEFT_STYLE, IProperty.AXIS_HORIZONTAL);
+        assertEquals(new Integer(10), prop.calculate(TestLU.createFloat(LexicalUnit.SAC_INCH, 0.2f), parentStyles, styles));
+    }
+    
+
+    /**
+     * From CSS2.1 section 8.5.2 (border-XXX-color),
+     * section 14.1 (color), and section 14.2.1 (background-color)
+     */
+    public void testColorProperty() throws Exception {
+    }
+    
+    
+    private class DummyDisplayDevice extends DisplayDevice {
+
+        public DummyDisplayDevice(int horizontalPPI, int verticalPPI) {
+            this.horizontalPPI = horizontalPPI;
+            this.verticalPPI = verticalPPI;
+        }
+        
+        public int getHorizontalPPI() {
+            return this.horizontalPPI;
+        }
+
+        public int getVerticalPPI() {
+            return this.verticalPPI;
+        }
+        
+        
+        private int horizontalPPI;
+        private int verticalPPI;
+    }
+}
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/RuleTest.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/RuleTest.java
new file mode 100644
index 0000000..09c0ad7
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/RuleTest.java
@@ -0,0 +1,272 @@
+/*******************************************************************************
+ * 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.net.URL;
+
+import org.eclipse.wst.xml.vex.core.internal.css.Rule;
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheet;
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheetReader;
+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.RootElement;
+
+import junit.framework.TestCase;
+
+/**
+ * Test rule matching.
+ */
+public class RuleTest extends TestCase {
+
+    public void testRuleMatching() throws Exception {
+        URL url = RuleTest.class.getResource("testRules.css");
+        StyleSheetReader reader = new StyleSheetReader();
+        reader.setSource(StyleSheet.SOURCE_USER);
+        StyleSheet ss = reader.read(url);
+        Rule[] rules = ss.getRules();
+        
+        RootElement a = new RootElement("a");
+        Element b = new Element("b");
+        Element c = new Element("c");
+        Element d = new Element("d");
+        Element e = new Element("e");
+        Element f = new Element("f");
+        
+        b.setAttribute("color", "blue");
+        c.setAttribute("color", "blue red");
+        d.setAttribute("color", "gree blue red");
+        e.setAttribute("color", "red blue");
+        f.setAttribute("color", "bluered");
+        
+        Document doc = new Document(a);
+        doc.insertElement(1, b);
+        doc.insertElement(2, c);
+        doc.insertElement(3, d);
+        doc.insertElement(4, e);
+        doc.insertElement(5, f);
+        
+//        /*  0 */ c { }
+        Rule rule = rules[0];
+        assertFalse(rule.matches(a));
+        assertFalse(rule.matches(b));
+        assertTrue(rule.matches(c));
+        assertFalse(rule.matches(d));
+        assertFalse(rule.matches(e));
+        
+//        /*  1 */ b c { }
+        rule = rules[1];
+        assertFalse(rule.matches(a));
+        assertFalse(rule.matches(b));
+        assertTrue(rule.matches(c));
+        assertFalse(rule.matches(d));
+        assertFalse(rule.matches(e));
+        
+//        /*  2 */ b d { }
+        rule = rules[2];
+        assertFalse(rule.matches(a));
+        assertFalse(rule.matches(b));
+        assertFalse(rule.matches(c));
+        assertTrue(rule.matches(d));
+        assertFalse(rule.matches(e));
+        
+//        /*  3 */ other b c { }
+        rule = rules[3];
+        assertFalse(rule.matches(a));
+        assertFalse(rule.matches(b));
+        assertFalse(rule.matches(c));
+        assertFalse(rule.matches(d));
+        assertFalse(rule.matches(e));
+        
+//        /*  4 */ other b d { }
+        rule = rules[4];
+        assertFalse(rule.matches(a));
+        assertFalse(rule.matches(b));
+        assertFalse(rule.matches(c));
+        assertFalse(rule.matches(d));
+        assertFalse(rule.matches(e));
+        
+//        /*  5 */ a c e { }
+        rule = rules[5];
+        assertFalse(rule.matches(a));
+        assertFalse(rule.matches(b));
+        assertFalse(rule.matches(c));
+        assertFalse(rule.matches(d));
+        assertTrue(rule.matches(e));
+        
+//        /*  6 */ c a e { }
+        rule = rules[6];
+        assertFalse(rule.matches(a));
+        assertFalse(rule.matches(b));
+        assertFalse(rule.matches(c));
+        assertFalse(rule.matches(d));
+        assertFalse(rule.matches(e));
+        
+//        /*  7 */ * { }
+        rule = rules[7];
+        assertTrue(rule.matches(a));
+        assertTrue(rule.matches(b));
+        assertTrue(rule.matches(c));
+        assertTrue(rule.matches(d));
+        assertTrue(rule.matches(e));
+
+//        /*  8 */ *[color]
+        rule = rules[8];
+        assertFalse(rule.matches(a));
+        assertTrue(rule.matches(b));
+        assertTrue(rule.matches(c));
+        assertTrue(rule.matches(d));
+        assertTrue(rule.matches(e));
+        
+//        /*  9 */ a[color]
+        rule = rules[9];
+        assertFalse(rule.matches(a));
+        assertFalse(rule.matches(b));
+        assertFalse(rule.matches(c));
+        assertFalse(rule.matches(d));
+        assertFalse(rule.matches(e));
+        
+//        /* 10 */ b[color]
+        rule = rules[10];
+        assertFalse(rule.matches(a));
+        assertTrue(rule.matches(b));
+        assertFalse(rule.matches(c));
+        assertFalse(rule.matches(d));
+        assertFalse(rule.matches(e));
+        
+//        /* 11 */ c[color]
+        rule = rules[11];
+        assertFalse(rule.matches(a));
+        assertFalse(rule.matches(b));
+        assertTrue(rule.matches(c));
+        assertFalse(rule.matches(d));
+        assertFalse(rule.matches(e));
+        
+//        /* 12 */ d[color]
+        rule = rules[12];
+        assertFalse(rule.matches(a));
+        assertFalse(rule.matches(b));
+        assertFalse(rule.matches(c));
+        assertTrue(rule.matches(d));
+        assertFalse(rule.matches(e));
+        
+//        /* 13 */ *[color=blue]
+        rule = rules[13];
+        assertFalse(rule.matches(a));
+        assertTrue(rule.matches(b));
+        assertFalse(rule.matches(c));
+        assertFalse(rule.matches(d));
+        assertFalse(rule.matches(e));
+        
+//        /* 14 */ a[color=blue]
+        rule = rules[14];
+        assertFalse(rule.matches(a));
+        assertFalse(rule.matches(b));
+        assertFalse(rule.matches(c));
+        assertFalse(rule.matches(d));
+        assertFalse(rule.matches(e));
+        
+//        /* 15 */ b[color=blue]
+        rule = rules[15];
+        assertFalse(rule.matches(a));
+        assertTrue(rule.matches(b));
+        assertFalse(rule.matches(c));
+        assertFalse(rule.matches(d));
+        assertFalse(rule.matches(e));
+        
+//        /* 16 */ b[color='blue']
+        rule = rules[16];
+        assertFalse(rule.matches(a));
+        assertTrue(rule.matches(b));
+        assertFalse(rule.matches(c));
+        assertFalse(rule.matches(d));
+        assertFalse(rule.matches(e));
+        
+//        /* 17 */ b[color="blue"]
+        rule = rules[17];
+        assertFalse(rule.matches(a));
+        assertTrue(rule.matches(b));
+        assertFalse(rule.matches(c));
+        assertFalse(rule.matches(d));
+        assertFalse(rule.matches(e));
+        
+//        /* 18 */ c[color=blue]
+        rule = rules[18];
+        assertFalse(rule.matches(a));
+        assertFalse(rule.matches(b));
+        assertFalse(rule.matches(c));
+        assertFalse(rule.matches(d));
+        assertFalse(rule.matches(e));
+        
+//        /* 19 */ a * { }
+        rule = rules[19];
+        assertFalse(rule.matches(a));
+        assertTrue(rule.matches(b));
+        assertTrue(rule.matches(c));
+        assertTrue(rule.matches(d));
+        assertTrue(rule.matches(e));
+        
+//        /* 20 */ a > * { }
+        rule = rules[20];
+        assertFalse(rule.matches(a));
+        assertTrue(rule.matches(b));
+        assertFalse(rule.matches(c));
+        assertFalse(rule.matches(d));
+        assertFalse(rule.matches(e));
+        
+//        /* 21 */ a *[color] { }
+        rule = rules[21];
+        assertFalse(rule.matches(a));
+        assertTrue(rule.matches(b));
+        assertTrue(rule.matches(c));
+        assertTrue(rule.matches(d));
+        assertTrue(rule.matches(e));
+        
+//        /* 22 */ a > *[color] { }
+        rule = rules[22];
+        assertFalse(rule.matches(a));
+        assertTrue(rule.matches(b));
+        assertFalse(rule.matches(c));
+        assertFalse(rule.matches(d));
+        assertFalse(rule.matches(e));
+        
+//        /* 23 */ *[color~=blue] { }
+        rule = rules[23];
+        assertFalse(rule.matches(a));
+        assertTrue(rule.matches(b));
+        assertTrue(rule.matches(c));
+        assertTrue(rule.matches(d));
+        assertTrue(rule.matches(e));
+        assertFalse(rule.matches(f));
+
+        //
+        // Rules that test class conditions
+        //
+        
+        b.setAttribute("class", "foo");
+        c.setAttribute("class", "foo bar");
+        d.setAttribute("class", "bar");
+        
+//        /* 24 */ .foo { }
+        rule = rules[24];
+        assertFalse(rule.matches(a));
+        assertTrue(rule.matches(b));
+        assertTrue(rule.matches(c));
+        assertFalse(rule.matches(d));
+        
+//        /* 25 */ .foo.bar { }
+        rule = rules[25];
+        assertFalse(rule.matches(a));
+        assertFalse(rule.matches(b));
+        assertTrue(rule.matches(c));
+        assertFalse(rule.matches(d));
+
+    }
+}
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/SerializationTest.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/SerializationTest.java
new file mode 100644
index 0000000..95d2165
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/SerializationTest.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * 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.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.URL;
+
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheet;
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheetReader;
+
+import junit.framework.TestCase;
+
+public class SerializationTest extends TestCase {
+
+    public void testSerialization() throws Exception {
+//        serialize("test1.css");
+//        serialize("test2.css");
+        serialize("testLexicalUnits.css");
+//        serialize("testRules.css");
+    }
+    
+    private void serialize(String resource) throws Exception {
+        
+        URL url = this.getClass().getResource(resource);
+        StyleSheetReader reader = new StyleSheetReader();
+        StyleSheet ss = reader.read(url);
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        ObjectOutputStream oos = new ObjectOutputStream(baos);
+        oos.writeObject(ss);
+        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        ObjectInputStream ois = new ObjectInputStream(bais);
+        ois.readObject();
+    }
+}
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/TestDisplayDevice.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/TestDisplayDevice.java
new file mode 100644
index 0000000..3c31faf
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/TestDisplayDevice.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * 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;
+
+public class TestDisplayDevice extends DisplayDevice {
+
+    private int horizontalPPI;
+    private int verticalPPI;
+
+    public TestDisplayDevice(int horizontalPPI, int verticalPPI) {
+        this.horizontalPPI = horizontalPPI;
+        this.verticalPPI = verticalPPI;
+    }
+
+    public int getHorizontalPPI() {
+        return this.horizontalPPI;
+    }
+
+    /**
+     *
+     */
+
+    public int getVerticalPPI() {
+        return this.verticalPPI;
+    }
+
+}
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/TestLU.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/TestLU.java
new file mode 100644
index 0000000..958ed2a
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/TestLU.java
@@ -0,0 +1,115 @@
+/*******************************************************************************
+ * 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;
+
+/**
+ * Dummy LexicalUnit implementation.
+ */
+public class TestLU implements LexicalUnit {
+
+    public TestLU(short type) {
+        this.lexicalUnitType = type;
+    }
+
+    public static LexicalUnit INHERIT = new TestLU(LexicalUnit.SAC_INHERIT);
+    
+    public static LexicalUnit createFloat(short units, float value) {
+        TestLU lu = new TestLU(units);
+        lu.setFloatValue(value);
+        return lu;
+    }
+    
+    public static LexicalUnit createIdent(String s) {
+        TestLU lu = new TestLU(LexicalUnit.SAC_IDENT);
+        lu.setStringValue(s);
+        return lu;
+    }
+    
+    public static LexicalUnit createString(String s) {
+        TestLU lu = new TestLU(LexicalUnit.SAC_STRING_VALUE);
+        lu.setStringValue(s);
+        return lu;
+    }
+    
+    public String getDimensionUnitText() {
+        return this.dimensionUnitText;
+    }
+    public float getFloatValue() {
+        return this.floatValue;
+    }
+    public String getFunctionName() {
+        return this.functionName;
+    }
+    public int getIntegerValue() {
+        return this.integerValue;
+    }
+    public short getLexicalUnitType() {
+        return this.lexicalUnitType;
+    }
+    public LexicalUnit getNextLexicalUnit() {
+        return this.nextLexicalUnit;
+    }
+    public LexicalUnit getParameters() {
+        return this.parameters;
+    }
+    public LexicalUnit getPreviousLexicalUnit() {
+        return this.previousLexicalUnit;
+    }
+    public String getStringValue() {
+        return this.stringValue;
+    }
+    public LexicalUnit getSubValues() {
+        return this.subValues;
+    }
+    public void setDimensionUnitText(String dimensionUnitText) {
+        this.dimensionUnitText = dimensionUnitText;
+    }
+    public void setFloatValue(float floatValue) {
+        this.floatValue = floatValue;
+    }
+    public void setFunctionName(String functionName) {
+        this.functionName = functionName;
+    }
+    public void setIntegerValue(int integerValue) {
+        this.integerValue = integerValue;
+    }
+    public void setLexicalUnitType(short lexicalUnitType) {
+        this.lexicalUnitType = lexicalUnitType;
+    }
+    public void setNextLexicalUnit(LexicalUnit nextLexicalUnit) {
+        this.nextLexicalUnit = nextLexicalUnit;
+    }
+    public void setParameters(LexicalUnit parameters) {
+        this.parameters = parameters;
+    }
+    public void setPreviousLexicalUnit(LexicalUnit previousLexicalUnit) {
+        this.previousLexicalUnit = previousLexicalUnit;
+    }
+    public void setStringValue(String stringValue) {
+        this.stringValue = stringValue;
+    }
+    public void setSubValues(LexicalUnit subValues) {
+        this.subValues = subValues;
+    }
+    private short lexicalUnitType;
+    private LexicalUnit nextLexicalUnit;
+    private LexicalUnit previousLexicalUnit;
+    private int integerValue;
+    private float floatValue;
+    private String dimensionUnitText;
+    private String functionName;
+    private LexicalUnit parameters;
+    private String stringValue;
+    private LexicalUnit subValues;
+    
+}
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/expansion.css b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/expansion.css
new file mode 100644
index 0000000..d731168
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/expansion.css
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * 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
+ *******************************************************************************/
+A {
+  margin: 1in;
+}
+
+B {
+  margin: 1in;
+  margin-bottom: 0.5in;
+  margin-left: 0.5in;
+  margin-right: 0.5in;
+  margin-top: 0.5in;
+}
+
+C {
+  margin-bottom: 0.5in;
+  margin-left: 0.5in;
+  margin-right: 0.5in;
+  margin-top: 0.5in;
+  margin: 1in;
+}
+
+D {
+ margin: 0.5in;
+ margin: 0.25in;
+}
+
+E {
+ margin: 1in 2in;
+}
+
+F {
+ margin: 1in 2in 3in;
+}
+
+G {
+ margin: 1in 2in 3in 4in;
+}
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/test1.css b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/test1.css
new file mode 100644
index 0000000..0903f4c
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/test1.css
@@ -0,0 +1,28 @@
+/* Stylesheet for unit testing */
+
+A {
+  name: A;
+}
+
+B {
+  name: B;
+}
+
+/* should take precedence over above, since more elements */
+B, D {
+  name: BorD;
+}
+
+C {
+  name: C;
+}
+
+/* should take precedence over C rule above, since more elements */
+A C {
+  name: AdescendC;
+}
+
+/* Confirm *not* selected. */
+A > C {
+  name: AchildC;
+}
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/test1.xml b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/test1.xml
new file mode 100644
index 0000000..0dbff01
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/test1.xml
@@ -0,0 +1,5 @@
+<A>
+  <B>
+    <C/>
+  </B>
+</A>
\ No newline at end of file
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/test2.css b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/test2.css
new file mode 100644
index 0000000..3fb0334
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/test2.css
@@ -0,0 +1,277 @@
+/* defaults */
+defaults {
+}
+
+/* extra tokens not covered by "simple" */
+extras {
+    background-color: lime;       /* 0, 255, 0 */
+
+    border-bottom-color: maroon;  /* 128, 0, 0 */
+    border-bottom-style: solid;
+
+    border-left-color: navy;      /* 0, 0, 128 */
+    border-left-style: dashed;
+
+    border-right-color: olive;    /* 128, 128, 0 */
+    border-right-style: dotted;
+
+    border-top-color: purple;     /* 128, 0, 128 */
+    border-top-style: double;
+
+    color: red;                   /* 255, 0, 0 */
+    display: inline;
+}
+
+extras2 {
+    background-color: silver;       /* 192, 192, 192 */
+
+    border-bottom-color: teal;      /* 0, 128, 128 */
+    border-bottom-style: none;
+    border-bottom-width: thin;      /* 0, due to style: none */
+
+    border-left-color: white;       /* 255, 255, 255 */
+    border-left-style: groove;
+
+    border-right-color: yellow;     /* 255, 255, 0 */
+    border-right-style: ridge;
+
+    border-top-style: inset;
+}
+
+xxsmall {
+    font-size: xx-small;
+}
+
+xsmall {
+    font-size: x-small;
+}
+
+small {
+    font-size: small;
+}
+
+medium {
+    font-size: medium;
+}
+
+large {
+    font-size: large;
+}
+
+xlarge {
+    font-size: x-large;
+}
+xxlarge {
+    font-size: xx-large;
+}
+
+smaller {
+    font-size: smaller;
+}
+
+larger {
+    font-size: larger;
+}
+
+font100pct {
+    font-size: 100%;
+}
+
+font80pct {
+    font-size: 80%;
+}
+
+font120pct {
+    font-size: 120%;
+}
+
+inherit {
+    background-color: inherit;
+
+    border-bottom-color: inherit;
+    border-bottom-style: inherit;
+    border-bottom-width: inherit;
+
+    border-left-color: inherit;
+    border-left-style: inherit;
+    border-left-width: inherit;
+
+    border-right-color: inherit;
+    border-right-style: inherit;
+    border-right-width: inherit;
+
+    border-top-color: inherit;
+    border-top-style: inherit;
+    border-top-width: inherit;
+
+    color: inherit;
+    display: inherit;
+
+    margin-bottom: inherit;
+    margin-left: inherit;
+    margin-right: inherit;
+    margin-top: inherit;
+
+    padding-bottom: inherit;
+    padding-left: inherit;
+    padding-right: inherit;
+    padding-top: inherit;
+
+    font-family: inherit;
+    font-size: inherit;
+    font-style: inherit;
+    font-weight: inherit;
+}
+
+
+/* normal, non-expanded values */
+simple {
+    background-color: aqua;       /* 0, 255, 255 */
+
+    border-bottom-color: black;   /* 0, 0, 0 */
+    border-bottom-style: solid;
+    border-bottom-width: thin;    /* 1px */
+
+    border-left-color: blue;
+    border-left-style: dashed;
+    border-left-width: medium;    /* 3px */
+
+    border-right-color: fuchsia;  /* 255, 0, 255 */
+    border-right-style: dotted;
+    border-right-width: thick;    /* 5px */
+
+    border-top-color: gray;
+    border-top-style: double;
+    border-top-width: 1px;
+
+    color: green;                 /* 0, 128, 0 */
+    display: block;
+
+    margin-bottom: 100cm;         /* 3543px */
+    margin-left: 0;               /* 0 */
+    margin-right: 10em;           /* 120px */
+    margin-top: 10ex;             /* 72px */
+
+    padding-bottom: 5in;          /* 450px */
+    padding-left: 1200mm;         /* 4251px */  
+    padding-right: 8pc;           /* 120px */
+    padding-top: 15pt;            /* 18px */
+
+    font-family: helvetica;
+    font-size: 10pt;              /* 12.5px */
+    font-style: italic;
+    font-weight: bold;
+}
+
+expandBorder {
+  border: 2px solid red;
+}
+
+expandBorderTop {
+  border-top: 2px solid red;
+}
+
+expandBorderLeft {
+  border-left: 2px solid red;
+}
+
+expandBorderBottom {
+  border-bottom: 2px solid red;
+}
+
+expandBorderRight {
+  border-right: 2px solid red;
+}
+
+expandBorder1 {
+  border: 2px solid;
+}
+
+expandBorder2 {
+  border: 2px red;
+}
+
+expandBorder3 {
+  border: 2px;
+}
+
+expandBorder4 {
+  border: solid red;
+}
+
+expandBorder5 {
+  border: solid;
+}
+
+expandBorder6 {
+  border: red;
+}
+
+borderColor1 {
+  border-color: red;
+}
+
+borderColor2 {
+  border-color: red green;
+}
+
+borderColor3 {
+  border-color: red green blue;
+}
+
+borderColor4 {
+  border-color: red green blue white;
+}
+
+borderStyle1 {
+  border-style: solid;
+}
+
+borderStyle2 {
+  border-style: solid dotted;
+}
+
+borderStyle3 {
+  border-style: solid dotted dashed;
+}
+
+borderStyle4 {
+  border-style: solid dotted dashed outset;
+}
+
+borderWidth1 {
+  border-style: solid;
+  border-width: 1px;
+}
+
+borderWidth2 {
+  border-style: solid;
+  border-width: 1px 2px;
+}
+
+borderWidth3 {
+  border-style: solid;
+  border-width: 1px 2px 3px;
+}
+
+borderWidth4 {
+  border-style: solid;
+  border-width: 1px 2px 3px 4px;
+}
+
+margin1 {
+  margin: 10px;
+}
+
+margin2 {
+  margin: 10px 20px;
+}
+
+margin3 {
+  margin: 10px 20px 30px;
+}
+
+margin4 {
+  margin: 10px 20px 30px 40px;
+}
+
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/testImportant.css b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/testImportant.css
new file mode 100644
index 0000000..1618d16
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/testImportant.css
@@ -0,0 +1,13 @@
+
+a {
+  background-color: black;
+  color: white;
+  border-top-color: blue !important;
+}
+
+* {
+  background-color: white;
+  color: black !important;
+  border-top-color: red !important;
+}
+
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/testLexicalUnits.css b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/testLexicalUnits.css
new file mode 100644
index 0000000..bf927e1
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/testLexicalUnits.css
@@ -0,0 +1,20 @@
+/* Stylesheet for the testLexicalUnits() test case */
+
+A {
+  background-color: #000;
+  border-bottom: rgb(10%, 20%, 30%);
+  border-left: rgb(15, 25)
+  border-right: 10;
+  border-top: rgb(larry, curly, moe);
+  color: rgb(255, 255, 255);
+  font-name: "Times New Roman", serif;
+  font-size: 10px;
+  font-weight: 400;
+  font-style: italic;
+}
+
+B {
+  background-color: foo;
+  color: 10px;
+}
+
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/testRules.css b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/testRules.css
new file mode 100644
index 0000000..a1c4ea4
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/css/testRules.css
@@ -0,0 +1,26 @@
+/*  0 */ c { }
+/*  1 */ b c { }
+/*  2 */ b d { }
+/*  3 */ other b c { }
+/*  4 */ other b d { }
+/*  5 */ a c e { }
+/*  6 */ c a e { }
+/*  7 */ * { }
+/*  8 */ *[color] { }
+/*  9 */ a[color] { }
+/* 10 */ b[color] { }
+/* 11 */ c[color] { }
+/* 12 */ d[color] { }
+/* 13 */ *[color=blue] { }
+/* 14 */ a[color=blue] { }
+/* 15 */ b[color=blue] { }
+/* 16 */ b[color='blue'] { }
+/* 17 */ b[color="blue"] { }
+/* 18 */ c[color=blue] { }
+/* 19 */ a * { }
+/* 20 */ a > * { }
+/* 21 */ a *[color] { }
+/* 22 */ a > *[color] { }
+/* 23 */ *[color~=blue] { }
+/* 24 */ .foo { }
+/* 25 */ .foo.bar { }
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/BlockElementBoxTest.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/BlockElementBoxTest.java
new file mode 100644
index 0000000..8ae6e37
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/BlockElementBoxTest.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.dom;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import org.eclipse.wst.xml.vex.core.internal.core.Graphics;
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheet;
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheetReader;
+import org.eclipse.wst.xml.vex.core.internal.dom.Document;
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentReader;
+import org.eclipse.wst.xml.vex.core.internal.layout.BlockElementBox;
+import org.eclipse.wst.xml.vex.core.internal.layout.Box;
+import org.eclipse.wst.xml.vex.core.internal.layout.CssBoxFactory;
+import org.eclipse.wst.xml.vex.core.internal.layout.FakeGraphics;
+import org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext;
+
+import junit.framework.TestCase;
+
+public class BlockElementBoxTest extends TestCase {
+    
+    private Graphics g;
+    private LayoutContext context;
+    
+    public BlockElementBoxTest() throws Exception {
+        
+        StyleSheetReader ssReader = new StyleSheetReader();
+        StyleSheet ss = ssReader.read(this.getClass().getResource("test.css"));
+
+        this.g = new FakeGraphics();
+        
+        this.context = new LayoutContext();
+        this.context.setBoxFactory(new CssBoxFactory());
+        this.context.setGraphics(this.g);
+        this.context.setStyleSheet(ss);
+    
+    }
+    
+    public void testPositioning() throws Exception {
+        
+        String docString = "<root><small/><medium/><large/></root>";
+        DocumentReader docReader = new DocumentReader();
+        docReader.setDebugging(true);
+        Document doc = docReader.read(docString);
+        BlockElementBox box = new BlockElementBox(context, null, doc.getRootElement());
+        
+        Method createChildren = BlockElementBox.class.getDeclaredMethod("createChildren", new Class[] { LayoutContext.class });
+        createChildren.setAccessible(true);
+        createChildren.invoke(box, new Object[] { this.context });
+        
+        Box[] children = box.getChildren();
+        assertEquals(3, children.length);
+        assertEquals(1 + 10, this.getGap(box, 0));
+        assertEquals(30 + 3 + 300 + 2 + 20, this.getGap(box, 1));
+        assertEquals(60 + 6 + 600 + 3 + 30, this.getGap(box, 2));
+        assertEquals(90 + 9, this.getGap(box, 3));
+    }
+    
+    public int getGap(BlockElementBox box, int n) throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
+        Method getGap = BlockElementBox.class.getDeclaredMethod("getGap", new Class[] { Integer.TYPE });
+        getGap.setAccessible(true);
+        return ((Integer) getGap.invoke(box, new Object[] { new Integer(n) })).intValue();
+    }
+}
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/DFABuilderTest.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/DFABuilderTest.java
new file mode 100644
index 0000000..5a758df
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/DFABuilderTest.java
@@ -0,0 +1,276 @@
+/*******************************************************************************
+ * 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 org.eclipse.wst.xml.vex.core.internal.dom.*;
+
+import junit.framework.*;
+
+/**
+ * Test the <code>net.sf.vex.dom</code> package.
+ */
+public class DFABuilderTest extends TestCase {
+
+    public void testAll() {
+	DFAState dfa;
+
+	dfa = parseRegex("a");
+	assertTrue(matches(dfa, "a"));
+	assertFalse(matches(dfa, ""));
+	assertFalse(matches(dfa, "aa"));
+
+	dfa = parseRegex("a?");
+	assertTrue(matches(dfa, ""));
+	assertTrue(matches(dfa, "a"));
+	assertFalse(matches(dfa, "aa"));
+
+	dfa = parseRegex("a*");
+	assertTrue(matches(dfa, ""));
+	assertTrue(matches(dfa, "a"));
+	assertTrue(matches(dfa, "aa"));
+	assertTrue(matches(dfa, "aaa"));
+
+	dfa = parseRegex("a+");
+	assertFalse(matches(dfa, ""));
+	assertTrue(matches(dfa, "a"));
+	assertTrue(matches(dfa, "aa"));
+	assertTrue(matches(dfa, "aaa"));
+
+	dfa = parseRegex("ab");
+	assertFalse(matches(dfa, ""));
+	assertFalse(matches(dfa, "a"));
+	assertFalse(matches(dfa, "b"));
+	assertTrue(matches(dfa, "ab"));
+	assertFalse(matches(dfa, "aab"));
+	assertFalse(matches(dfa, "abb"));
+
+	dfa = parseRegex("a|b");
+	assertFalse(matches(dfa, ""));
+	assertTrue(matches(dfa, "a"));
+	assertTrue(matches(dfa, "b"));
+	assertFalse(matches(dfa, "ab"));
+	assertFalse(matches(dfa, "ba"));
+
+	dfa = parseRegex("a?b");
+	assertFalse(matches(dfa, ""));
+	assertFalse(matches(dfa, "a"));
+	assertTrue(matches(dfa, "b"));
+	assertTrue(matches(dfa, "ab"));
+	assertFalse(matches(dfa, "aa"));
+	assertFalse(matches(dfa, "bb"));
+	assertFalse(matches(dfa, "aab"));
+
+	dfa = parseRegex("a*b");
+	assertFalse(matches(dfa, ""));
+	assertFalse(matches(dfa, "a"));
+	assertTrue(matches(dfa, "b"));
+	assertTrue(matches(dfa, "ab"));
+	assertFalse(matches(dfa, "aa"));
+	assertFalse(matches(dfa, "bb"));
+	assertTrue(matches(dfa, "aab"));
+	assertTrue(matches(dfa, "aaab"));
+	assertFalse(matches(dfa, "aabb"));
+
+	dfa = parseRegex("a+b");
+	assertFalse(matches(dfa, ""));
+	assertFalse(matches(dfa, "a"));
+	assertFalse(matches(dfa, "b"));
+	assertTrue(matches(dfa, "ab"));
+	assertFalse(matches(dfa, "aa"));
+	assertFalse(matches(dfa, "bb"));
+	assertTrue(matches(dfa, "aab"));
+	assertTrue(matches(dfa, "aaab"));
+	assertFalse(matches(dfa, "aabb"));
+
+	dfa = parseRegex("(ts?)?p*");
+	assertTrue(matches(dfa, ""));
+	assertTrue(matches(dfa, "t"));
+	assertTrue(matches(dfa, "ts"));
+	assertTrue(matches(dfa, "tsppp"));
+	assertTrue(matches(dfa, "ppp"));
+	assertFalse(matches(dfa, "s"));
+	assertFalse(matches(dfa, "sp"));
+    }
+
+    /**
+     * Returns true if the given DFA matches the given string.
+     */
+    private boolean matches(DFAState state, String s) {
+	for (int i = 0; i < s.length(); i++) {
+	    String symbol = String.valueOf(s.charAt(i));
+	    state = state.getNextState(symbol);
+	    if (state == null) {
+		return false;
+	    }
+	}
+	return state.isAccepting();
+    }
+
+    /**
+     * Parses simple regex's into nodes.
+     */
+    private DFAState parseRegex(String s) {
+	ParseState ps = new ParseState();
+	ps.s = s; 
+	ps.pos = 0;
+	return DFABuilder.createDFA(parseRegex(ps));
+    }
+
+    private static class ParseState {
+	public String s;
+	public int pos;
+	public char getChar() {
+	    if (pos == s.length()) {
+		return 0;
+	    } else {
+		return s.charAt(pos++);
+	    }
+	}
+	public char peekChar() {
+	    if (pos == s.length()) {
+		return 0;
+	    } else {
+		return s.charAt(pos);
+	    }
+	}
+    }
+
+    //
+    // regex ::= null
+    //        |  part regex*
+    //        |  ')'
+    //
+    // part ::= repeating [ '|' part ]
+    //
+    // repeating ::= simplepart [ '?' | '*' | '+' ]
+    //
+    // simplepart ::= char
+    //             |  '(' regex
+    //
+    private DFABuilder.Node parseRegex(ParseState ps) {
+
+	if (ps.peekChar() == ')') {
+	    ps.getChar();
+	    return null;
+	}
+
+	DFABuilder.Node node = parsePart(ps);
+	if (node == null) {
+	    return node;
+	}
+
+	DFABuilder.Node node2 = parseRegex(ps);
+	if (node2 != null) {
+	    node =  DFABuilder.createSequenceNode(node, node2);
+	}
+
+	return node;
+    }
+
+    private DFABuilder.Node parsePart(ParseState ps) {
+	DFABuilder.Node node = parseRepeating(ps);
+	if (node == null) {
+	    return node;
+	}
+
+	if (ps.peekChar() == '|') {
+	    ps.getChar();
+	    DFABuilder.Node node2 = parsePart(ps);
+	    if (node2 != null) {
+		node = DFABuilder.createChoiceNode(node, node2);
+	    }
+	}
+
+	return node;
+    }
+
+
+    private DFABuilder.Node parseRepeating(ParseState ps) {
+
+	DFABuilder.Node node = parseSimplePart(ps);
+	if (node == null) {
+	    return node;
+	}
+
+	if (ps.peekChar() == '?') {
+	    ps.getChar();
+	    node = DFABuilder.createOptionalNode(node);
+	} else if (ps.peekChar() == '*') {
+	    ps.getChar();
+	    node = DFABuilder.createRepeatingNode(node, 0);
+	} else if (ps.peekChar() == '+') {
+	    ps.getChar();
+	    node = DFABuilder.createRepeatingNode(node, 1);
+	}
+
+	return node;
+    }
+
+    private DFABuilder.Node parseSimplePart(ParseState ps) {
+
+	DFABuilder.Node node;
+
+	char c = ps.getChar();
+
+	if (c == 0) {
+	    return null;
+	} else if (c == '(') {
+	    node = parseRegex(ps);
+	} else {
+	    node = DFABuilder.createSymbolNode(String.valueOf(c));
+	}
+
+	return node;
+    }
+
+    /*
+    private DFABuilder.Node parseRegexToNode(ParseState ps) {
+	DFABuilder.Node node = null;
+	while (ps.pos < ps.s.length()) {
+	    DFABuilder.Node nextNode;
+	    char c = ps.s.charAt(ps.pos);
+	    ps.pos++;
+	    if (c == ')') {
+		return node;
+	    } else if (c == '(') {
+		nextNode = parseRegexToNode(ps);
+	    } else if (c == '?' || c == '*' || c == '+') {
+		throw new RuntimeException("Misplaced '?': " + ps.s);
+	    } else {
+		nextNode = DFABuilder.createSymbolNode(String.valueOf(c));
+	    }
+
+	    if (ps.pos < ps.s.length()) {
+		char c2 = ps.s.charAt(ps.pos);
+		if (c2 == '?') {
+		    nextNode = DFABuilder.createOptionalNode(nextNode);
+		    ps.pos++;
+		} else if (c2 == '*') {
+		    nextNode = DFABuilder.createRepeatingNode(nextNode, 0);
+		    ps.pos++;
+		} else if (c2 == '+') {
+		    nextNode = DFABuilder.createRepeatingNode(nextNode, 1);
+		    ps.pos++;
+		}
+	    }
+
+	    if (node == null) {
+		node = nextNode;
+	    } else {
+		node = DFABuilder.createSequenceNode(node, nextNode);
+	    }
+	}
+
+	return node;
+    }
+    */
+}
+
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/DTDValidatorTest.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/DTDValidatorTest.java
new file mode 100644
index 0000000..3b7fcc9
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/DTDValidatorTest.java
@@ -0,0 +1,111 @@
+/*******************************************************************************
+ * 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.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.URL;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.eclipse.wst.xml.vex.core.internal.dom.AttributeDefinition;
+import org.eclipse.wst.xml.vex.core.internal.dom.DTDValidator;
+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.RootElement;
+import org.eclipse.wst.xml.vex.core.internal.dom.Validator;
+
+import junit.framework.TestCase;
+
+public class DTDValidatorTest extends TestCase {
+
+    public void testAll() throws Exception {
+	URL url = DTDValidatorTest.class.getResource("test1.dtd");
+	Validator validator = DTDValidator.create(url);
+
+	AttributeDefinition.Type adType = validator.getAttributeDefinitions("section")[0].getType();
+    
+	// Test serialization while we're at it
+	ByteArrayOutputStream baos = new ByteArrayOutputStream();
+	ObjectOutputStream oos = new ObjectOutputStream(baos);
+	oos.writeObject(validator);
+    
+	ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+	ObjectInputStream ois = new ObjectInputStream(bais);
+	validator = (Validator) ois.readObject();
+
+	AttributeDefinition.Type adType2 = validator.getAttributeDefinitions("section")[0].getType();
+    
+	assertSame(adType, adType2);
+    
+	Document doc;
+	Set expected;
+
+	doc = new Document(new RootElement("empty"));
+        doc.setValidator(validator);
+	assertEquals(Collections.EMPTY_SET, getValidItemsAt(doc, 1));
+
+	doc = new Document(new RootElement("any"));
+        doc.setValidator(validator);
+	Set anySet = new HashSet();
+	anySet.add(Validator.PCDATA);
+	anySet.add("any");
+	anySet.add("empty");
+	anySet.add("section");
+	anySet.add("title");
+	anySet.add("para");
+	anySet.add("emphasis");
+	assertEquals(anySet, getValidItemsAt(doc, 1));
+
+	// <section> <title> a b </title> <para> </para> </section>
+	//          1       2 3 4        5      6       7
+	doc = new Document(new RootElement("section"));
+        doc.setValidator(validator);
+	doc.insertElement(1, new Element("title"));
+	doc.insertText(2, "ab");
+	doc.insertElement(5, new Element("para"));
+
+	assertEquals(Collections.EMPTY_SET, getValidItemsAt(doc, 1));
+	expected = Collections.singleton(Validator.PCDATA);
+	assertEquals(expected, getValidItemsAt(doc, 2));
+	assertEquals(expected, getValidItemsAt(doc, 3));
+	assertEquals(expected, getValidItemsAt(doc, 4));
+	expected = Collections.singleton("para");
+	assertEquals(expected, getValidItemsAt(doc, 5));
+	assertEquals(expected, getValidItemsAt(doc, 7));
+	expected = new HashSet();
+	expected.add(Validator.PCDATA);
+	expected.add("emphasis");
+	assertEquals(expected, getValidItemsAt(doc, 6));
+
+    }
+
+    private Set getValidItemsAt(Document doc, int offset) {
+        Element element = doc.getElementAt(offset);
+        String[] prefix = doc.getNodeNames(element.getStartOffset() + 1, offset);
+        String[] suffix = doc.getNodeNames(offset, element.getEndOffset());
+        return doc.getValidator().getValidItems(element.getName(), prefix, suffix);
+    }
+    /*
+    private void dump(Validator validator, Document doc, int offset) {
+	Set set = getValidItemsAt(doc, offset);
+	
+	Iterator iter = set.iterator();
+	while (iter.hasNext()) {
+	    System.out.println("  " + iter.next());
+	}
+    }
+    */
+}
+
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentWriterTest.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentWriterTest.java
new file mode 100644
index 0000000..aa2b180
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentWriterTest.java
@@ -0,0 +1,189 @@
+/*******************************************************************************
+ * 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.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Arrays;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParserFactory;
+import junit.framework.TestCase;
+
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheet;
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheetReader;
+import org.eclipse.wst.xml.vex.core.internal.dom.Document;
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentWriter;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.dom.IWhitespacePolicy;
+import org.eclipse.wst.xml.vex.core.internal.dom.IWhitespacePolicyFactory;
+import org.eclipse.wst.xml.vex.core.internal.dom.Node;
+import org.eclipse.wst.xml.vex.core.internal.widget.CssWhitespacePolicy;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * Test the DocumentWriterImpl class.
+ */
+public class DocumentWriterTest extends TestCase {
+
+    public void testWriteDocument() throws Exception {
+
+        StyleSheetReader reader = new StyleSheetReader();
+        StyleSheet ss = reader.read(this.getClass().getResource("test.css"));
+
+        URL docUrl = this.getClass().getResource("DocumentWriterTest1.xml");
+
+        Document docOrig = readDocument(new InputSource(docUrl.toString()), ss);
+
+        DocumentWriter dw = new DocumentWriter();
+        dw.setWhitespacePolicy(new CssWhitespacePolicy(ss));
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+        dw.write(docOrig, os);
+
+        InputStream is = new ByteArrayInputStream(os.toByteArray());
+
+        // Dump document to console
+//        BufferedReader br = new BufferedReader(new InputStreamReader(is));
+//        while (true) {
+//            String s = br.readLine();
+//            if (s == null)
+//                break;
+//            System.out.println(s);
+//        }
+//        is.reset();
+        
+        Document docNew = readDocument(new InputSource(is), ss);
+
+        assertEquals(docOrig, docNew);
+    }
+
+    private void assertEquals(Document expected, Document actual)
+        throws Exception {
+
+        assertEquals(expected.getRootElement(), actual.getRootElement());
+    }
+
+    private void assertEquals(Element expected, Element actual)
+        throws Exception {
+
+        System.out.println("Checking " + actual.getName());
+        assertEquals(expected.getName(), actual.getName());
+
+        String[] expectedAttrs = expected.getAttributeNames();
+        Arrays.sort(expectedAttrs);
+
+        String[] actualAttrs = actual.getAttributeNames();
+        Arrays.sort(actualAttrs);
+
+        assertEquals(expectedAttrs.length, actualAttrs.length);
+        for (int i = 0; i < expectedAttrs.length; i++) {
+            assertEquals(expectedAttrs[i], actualAttrs[i]);
+        }
+
+        Node[] expectedContent = expected.getChildNodes();
+        Node[] actualContent = actual.getChildNodes();
+        assertEquals(expectedContent.length, actualContent.length);
+        for (int i = 0; i < expectedContent.length; i++) {
+            assertEquals(
+                expectedContent[i].getClass(),
+                actualContent[i].getClass());
+            if (expectedContent[i] instanceof Element) {
+                assertEquals(
+                    (Element) expectedContent[i],
+                    (Element) actualContent[i]);
+            } else {
+                assertEquals(
+                    expectedContent[i].getText(),
+                    actualContent[i].getText());
+            }
+        }
+    }
+
+    private static Document readDocument(InputSource is, StyleSheet ss)
+        throws ParserConfigurationException, SAXException, IOException {
+
+        SAXParserFactory factory = SAXParserFactory.newInstance();
+        XMLReader xmlReader = factory.newSAXParser().getXMLReader();
+        DefaultHandler defaultHandler = new DefaultHandler();
+        
+        final IWhitespacePolicy policy = new CssWhitespacePolicy(ss);
+            
+        IWhitespacePolicyFactory wsFactory = new IWhitespacePolicyFactory() {
+            public IWhitespacePolicy getPolicy(String publicId) {
+                return policy; 
+            }
+        };
+        
+        org.eclipse.wst.xml.vex.core.internal.dom.DocumentBuilder builder =
+            new org.eclipse.wst.xml.vex.core.internal.dom.DocumentBuilder(wsFactory);
+
+        xmlReader.setContentHandler(builder);
+        xmlReader.setDTDHandler(defaultHandler);
+        xmlReader.setEntityResolver(defaultHandler);
+        xmlReader.setErrorHandler(defaultHandler);
+        xmlReader.parse(is);
+        return builder.getDocument();
+    }
+
+    /**
+     * Which elements are block elements for the purposes of our test cases.
+     */
+    /*
+    private static boolean isBlockElement(Element element) {
+        return element.getName().equals("html")
+            || element.getName().equals("body")
+            || element.getName().equals("p");
+    }
+*/
+    /**
+     * Parse the given document and pass them through to stdout to confirm
+     * their goodness.
+     */
+    public static void main(String[] args) {
+        if (args.length < 2) {
+            System.out.println("Usage: java DocumentWriterTest filename width");
+            System.exit(1);
+        }
+
+        FileInputStream fis = null;
+        try {
+            fis = new FileInputStream(args[0]);
+            int width = Integer.parseInt(args[1]);
+            StyleSheetReader reader = new StyleSheetReader();
+            StyleSheet ss =
+                reader.read(DocumentWriterTest.class.getResource("test.css"));
+            Document doc = readDocument(new InputSource(fis), ss);
+
+            DocumentWriter writer = new DocumentWriter();
+            writer.setWhitespacePolicy(new CssWhitespacePolicy(ss));
+            writer.setWrapColumn(width);
+
+            writer.write(doc, System.out);
+        } catch (Exception ex) {
+            ex.printStackTrace();
+            System.exit(1);
+        } finally {
+            if (fis != null) {
+                try {
+                    fis.close();
+                } catch (IOException ex) {
+                }
+                fis = null;
+            }
+        }
+    }
+}
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentWriterTest1.xml b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentWriterTest1.xml
new file mode 100644
index 0000000..4b1428b
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/DocumentWriterTest1.xml
@@ -0,0 +1,11 @@
+<html>
+  <body style="font: 10pt 'Times New Roman', serif;" foo="bar none" xml:lang="en">
+  
+    <p>This should be a complex document with some <a href="here is a
+    URL">elements with attributes</a>. The more complex, the more
+    thorough the check will be.</p>
+    
+    <img src="http://myhost.com/really/long/path/to/a/jpeg.jpg" alt="&lt;pickin &amp; grinnin&gt;"/>
+    
+  </body>
+</html>
\ No newline at end of file
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/DomTest.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/DomTest.java
new file mode 100644
index 0000000..b11dfee
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/DomTest.java
@@ -0,0 +1,349 @@
+/*******************************************************************************
+ * 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 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.dom.Node;
+import org.eclipse.wst.xml.vex.core.internal.dom.RootElement;
+import org.eclipse.wst.xml.vex.core.internal.dom.Text;
+
+import junit.framework.TestCase;
+
+/**
+ * Test the <code>net.sf.vex.dom</code> package.
+ */
+public class DomTest extends TestCase {
+
+    public void testDom() throws Exception {
+
+	//
+	// Document initialisation
+	//
+	RootElement root = new RootElement("article");
+	Document doc = new Document(root);
+	Node[] content;
+	Element[] children;
+
+	//              root
+	//              | |
+	//               * *
+
+	assertEquals(2, doc.getLength());
+	assertEquals(root, doc.getRootElement());
+	assertEquals(0, root.getStartOffset());
+	assertEquals(1, root.getEndOffset());
+
+	content = root.getChildNodes();
+	assertEquals(0, content.length);
+	children = root.getChildElements();
+	assertEquals(0, children.length);
+
+	//               root
+	//              |     |
+	//               * a c *
+
+	try {
+	    doc.insertText(0, "ac");
+	    fail("Expected IllegalArgumentException");
+	} catch (IllegalArgumentException ex) {
+	}
+
+	
+	try {
+	    doc.insertText(2, "ac");
+	    fail("Expected IllegalArgumentException");
+	} catch (IllegalArgumentException ex) {
+	}
+
+	doc.insertText(1, "ac");
+	assertEquals(4, doc.getLength());
+	content = root.getChildNodes();
+	assertEquals(1, content.length);
+	assertIsText(content[0], "ac", 1, 3);
+	assertEquals(1, content[0].getStartPosition().getOffset());
+	assertEquals(3, content[0].getEndPosition().getOffset());
+	assertEquals(0, root.getStartOffset());
+	assertEquals(3, root.getEndOffset());
+
+	//
+	// Try inserting at illegal offset
+	//
+	Element element = new Element("b");
+	
+	try {
+	    doc.insertElement(0, element);
+	    fail("Expected IllegalArgumentException");
+	} catch (IllegalArgumentException ex) {
+	}
+
+	
+	try {
+	    doc.insertElement(4, element);
+	    fail("Expected IllegalArgumentException");
+	} catch (IllegalArgumentException ex) {
+	}
+
+	//                 root
+	//              |         |
+	//              |    z    |
+	//              |   | |   |
+	//               * a * * c *
+	//              0 1 2 3 4 5 6
+	//
+	doc.insertElement(2, element);
+	assertEquals(root, element.getParent());
+	assertEquals(6, doc.getLength());
+
+	Element element2 = new Element("x");
+	doc.insertElement(2, element2);
+
+	content = root.getChildNodes();
+	assertEquals(4, content.length);
+	assertIsText(content[0], "a", 1, 2);
+	assertIsElement(content[1], "x", root, 2, 3);
+	assertIsElement(content[2], "b", root, 4, 5);
+	assertIsText(content[3], "c", 6, 7);
+
+    }
+    
+    public void testFragments() throws Exception {
+        
+        Document doc;
+        DocumentFragment frag;
+        Element[] elements;
+        Node[] nodes;
+        Element root;
+        Element x;
+        Element y;
+        Element z;
+        
+        // Case 1: just text
+        //
+        //          root
+        //        * a b c *
+        //       0 1 2 3 4 5
+        doc = new Document(new RootElement("root"));
+        doc.insertText(1, "abc");
+        
+        try {
+            frag = doc.getFragment(2, 2);
+            fail();
+        } catch (IllegalArgumentException ex) {
+        }
+        
+        try {
+            frag = doc.getFragment(-1, 0);
+            fail();
+        } catch (IllegalArgumentException ex) {
+        }
+        
+        try {
+            frag = doc.getFragment(4, 5);
+            fail();
+        } catch (IllegalArgumentException ex) {
+        }
+        
+        frag = doc.getFragment(2, 3);
+        assertEquals(1, frag.getContent().getLength());
+        assertEquals(0, frag.getElements().length);
+        nodes = frag.getNodes();
+        assertEquals(1, nodes.length);
+        this.assertIsText(nodes[0], "b", 0, 1);
+
+        // Case 2: single element, no children
+        //        
+        //                   root
+        //              |           |
+        //              |     z     |
+        //              |   |   |   |
+        //               * a * b * c *
+        //              0 1 2 3 4 5 6 7
+
+        //                  z  
+        //                |   |
+        //               a * b * c
+        //              0 1 2 3 4 5
+
+        doc = new Document(new RootElement("root"));
+        doc.insertText(1, "ac");
+        doc.insertElement(2, new Element("z"));
+        doc.insertText(3, "b");
+
+        frag = doc.getFragment(1, 6);
+        elements = frag.getElements();
+        assertEquals(1, elements.length);
+        this.assertIsElement(elements[0], "z", null, 1, 3);
+        nodes = frag.getNodes();
+        assertEquals(3, nodes.length);
+        assertIsText(nodes[0], "a", 0, 1);
+        assertIsElement(nodes[1], "z", null, 1, 3);
+        assertIsText(nodes[2], "c", 4, 5);
+        nodes = elements[0].getChildNodes();
+        assertEquals(1, nodes.length);
+        assertIsText(nodes[0], "b", 2, 3);
+        
+        // Case 3: complex with child elements
+        //        
+        //                            root
+        //              |                               |
+        //              |               z               |
+        //              |   |                       |   |
+        //              |   |     | x |   | y |     |   | 
+        //               * a * b c * d * e * f * g h * i *
+        //              0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7
+        //  3a:                 |<------frag----->|
+        //  3b:             |<--------frag----------->|
+
+        
+
+        doc = new Document(new RootElement("root"));
+        doc.insertText(1, "ai");
+        doc.insertElement(2, new Element("z"));
+        doc.insertText(3, "bcgh");
+        doc.insertElement(5, new Element("x"));
+        doc.insertText(6, "d");
+        doc.insertText(8, "e");
+        doc.insertElement(9, new Element("y"));
+        doc.insertText(10, "f");
+        
+        //  3a:
+        //                | x |   | y | 
+        //               c * d * e * f * g 
+        //              0 1 2 3 4 5 6 7 8 9
+        frag = doc.getFragment(4, 13);
+        assertEquals(9, frag.getContent().getLength());
+        
+        elements = frag.getElements();
+        assertEquals(2, elements.length);
+        assertIsElement(elements[0], "x", null, 1, 3);
+        assertIsElement(elements[1], "y", null, 5, 7);
+
+        nodes = frag.getNodes();
+        assertEquals(5, nodes.length);
+        assertIsText(nodes[0], "c", 0, 1);
+        assertIsElement(nodes[1], "x", null, 1, 3);
+        assertIsText(nodes[2], "e", 4, 5);
+        assertIsElement(nodes[3], "y", null, 5, 7);
+        assertIsText(nodes[4], "g", 8, 9);
+        
+        //  3b:
+        //                          z
+        //              |                       |
+        //              |     | x |   | y |     | 
+        //               * b c * d * e * f * g h * 
+        //              0 1 2 3 4 5 6 7 8 9 0 1 2 3
+        frag = doc.getFragment(2, 15);
+        assertEquals(13, frag.getContent().getLength());
+        
+        elements = frag.getElements();
+        assertEquals(1, elements.length);
+        assertIsElement(elements[0], "z", null, 0, 12);
+        
+        nodes = frag.getNodes();
+        assertEquals(1, nodes.length);
+        assertIsElement(nodes[0], "z", null, 0, 12);
+        
+        z = elements[0];
+        nodes = z.getChildNodes();
+        assertEquals(5, nodes.length);
+        assertIsText(nodes[0], "bc", 1, 3);
+        assertIsElement(nodes[1], "x", z, 3, 5);
+        assertIsText(nodes[2], "e", 6, 7);
+        assertIsElement(nodes[3], "y", z, 7, 9);
+        assertIsText(nodes[4], "gh", 10, 12);
+        
+        // 3c: remove and re-insert the same frag as in 3a
+        frag = doc.getFragment(4, 13);
+        doc.delete(4, 13);
+        doc.insertFragment(4, frag);
+        
+        root = doc.getRootElement();
+        assertIsElement(root, "root", null, 0, 16);
+        nodes = root.getChildNodes();
+        assertEquals(3, nodes.length);
+        assertIsText(nodes[0], "a", 1, 2);
+        assertIsElement(nodes[1], "z", doc.getRootElement(), 2, 14);
+        assertIsText(nodes[2], "i", 15, 16);
+        z = (Element) nodes[1];
+        nodes = z.getChildNodes();
+        assertEquals(5, nodes.length);
+        assertIsText(nodes[0], "bc", 3, 5);
+        assertIsElement(nodes[1], "x", z, 5, 7);
+        assertIsText(nodes[2], "e", 8, 9);
+        assertIsElement(nodes[3], "y", z, 9, 11);
+        assertIsText(nodes[4], "gh", 12, 14);
+        x = (Element) nodes[1];
+        y = (Element) nodes[3];
+        nodes = x.getChildNodes();
+        assertEquals(1, nodes.length);
+        assertIsText(nodes[0], "d", 6, 7);
+        nodes = y.getChildNodes();
+        assertEquals(1, nodes.length);
+        assertIsText(nodes[0], "f", 10, 11);
+        
+        // 3d: remove and re-insert the same frag as in 3b
+        frag = doc.getFragment(2, 15);
+        doc.delete(2, 15);
+        doc.insertFragment(2, frag);
+        
+        root = doc.getRootElement();
+        assertIsElement(root, "root", null, 0, 16);
+        nodes = root.getChildNodes();
+        assertEquals(3, nodes.length);
+        assertIsText(nodes[0], "a", 1, 2);
+        assertIsElement(nodes[1], "z", doc.getRootElement(), 2, 14);
+        assertIsText(nodes[2], "i", 15, 16);
+        z = (Element) nodes[1];
+        nodes = z.getChildNodes();
+        assertEquals(5, nodes.length);
+        assertIsText(nodes[0], "bc", 3, 5);
+        assertIsElement(nodes[1], "x", z, 5, 7);
+        assertIsText(nodes[2], "e", 8, 9);
+        assertIsElement(nodes[3], "y", z, 9, 11);
+        assertIsText(nodes[4], "gh", 12, 14);
+        x = (Element) nodes[1];
+        y = (Element) nodes[3];
+        nodes = x.getChildNodes();
+        assertEquals(1, nodes.length);
+        assertIsText(nodes[0], "d", 6, 7);
+        nodes = y.getChildNodes();
+        assertEquals(1, nodes.length);
+        assertIsText(nodes[0], "f", 10, 11);
+        
+    }
+
+    public void assertIsElement(Node node, 
+				String name, 
+				Element parent,
+				int startOffset, 
+				int endOffset) {
+
+	assertTrue(node instanceof Element);
+	assertEquals(name, ((Element)node).getName());
+	assertEquals(parent, ((Element)node).getParent());
+	assertEquals(startOffset, node.getStartOffset());
+	assertEquals(endOffset, node.getEndOffset());
+    }
+
+    public void assertIsText(Node node, 
+			     String text, 
+			     int startOffset, 
+			     int endOffset) {
+
+	assertTrue(node instanceof Text);
+	assertEquals(text, node.getText());
+	assertEquals(startOffset, node.getStartOffset());
+	assertEquals(endOffset, node.getEndOffset());
+    }
+}
+
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/GapContentTest.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/GapContentTest.java
new file mode 100644
index 0000000..6363436
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/GapContentTest.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.dom;
+
+import org.eclipse.wst.xml.vex.core.internal.dom.GapContent;
+import org.eclipse.wst.xml.vex.core.internal.dom.Position;
+
+import junit.framework.TestCase;
+
+/**
+ * Test the GapContent class
+ */
+public class GapContentTest extends TestCase {
+
+    public void testGapContent() throws Exception {
+	//
+	//  a b  (gap) c d
+	// | | |        | |
+	// 0 1 2        3 4
+	//
+
+	GapContent content = new GapContent(2);
+	assertEquals(0, content.getLength());
+	content.insertString(0, "a");
+	assertEquals(1, content.getLength());
+	content.insertString(1, "d");
+	assertEquals(2, content.getLength());
+	content.insertString(1, "c");
+	assertEquals(3, content.getLength());
+	content.insertString(1, "b");
+	assertEquals(4, content.getLength());
+
+	Position pa = content.createPosition(0);
+	Position pb = content.createPosition(1);
+	Position pc = content.createPosition(2);
+	Position pd = content.createPosition(3);
+	Position pe = content.createPosition(4);
+
+	try {
+	    content.getString(-1, 1);
+	    fail("expected exception");
+	} catch (IllegalArgumentException ex) {
+	}
+
+	try {
+	    content.getString(4, 1);
+	    fail("expected exception");
+	} catch (IllegalArgumentException ex) {
+	}
+
+	try {
+	    content.getString(0, -1);
+	    fail("expected exception");
+	} catch (IllegalArgumentException ex) {
+	}
+
+	try {
+	    content.getString(0, 5);
+	    fail("expected exception");
+	} catch (IllegalArgumentException ex) {
+	}
+
+	try {
+	    content.createPosition(-1);
+	    fail("expected exception");
+	} catch (IllegalArgumentException ex) {
+	}
+
+	try {
+	    content.createPosition(5);
+	    fail("expected exception");
+	} catch (IllegalArgumentException ex) {
+	}
+
+	assertEquals("a", content.getString(0, 1));
+	assertEquals("b", content.getString(1, 1));
+	assertEquals("c", content.getString(2, 1));
+	assertEquals("d", content.getString(3, 1));
+
+	assertEquals("ab", content.getString(0, 2));
+	assertEquals("bc", content.getString(1, 2));
+	assertEquals("cd", content.getString(2, 2));
+
+	assertEquals("abc", content.getString(0, 3));
+	assertEquals("bcd", content.getString(1, 3));
+
+	assertEquals("abcd", content.getString(0, 4));
+
+	//
+	//  a b x   (gap)   y c d
+	// | | | |           | | |
+	// 0 1 2 3           4 5 6
+	// 
+	content.insertString(2, "y");
+	assertEquals(5, content.getLength());
+	content.insertString(2, "x");
+	assertEquals(6, content.getLength());
+
+	assertEquals(0, pa.getOffset());
+	assertEquals(1, pb.getOffset());
+	assertEquals(4, pc.getOffset());
+	assertEquals(5, pd.getOffset());
+	assertEquals(6, pe.getOffset());
+
+	Position px = content.createPosition(2);
+	Position py = content.createPosition(3);
+
+	content.remove(2, 2);
+
+	assertEquals(4, content.getLength());
+
+	assertEquals(0, pa.getOffset());
+	assertEquals(1, pb.getOffset());
+	assertEquals(2, px.getOffset());
+	assertEquals(2, py.getOffset());
+	assertEquals(2, pc.getOffset());
+	assertEquals(3, pd.getOffset());
+	assertEquals(4, pe.getOffset());
+	
+	assertEquals("a", content.getString(0, 1));
+	assertEquals("b", content.getString(1, 1));
+	assertEquals("c", content.getString(2, 1));
+	assertEquals("d", content.getString(3, 1));
+
+	assertEquals("ab", content.getString(0, 2));
+	assertEquals("bc", content.getString(1, 2));
+	assertEquals("cd", content.getString(2, 2));
+
+	assertEquals("abc", content.getString(0, 3));
+	assertEquals("bcd", content.getString(1, 3));
+
+	assertEquals("abcd", content.getString(0, 4));
+
+    }
+}
+
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/SpaceNormalizerTest.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/SpaceNormalizerTest.java
new file mode 100644
index 0000000..5511862
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/SpaceNormalizerTest.java
@@ -0,0 +1,188 @@
+/*******************************************************************************
+ * 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.ByteArrayInputStream;
+import java.io.IOException;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParserFactory;
+import junit.framework.TestCase;
+
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheet;
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheetReader;
+import org.eclipse.wst.xml.vex.core.internal.dom.Document;
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentBuilder;
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.dom.IWhitespacePolicy;
+import org.eclipse.wst.xml.vex.core.internal.dom.IWhitespacePolicyFactory;
+import org.eclipse.wst.xml.vex.core.internal.dom.Node;
+import org.eclipse.wst.xml.vex.core.internal.dom.Text;
+import org.eclipse.wst.xml.vex.core.internal.widget.CssWhitespacePolicy;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+
+/**
+ * Test the SpaceNormalizer class.
+ */
+public class SpaceNormalizerTest extends TestCase {
+
+    /**
+     * Test the normalize method. Test cases are as follows.
+     *
+     * <ul>
+     * <li>leading w/s trimmed</li>
+     * <li>trailing w/s trimmed</li>
+     * <li>internal w/s collapsed to a single space</li>
+     * <li>internal w/s before and after an inline child element collapsed 
+     *     to a single space.</li>
+     * <li>internal w/s before and after a block child element removed.</li>
+     * <li>spaces between blocks eliminated.</li>
+     * <li>no extraneous spaces before or after elements added</li>
+     * </ul>
+     */
+    public void testNormalize() throws Exception {
+        
+	String input = "<doc>\n\t  " +
+	    "<block>\n\t foo\n\t <inline>foo\n\t bar</inline>\n\t baz\n\t </block>\n\t " +
+	    "<block>\n\t foo\n\t <block>bar</block>\n\t baz</block>" +
+	    "<block>\n\t foo<inline> foo bar </inline>baz \n\t </block>" +
+	    "<block>\n\t foo<block>bar</block>baz \n\t</block>" +
+	    "\n\t </doc>";
+
+        StyleSheetReader reader = new StyleSheetReader();
+        StyleSheet ss = reader.read(this.getClass().getResource("test.css"));
+
+	Document doc = createDocument(input, ss);
+
+	//SpaceNormalizer norm = new SpaceNormalizer(ss);
+	//norm.normalize(doc);
+
+	Element element;
+
+	element = doc.getRootElement();
+	assertContent(element, new String[] { "<block>", 
+					      "<block>", 
+					      "<block>", 
+					      "<block>" });
+
+	Element[] children = element.getChildElements();
+
+	//--- Block 0 ---
+
+	assertContent(children[0], new String[] { "foo ",
+						  "<inline>",
+						  " baz" });
+	Element[] c2 = children[0].getChildElements();
+	assertContent(c2[0], new String[] { "foo bar" });
+
+	//--- Block 1 ---
+
+	assertContent(children[1], new String[] { "foo",
+						  "<block>",
+						  "baz" });
+	c2 = children[1].getChildElements();
+	assertContent(c2[0], new String[] { "bar" });
+
+	//--- Block 2 ---
+
+	assertContent(children[2], new String[] { "foo",
+						  "<inline>",
+						  "baz" });
+	c2 = children[2].getChildElements();
+	assertContent(c2[0], new String[] { "foo bar" });
+
+	//--- Block 3 ---
+
+	assertContent(children[3], new String[] { "foo",
+						  "<block>",
+						  "baz" });
+	c2 = children[3].getChildElements();
+	assertContent(c2[0], new String[] { "bar" });
+
+    
+	//========= Now test with a PRE element =========
+    
+	input = "<doc>\n\t  " +
+	"<pre>\n\t foo\n\t <inline>\n\t foo\n\t bar\n \t</inline>\n\t baz\n\t </pre>\n\t " +
+	"\n\t </doc>";
+
+	doc = createDocument(input, ss);
+
+	element = doc.getRootElement();
+	assertContent(element, new String[] { "<pre>" });
+    
+	Element pre = element.getChildElements()[0];
+	assertContent(pre, new String[] {
+	        "\n\t foo\n\t ", "<inline>", "\n\t baz\n\t "
+	});
+    
+	Element inline = pre.getChildElements()[0];
+	assertContent(inline, new String[] { "\n\t foo\n\t bar\n \t" });
+    
+    }
+
+
+    //========================================================= PRIVATE
+
+//    private static final String DTD = "<!ELEMENT doc ANY>";
+
+    /**
+     * Asserts the content of the given element matches the given
+     * list. If a string in content is enclosed in angle brackets,
+     * it's assume to refer to the name of an element; otherwise, it
+     * represents text content.
+     */
+    private void assertContent(Element element, String[] strings) {
+	Node[] content = element.getChildNodes();
+	assertEquals(strings.length, content.length);
+	for (int i = 0; i < strings.length; i++) {
+	    if (strings[i].startsWith("<")) {
+		String name = strings[i].substring(1, strings[i].length() - 1);
+		assertTrue(content[i] instanceof Element);
+		assertEquals(name, ((Element)content[i]).getName());
+	    } else {
+		assertTrue(content[i] instanceof Text);
+		assertEquals(strings[i], content[i].getText());
+	    }
+	}
+    }
+
+    private Document createDocument(String s, StyleSheet ss) 
+	throws ParserConfigurationException, SAXException, IOException {
+
+	SAXParserFactory factory = SAXParserFactory.newInstance();
+	XMLReader xmlReader = factory.newSAXParser().getXMLReader();
+	final StyleSheet mySS = ss;
+	DocumentBuilder builder = new DocumentBuilder(new IWhitespacePolicyFactory() {
+	    
+	    public IWhitespacePolicy getPolicy(String publicId) {
+	        return new CssWhitespacePolicy(mySS);
+	    }
+	    
+	});
+
+	InputSource is = new InputSource(new ByteArrayInputStream(s.getBytes()));
+	xmlReader.setContentHandler(builder);
+	//xmlReader.setDTDHandler(defaultHandler);
+//	xmlReader.setEntityResolver(new EntityResolver() {
+//	    public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
+//	        System.out.println("resolveEntity called");
+//	        return new InputSource(new ByteArrayInputStream(DTD.getBytes()));
+//	    }
+//        });
+	//xmlReader.setErrorHandler(defaultHandler);
+	xmlReader.parse(is);
+	return builder.getDocument();
+    }
+
+}
+
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/TextWrapperTest.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/TextWrapperTest.java
new file mode 100644
index 0000000..0d9ae11
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/TextWrapperTest.java
@@ -0,0 +1,109 @@
+/*******************************************************************************
+ * 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 org.eclipse.wst.xml.vex.core.internal.dom.TextWrapper;
+
+import junit.framework.TestCase;
+
+/**
+ * Test the TextWrapper class.
+ */
+public class TextWrapperTest extends TestCase {
+
+    public void testWrap() {
+	String[] results;
+	String[] inputs;
+	TextWrapper wrapper = new TextWrapper();
+
+	results = wrapper.wrap(40);
+	assertEquals(0, results.length);
+
+	inputs = new String[] {
+	    "Here ",
+	    "are ", 
+	    "some ",
+	    "short ",
+	    "words ",
+	    "and here are some long ones. We make sure we have some short stuff and some long stuff, just to make sure it all wraps." };
+
+	for (int i = 0; i < inputs.length; i++) {
+	    wrapper.add(inputs[i]);
+	}
+	results = wrapper.wrap(40);
+	assertWidth(results, 40);
+	assertPreserved(inputs, results);
+
+	wrapper.clear();
+	results = wrapper.wrap(40);
+	assertEquals(0, results.length);
+
+	String s1 = "yabba ";
+	String s3 = "yabba yabba yabba ";
+	wrapper.add(s1);
+	wrapper.addNoSplit(s3);
+	wrapper.addNoSplit(s3);
+	wrapper.add(s1);
+	results = wrapper.wrap(18);
+	assertEquals(4, results.length);
+	assertEquals(s1, results[0]);
+	assertEquals(s3, results[1]);
+	assertEquals(s3, results[2]);
+	assertEquals(s1, results[3]);
+    }
+
+    /**
+     * Ensure the two string arrays represent the same run of text
+     * after all elements are concatenated.
+     */
+    private void assertPreserved(String[] inputs, String[] results) {
+	StringBuffer inputSB = new StringBuffer();
+	StringBuffer resultSB = new StringBuffer();
+	for (int i = 0; i < inputs.length; i++) {
+	    inputSB.append(inputs[i]);
+	}
+	for (int i = 0; i < results.length; i++) {
+	    resultSB.append(results[i]);
+	}
+	assertEquals(inputSB.toString(), resultSB.toString());
+    }
+
+    /**
+     * Ensure all lines fit within the given width, and that adding an
+     * extra token from the next line would blow it.
+     */
+    private void assertWidth(String[] results, int width) {
+	for (int i = 0; i < results.length; i++) {
+	    assertTrue(results[i].length() > 0);
+	    assertTrue(results[i].length() <= width);
+	    if (i < results.length-1) {
+		assertTrue(results[i].length() 
+			   + getToken(results[i+1]).length() > width);
+	    }
+	}
+    }
+
+    /**
+     * Get a token from a string.
+     */
+    private String getToken(String s) {
+	int i = 0;
+	while (i < s.length() && !Character.isWhitespace(s.charAt(i))) {
+	    i++;
+	}
+	while (i < s.length() && Character.isWhitespace(s.charAt(i))) {
+	    i++;
+	}
+	return s.substring(0, i);
+    }
+
+}
+
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/test.css b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/test.css
new file mode 100644
index 0000000..9a83031
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/test.css
@@ -0,0 +1,53 @@
+
+html {
+  display: block;
+}
+
+body {
+  display: block;
+}
+
+p {
+  display: block;
+}
+
+block {
+  display: block;
+}
+
+pre {
+  display: block;
+  white-space: pre;
+}
+
+
+/* Styles for positioning tests */
+root {
+  border: 3px 7px 11px 13px;
+  padding: 17px 19px 23px 29px;
+}
+
+small {
+  border-width: 1px 2px 3px 4px;
+  border-style: solid;
+  margin: 100px 200px 300px 400px;
+  padding: 10px 20px 30px 40px;
+  display: block;
+}
+
+medium {
+  border-width: 2px 4px 6px 8px;
+  border-style: solid;
+  margin: 200px 400px 600px 800px;
+  padding: 20px 40px 60px 80px;
+  display: block;
+}
+
+large {
+  border-width: 3px 6px 9px 12px;
+  border-style: solid;
+  margin: 300px 600px 900px 1200px;
+  padding: 30px 60px 90px 120px;
+  display: block;
+}
+
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/test1.dtd b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/test1.dtd
new file mode 100644
index 0000000..f246a88
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/dom/test1.dtd
@@ -0,0 +1,10 @@
+<!ELEMENT any ANY>
+<!ELEMENT empty EMPTY>
+<!ELEMENT section (title?, para+)>
+<!ELEMENT para (#PCDATA | emphasis)*>
+<!ELEMENT title (#PCDATA)>
+<!ELEMENT emphasis (#PCDATA)>
+
+<!-- a dummy attribute, just to make sure attribute def serialization is OK -->
+<!ATTLIST section
+	name	CDATA	#IMPLIED>
\ No newline at end of file
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/FakeGraphics.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/FakeGraphics.java
new file mode 100644
index 0000000..a235c7a
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/FakeGraphics.java
@@ -0,0 +1,166 @@
+/*******************************************************************************
+ * 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.Color;
+import org.eclipse.wst.xml.vex.core.internal.core.ColorResource;
+import org.eclipse.wst.xml.vex.core.internal.core.DisplayDevice;
+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.FontSpec;
+import org.eclipse.wst.xml.vex.core.internal.core.Graphics;
+import org.eclipse.wst.xml.vex.core.internal.core.Rectangle;
+
+/**
+ * A pseudo-Graphics class that returns a known set of font metrics.
+ */
+public class FakeGraphics implements Graphics {
+
+    private int charWidth = 6;
+    
+    public FakeGraphics() {
+        DisplayDevice.setCurrent(new DisplayDevice() {
+            public int getHorizontalPPI() {
+                return 72;
+            }
+            public int getVerticalPPI() {
+                return 72;
+            }
+        });
+    }
+
+    private FontMetrics fontMetrics = new FontMetrics() {
+        public int getAscent() {
+            return 10;
+        }
+        public int getDescent() {
+            return 3;
+        }
+        public int getHeight() {
+            return 13;
+        }
+        public int getLeading() {
+            return 2;
+        }
+    };
+    
+    public int charsWidth(char[] data, int offset, int length) {
+        return length * charWidth;
+    }
+
+    public ColorResource createColor(Color rgb) {
+        return new ColorResource() {
+            public void dispose() {
+            }
+        };
+    }
+
+    public FontResource createFont(FontSpec fontSpec) {
+        return new FontResource() {
+            public void dispose() {
+            }
+        };
+    }
+
+    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) {
+    }
+
+    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() {
+        return null;
+    }
+
+    public ColorResource getBackgroundColor() {
+        return null;
+    }
+
+    public ColorResource getColor() {
+        return null;
+    }
+
+    public FontResource getFont() {
+        return null;
+    }
+
+    public int getLineStyle() {
+        return 0;
+    }
+
+    public int getLineWidth() {
+        return 0;
+    }
+
+    public ColorResource getSystemColor(int id) {
+        return null;
+    }
+
+    public FontMetrics getFontMetrics() {
+        return this.fontMetrics;
+    }
+
+    public boolean isAntiAliased() {
+        return false;
+    }
+
+    public void setAntiAliased(boolean antiAliased) {
+    }
+
+    public ColorResource setBackgroundColor(ColorResource color) {
+        return null;
+    }
+
+    public ColorResource setColor(ColorResource color) {
+        return null;
+    }
+
+    public FontResource setFont(FontResource font) {
+        return null;
+    }
+
+    public void setLineStyle(int style) {
+    }
+
+    public void setLineWidth(int width) {
+    }
+
+    public int stringWidth(String s) {
+        return charWidth * s.length();
+    }
+
+    public int getCharWidth() {
+        return this.charWidth;
+    }
+
+    public void setXORMode(boolean xorMode) {
+        // TODO Auto-generated method stub
+        
+    }
+}
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/LayoutTest.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/LayoutTest.java
new file mode 100644
index 0000000..4daa71c
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/LayoutTest.java
@@ -0,0 +1,259 @@
+/*******************************************************************************
+ * 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.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Stack;
+
+import javax.xml.parsers.FactoryConfigurationError;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParserFactory;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheet;
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheetReader;
+import org.eclipse.wst.xml.vex.core.internal.dom.Document;
+import org.eclipse.wst.xml.vex.core.internal.dom.DocumentReader;
+import org.eclipse.wst.xml.vex.core.internal.dom.IWhitespacePolicy;
+import org.eclipse.wst.xml.vex.core.internal.dom.IWhitespacePolicyFactory;
+import org.eclipse.wst.xml.vex.core.internal.layout.Box;
+import org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext;
+import org.eclipse.wst.xml.vex.core.internal.layout.RootBox;
+import org.eclipse.wst.xml.vex.core.internal.layout.TextBox;
+import org.eclipse.wst.xml.vex.core.internal.widget.CssWhitespacePolicy;
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * Runs several suites of layout tests. Each suite is defined in an XML file.
+ * The XML files to run are registered in the suite() method. 
+ */
+public class LayoutTest extends TestCase {
+
+    public String id;
+    public String doc;
+    public int layoutWidth = 100;
+    public BoxSpec result;
+    public String css;
+    
+    public static Test suite() throws ParserConfigurationException, FactoryConfigurationError, IOException, SAXException {
+        TestSuite suite = new TestSuite(LayoutTest.class.getName());
+        suite.addTest(loadSuite("block-inline.xml"));
+        suite.addTest(loadSuite("before-after.xml"));
+        suite.addTest(loadSuite("linebreaks.xml"));
+        suite.addTest(loadSuite("tables.xml"));
+        return suite;
+    }
+    
+    public static Test loadSuite(String filename) throws ParserConfigurationException, FactoryConfigurationError, IOException, SAXException {
+        XMLReader xmlReader = SAXParserFactory.newInstance().newSAXParser().getXMLReader();
+        TestCaseBuilder builder = new TestCaseBuilder();
+        xmlReader.setContentHandler(builder);
+        //xmlReader.setEntityResolver(builder);
+        URL url = LayoutTest.class.getResource(filename);
+        xmlReader.parse(new InputSource(url.toString()));
+        
+        TestSuite suite = new TestSuite(filename);
+        for (Iterator it = builder.testCases.iterator(); it.hasNext();) {
+            LayoutTest test = (LayoutTest) it.next();
+            suite.addTest(test);
+        }
+        return suite;
+    }
+    
+
+    public LayoutTest() {
+        super("testLayout");
+    }
+
+    
+    public String getName() {
+        return this.id;
+    }
+    
+    
+    public void testLayout() throws Exception {
+        System.out.println();
+        System.out.println("-----------------------------------------------------------------------");
+        System.out.println("Test: " + this.id);
+        System.out.println("-----------------------------------------------------------------------");
+        
+        URL url = LayoutTest.class.getResource(this.css);
+        StyleSheetReader reader = new StyleSheetReader();
+        final StyleSheet ss = reader.read(url);
+        
+        FakeGraphics g = new FakeGraphics();
+        
+        LayoutContext context = new LayoutContext();
+        context.setBoxFactory(new TestBoxFactory());
+        context.setGraphics(g);
+        context.setStyleSheet(ss);
+
+        DocumentReader docReader = new DocumentReader();
+        docReader.setWhitespacePolicyFactory(new IWhitespacePolicyFactory() {
+            public IWhitespacePolicy getPolicy(String publicId) {
+                return new CssWhitespacePolicy(ss);
+            }
+        });
+        Document doc = docReader.read(this.doc);
+        context.setDocument(doc);
+        
+        RootBox rootBox = new RootBox(context, doc.getRootElement(), this.layoutWidth);
+        rootBox.layout(context, 0, Integer.MAX_VALUE);
+
+        assertBox(this.result, rootBox, "");
+    }
+    
+    
+    private static void assertBox(BoxSpec boxSpec, Box box, String indent) {
+        
+        System.out.println(indent + boxSpec.className);
+        
+        if (boxSpec.className != null) {
+            String actualClassName = box.getClass().getName();
+            if (boxSpec.className.lastIndexOf('.') == -1) {
+                // no dot in box spec classname, so strip the prefix from the actual classname
+                int lastDot = actualClassName.lastIndexOf('.'); 
+                actualClassName = actualClassName.substring(lastDot + 1);
+            }
+            assertEquals(boxSpec.className, actualClassName);
+        }
+        
+        if (boxSpec.element != null) {
+            assertNotNull(box.getElement());
+            assertEquals(boxSpec.element, box.getElement().getName());
+        }
+
+        if (boxSpec.text != null && box instanceof TextBox) {
+            assertEquals(boxSpec.text, ((TextBox) box).getText());
+        }
+        
+        if (boxSpec.children.size() > 0 && box.getChildren() == null) {
+            fail("Expected " + boxSpec.children.size() + " children, but " + boxSpec.className + "'s children is null");
+        }
+
+        if (boxSpec.children.size() != box.getChildren().length) {
+            System.out.println("Wrong number of child boxes");
+            System.out.println("  Expected:");
+            for (Iterator it = boxSpec.children.iterator(); it.hasNext();) {
+                BoxSpec childSpec = (BoxSpec) it.next();
+                System.out.print("    " + childSpec.className);
+                if (childSpec.text != null) {
+                    System.out.print(" '" + childSpec.text + "'");
+                }
+                System.out.println();
+            }
+            System.out.println("  Actual:");
+            for (int i = 0; i < box.getChildren().length; i++) {
+                Box childBox = box.getChildren()[i];
+                System.out.println("    " + childBox.getClass() + ": " + childBox);
+            }
+            fail("Wrong number of child boxes.");
+        }
+        
+        for (int i = 0; i < boxSpec.children.size(); i++) {
+            assertBox((BoxSpec) boxSpec.children.get(i), box.getChildren() [i], indent + "  ");
+        }
+        
+    }
+    
+    private static class TestCaseBuilder extends DefaultHandler {
+        
+        private List testCases;
+        private String css;
+        private LayoutTest testCase;
+        private BoxSpec boxSpec;
+        private Stack boxSpecs;
+        private boolean inDoc;
+        
+        public void characters(char[] ch, int start, int length)
+                throws SAXException {
+            
+            String s = new String(ch, start, length).trim();
+            if (s.length() > 0) {
+                if (inDoc) {
+                    this.testCase.doc = new String(ch, start, length);
+                } else {
+                    throw new IllegalStateException();
+                }
+            }
+        }
+        public void endElement(String uri, String localName, String qName)
+                throws SAXException {
+            if (qName.equals("box")) {
+                if (this.boxSpecs.isEmpty()) {
+                    this.boxSpec = null;
+                } else {
+                    this.boxSpec = (BoxSpec) this.boxSpecs.pop();
+                }
+            } else if (qName.equals("doc")) {
+                this.inDoc = false;
+            }
+        }
+        public void startElement(String uri, String localName, String qName,
+                Attributes attributes) throws SAXException {
+            
+            if (qName.equals("testcases")) {
+                this.testCases = new ArrayList();
+                this.css = attributes.getValue("css");
+                if (this.css == null) {
+                    this.css = "test.css";
+                }
+                this.testCase = null;
+                this.boxSpecs = new Stack();
+            } else if (qName.equals("test")) {
+                this.testCase = new LayoutTest();
+                this.testCase.id = attributes.getValue("id");
+                this.testCase.css = this.css;
+                String layoutWidth = attributes.getValue("layoutWidth");
+                if (layoutWidth != null) {
+                    this.testCase.layoutWidth = Integer.parseInt(layoutWidth);
+                }
+                testCases.add(this.testCase);
+            } else if (qName.equals("doc")) {
+                this.inDoc = true;
+            } else if (qName.equals("result")) {
+            } else if (qName.equals("box")) {
+                BoxSpec parent = this.boxSpec;
+                this.boxSpec = new BoxSpec();
+                this.boxSpec.className = attributes.getValue("class");
+                this.boxSpec.element = attributes.getValue("element");
+                this.boxSpec.text = attributes.getValue("text");
+                if (parent == null) {
+                    this.testCase.result = this.boxSpec;
+                } else {
+                    this.boxSpecs.push(parent);
+                    parent.children.add(this.boxSpec);
+                }
+            } else {
+                throw new SAXException("Unrecognized element: " + qName);
+            }
+        }
+    }
+    
+    private static class BoxSpec {
+        public String className;
+        public String element;
+        public List children = new ArrayList();
+        public String text;
+    }
+    
+}
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/TestBlockElementBox.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/TestBlockElementBox.java
new file mode 100644
index 0000000..4e23c87
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/TestBlockElementBox.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.layout;
+
+import java.net.URL;
+
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheet;
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheetReader;
+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.RootElement;
+import org.eclipse.wst.xml.vex.core.internal.layout.BlockElementBox;
+import org.eclipse.wst.xml.vex.core.internal.layout.BlockPseudoElementBox;
+import org.eclipse.wst.xml.vex.core.internal.layout.Box;
+import org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext;
+import org.eclipse.wst.xml.vex.core.internal.layout.RootBox;
+
+import junit.framework.TestCase;
+
+
+public class TestBlockElementBox extends TestCase {
+    
+    FakeGraphics g;
+    LayoutContext context;
+
+    public TestBlockElementBox() throws Exception {
+        URL url = this.getClass().getResource("test.css");
+        StyleSheetReader reader = new StyleSheetReader();
+        StyleSheet ss = reader.read(url);
+        
+        this.g = new FakeGraphics();
+        
+        this.context = new LayoutContext();
+        this.context.setBoxFactory(new TestBoxFactory());
+        this.context.setGraphics(this.g);
+        this.context.setStyleSheet(ss);
+    }
+
+    public void testBeforeAfter() throws Exception {
+        RootElement root = new RootElement("root");
+        Document doc = new Document(root);
+        doc.insertElement(1, new Element("beforeBlock"));
+        
+        RootBox rootBox = new RootBox(this.context, root, 500);
+        rootBox.layout(this.context, 0, Integer.MAX_VALUE);
+
+        Box[] children;
+        BlockElementBox beb;
+        
+        children = rootBox.getChildren();
+        assertEquals(1, children.length);
+        assertEquals(BlockElementBox.class, children[0].getClass());
+        beb = (BlockElementBox) children[0];
+        assertEquals(root, beb.getElement());
+        
+        children = beb.getChildren();
+        assertEquals(2, children.length);
+        assertEquals(BlockPseudoElementBox.class, children[0].getClass());
+        assertEquals(BlockElementBox.class, children[1].getClass());
+        beb = (BlockElementBox) children[1];
+        assertEquals("beforeBlock", beb.getElement().getName());
+        
+    }
+    
+}
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/TestBlocksInInlines.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/TestBlocksInInlines.java
new file mode 100644
index 0000000..3b004ff
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/TestBlocksInInlines.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.layout;
+
+import java.net.URL;
+
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheet;
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheetReader;
+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.RootElement;
+import org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext;
+import org.eclipse.wst.xml.vex.core.internal.layout.RootBox;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests proper function of a block-level element within an inline element.
+ * These must be layed out as a block child of the containing block element.
+ */
+public class TestBlocksInInlines extends TestCase {
+
+    FakeGraphics g;
+    LayoutContext context;
+
+    public TestBlocksInInlines() throws Exception {
+        URL url = this.getClass().getResource("test.css");
+        StyleSheetReader reader = new StyleSheetReader();
+        StyleSheet ss = reader.read(url);
+        
+        this.g = new FakeGraphics();
+        
+        this.context = new LayoutContext();
+        this.context.setBoxFactory(new TestBoxFactory());
+        this.context.setGraphics(this.g);
+        this.context.setStyleSheet(ss);
+    }
+
+    public void testBlockInInline() throws Exception {
+        RootElement root = new RootElement("root");
+        Document doc = new Document(root);
+
+        doc.insertText(1, "one  five");
+        doc.insertElement(5, new Element("b"));
+        doc.insertText(6, "two  four");
+        doc.insertElement(10, new Element("p"));
+        doc.insertText(11, "three");
+        
+        RootBox rootBox = new RootBox(this.context, root, 500);
+        rootBox.layout(this.context, 0, Integer.MAX_VALUE);
+
+        
+    }
+}
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/TestBoxFactory.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/TestBoxFactory.java
new file mode 100644
index 0000000..b6d12e5
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/TestBoxFactory.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.layout;
+
+import org.eclipse.wst.xml.vex.core.internal.dom.Element;
+import org.eclipse.wst.xml.vex.core.internal.layout.BlockElementBox;
+import org.eclipse.wst.xml.vex.core.internal.layout.Box;
+import org.eclipse.wst.xml.vex.core.internal.layout.CssBoxFactory;
+import org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext;
+import org.eclipse.wst.xml.vex.core.internal.layout.SpaceBox;
+
+
+
+/**
+ * A box factory that, for an element named &lt;space&gt;, returns a SpaceBox
+ * with height and width given by attributes of those names, e.g.
+ * &lt;space height="100" width="200"/&gt;
+ */
+public class TestBoxFactory extends CssBoxFactory {
+    
+    public Box createBox(LayoutContext context, Element element,
+            BlockElementBox parent, int width) {
+        
+        if (element.getName().equals("space")) {
+            int w = 0;
+            int h = 0;
+            try {
+                w = Integer.parseInt(element.getAttribute("width"));
+            } catch (NumberFormatException ex) {
+            }
+            try {
+                h = Integer.parseInt(element.getAttribute("height"));
+            } catch (NumberFormatException ex) {
+            }
+            return new SpaceBox(w, h);
+        }
+        // TODO Auto-generated method stub
+        return super.createBox(context, element, parent, width);
+    }
+}
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/TestDocumentTextBox.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/TestDocumentTextBox.java
new file mode 100644
index 0000000..afcfe03
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/TestDocumentTextBox.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.layout;
+
+import java.net.URL;
+
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheet;
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheetReader;
+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.RootElement;
+import org.eclipse.wst.xml.vex.core.internal.layout.CssBoxFactory;
+import org.eclipse.wst.xml.vex.core.internal.layout.DocumentTextBox;
+import org.eclipse.wst.xml.vex.core.internal.layout.InlineBox;
+import org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests the DocumentTestBox class. We focus here on proper offsets, since
+ * text splitting is tested thoroughly in TestStaticTextBox.
+ */
+public class TestDocumentTextBox extends TestCase {
+
+    FakeGraphics g;
+    LayoutContext context;
+    
+    public TestDocumentTextBox() throws Exception {
+        
+        URL url = this.getClass().getResource("test.css");
+        StyleSheetReader reader = new StyleSheetReader();
+        StyleSheet ss = reader.read(url);
+        
+        this.g = new FakeGraphics();
+        
+        this.context = new LayoutContext();
+        this.context.setBoxFactory(new CssBoxFactory());
+        this.context.setGraphics(this.g);
+        this.context.setStyleSheet(ss);
+    }
+    
+    public void testSplit() throws Exception {
+        RootElement root = new RootElement("root");
+        Document doc = new Document(root);
+        
+        Styles styles = this.context.getStyleSheet().getStyles(root);
+        
+        int width = g.getCharWidth();
+        
+        //  0     6     13      21
+        // /     /      /       /
+        // baggy orange trousers
+        
+        doc.insertText(1, "baggy orange trousers");
+        DocumentTextBox box = new DocumentTextBox(this.context, root, 1, 22);
+        assertEquals(box.getText().length() * width, box.getWidth());
+        assertEquals(styles.getLineHeight(), box.getHeight());
+        assertSplit(box, 22, false, "baggy orange trousers", null);
+        assertSplit(box, 21, false, "baggy orange trousers", null);
+        assertSplit(box, 20, false, "baggy orange ", "trousers");
+        assertSplit(box, 13, false, "baggy orange ", "trousers");
+        assertSplit(box, 12, false, "baggy ", "orange trousers");
+        assertSplit(box, 6, false, "baggy ", "orange trousers");
+        assertSplit(box, 5, false, null, "baggy orange trousers");
+        assertSplit(box, 1, false, null, "baggy orange trousers");
+        assertSplit(box, 0, false, null, "baggy orange trousers");
+        assertSplit(box, -1, false, null, "baggy orange trousers");
+
+        assertSplit(box, 22, true, "baggy orange trousers", null);
+        assertSplit(box, 21, true, "baggy orange trousers", null);
+        assertSplit(box, 20, true, "baggy orange ", "trousers");
+        assertSplit(box, 13, true, "baggy orange ", "trousers");
+        assertSplit(box, 12, true, "baggy ", "orange trousers");
+        assertSplit(box, 6, true, "baggy ", "orange trousers");
+        assertSplit(box, 5, true, "baggy", " orange trousers");
+        assertSplit(box, 4, true, "bagg", "y orange trousers");
+        assertSplit(box, 3, true, "bag", "gy orange trousers");
+        assertSplit(box, 2, true, "ba", "ggy orange trousers");
+        assertSplit(box, 1, true, "b", "aggy orange trousers");
+        assertSplit(box, 0, true, "b", "aggy orange trousers");
+        assertSplit(box, -1, true, "b", "aggy orange trousers");
+        
+        doc.delete(1, 22);
+        
+        //                  0    5   10
+        //                 /    /    /
+        doc.insertText(1, "red  green");
+        box = new DocumentTextBox(this.context, root, 1, 11);
+        assertSplit(box, 11, false, "red  green", null);
+        assertSplit(box, 10, false, "red  green", null);
+        assertSplit(box, 9, false, "red  ", "green");
+        assertSplit(box, 5, false, "red  ", "green");
+        
+        //
+        // This is the way it should work from a formatting point-of-view, but
+        // it could be problematic when it gets to positioning the caret, e.g.
+        // if we had lots of spaces to the right of a word it would format
+        // properly, but the caret would get carried out of the formatted area.
+        // 
+//        assertSplit(box, 4, false, null, "red  green");
+//        assertSplit(box, 1, false, null, "red  green");
+//        assertSplit(box, 0, false, null, "red  green");
+//        assertSplit(box, -1, false, null, "red  green");
+
+        //
+        // This solves the caret problem at the expense of the formatting
+        // problem. It also happens to be how my initial implementation works!
+        // In the end it doesn't much matter, since Vex should collapse
+        // contiguous space into a single space character.
+        //
+        assertSplit(box, 4, false, "red ", " green");
+        assertSplit(box, 3, false, null, "red  green");
+        assertSplit(box, 1, false, null, "red  green");
+        assertSplit(box, 0, false, null, "red  green");
+        assertSplit(box, -1, false, null, "red  green");
+
+        assertSplit(box, 4, true, "red ", " green");
+        assertSplit(box, 3, true, "red", "  green");
+        assertSplit(box, 1, true, "r", "ed  green");
+        assertSplit(box, 0, true, "r", "ed  green");
+        assertSplit(box, -1, true, "r", "ed  green");
+
+    }
+    
+    private void assertSplit(DocumentTextBox box, int splitPos, boolean force, String left, String right) {
+        
+        Styles styles = this.context.getStyleSheet().getStyles(box.getElement());
+        
+        int width = g.getCharWidth();
+        
+        InlineBox.Pair pair = box.split(context, splitPos * width, force);
+
+        DocumentTextBox leftBox = (DocumentTextBox) pair.getLeft();
+        DocumentTextBox rightBox = (DocumentTextBox) pair.getRight();
+        
+        int leftOffset = 1;
+        int midOffset = leftOffset + (left == null ? 0 : left.length());
+        int rightOffset = leftOffset + box.getText().length();
+        
+        if (left == null) {
+            assertNull(leftBox); 
+        } else {
+            assertNotNull(leftBox);
+            assertEquals(left, leftBox.getText());
+            assertEquals(left.length() * width, leftBox.getWidth());
+            assertEquals(styles.getLineHeight(), leftBox.getHeight());
+            assertEquals(leftOffset, leftBox.getStartOffset());
+            assertEquals(midOffset - 1, leftBox.getEndOffset());
+        }
+        
+        if (right == null) {
+            assertNull(rightBox); 
+        } else {
+            assertNotNull(rightBox);
+            assertEquals(right, rightBox.getText());
+            assertEquals(right.length() * width, rightBox.getWidth());
+            assertEquals(styles.getLineHeight(), rightBox.getHeight());
+            assertEquals(midOffset, rightBox.getStartOffset());
+            assertEquals(rightOffset - 1, rightBox.getEndOffset());
+        }
+        
+    }
+}
+
+
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/TestParagraphBox.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/TestParagraphBox.java
new file mode 100644
index 0000000..b8468dd
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/TestParagraphBox.java
@@ -0,0 +1,74 @@
+/*******************************************************************************
+ * 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.net.URL;
+
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheet;
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheetReader;
+import org.eclipse.wst.xml.vex.core.internal.layout.CssBoxFactory;
+import org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext;
+
+import junit.framework.TestCase;
+
+public class TestParagraphBox extends TestCase {
+
+    FakeGraphics g;
+    LayoutContext context;
+    
+    public TestParagraphBox() throws Exception {
+        
+        URL url = this.getClass().getResource("test.css");
+        StyleSheetReader reader = new StyleSheetReader();
+        StyleSheet ss = reader.read(url);
+        
+        this.g = new FakeGraphics();
+        
+        this.context = new LayoutContext();
+        this.context.setBoxFactory(new CssBoxFactory());
+        this.context.setGraphics(this.g);
+        this.context.setStyleSheet(ss);
+    }
+
+    /*
+    public void testWordWrap() throws Exception {
+        RootElement root = new RootElement("root");
+        Document doc = new Document(root);
+        
+        Styles styles = this.context.getStyleSheet().getStyles(root);
+        
+        FontMetrics fm = this.g.getFontMetrics();
+        
+        // Test Case 1: check the offsets 
+        //
+        // UPPER CASE indicates static text
+        // lower case indicates document text
+        // [ ] represent element start and end         
+        //
+        // BLACK WHITE GRAY
+        // RED [orange] YELLOW   (line is 1:8, last=false)
+        // BLACK WHITE GRAY
+        // [blue] GREEN [pink]     (line is 9:20 last=true)
+        // BLACK WHITE GRAY
+        //
+        // Document looks like this (# chars are element sentinels
+        //    2     8      16  20
+        //   /     /       /   /    
+        // ##orange##blue##pink##
+        //           \   \ 
+        //            10  14
+        //
+        
+    }
+    */
+}
+
+
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/TestStaticTextBox.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/TestStaticTextBox.java
new file mode 100644
index 0000000..59e4236
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/TestStaticTextBox.java
@@ -0,0 +1,154 @@
+/*******************************************************************************
+ * 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.net.URL;
+
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheet;
+import org.eclipse.wst.xml.vex.core.internal.css.StyleSheetReader;
+import org.eclipse.wst.xml.vex.core.internal.css.Styles;
+import org.eclipse.wst.xml.vex.core.internal.dom.RootElement;
+import org.eclipse.wst.xml.vex.core.internal.layout.CssBoxFactory;
+import org.eclipse.wst.xml.vex.core.internal.layout.InlineBox;
+import org.eclipse.wst.xml.vex.core.internal.layout.LayoutContext;
+import org.eclipse.wst.xml.vex.core.internal.layout.StaticTextBox;
+
+import junit.framework.TestCase;
+
+public class TestStaticTextBox extends TestCase {
+
+    FakeGraphics g;
+    LayoutContext context;
+    
+    public TestStaticTextBox() throws Exception {
+        
+        URL url = this.getClass().getResource("test.css");
+        StyleSheetReader reader = new StyleSheetReader();
+        StyleSheet ss = reader.read(url);
+        
+        this.g = new FakeGraphics();
+        
+        this.context = new LayoutContext();
+        this.context.setBoxFactory(new CssBoxFactory());
+        this.context.setGraphics(this.g);
+        this.context.setStyleSheet(ss);
+    }
+    
+    public void testSplit() throws Exception {
+        RootElement root = new RootElement("root");
+        
+        Styles styles = this.context.getStyleSheet().getStyles(root);
+        
+        int width = g.getCharWidth();
+        
+        //  0     6     13      21
+        // /     /      /       /
+        // baggy orange trousers
+        
+        StaticTextBox box = new StaticTextBox(this.context, root, "baggy orange trousers");
+        assertEquals(box.getText().length() * width, box.getWidth());
+        assertEquals(styles.getLineHeight(), box.getHeight());
+        assertSplit(box, 22, false, "baggy orange trousers", null);
+        assertSplit(box, 21, false, "baggy orange trousers", null);
+        assertSplit(box, 20, false, "baggy orange ", "trousers");
+        assertSplit(box, 13, false, "baggy orange ", "trousers");
+        assertSplit(box, 12, false, "baggy ", "orange trousers");
+        assertSplit(box, 6, false, "baggy ", "orange trousers");
+        assertSplit(box, 5, false, null, "baggy orange trousers");
+        assertSplit(box, 1, false, null, "baggy orange trousers");
+        assertSplit(box, 0, false, null, "baggy orange trousers");
+        assertSplit(box, -1, false, null, "baggy orange trousers");
+
+        assertSplit(box, 22, true, "baggy orange trousers", null);
+        assertSplit(box, 21, true, "baggy orange trousers", null);
+        assertSplit(box, 20, true, "baggy orange ", "trousers");
+        assertSplit(box, 13, true, "baggy orange ", "trousers");
+        assertSplit(box, 12, true, "baggy ", "orange trousers");
+        assertSplit(box, 6, true, "baggy ", "orange trousers");
+        assertSplit(box, 5, true, "baggy", " orange trousers");
+        assertSplit(box, 4, true, "bagg", "y orange trousers");
+        assertSplit(box, 3, true, "bag", "gy orange trousers");
+        assertSplit(box, 2, true, "ba", "ggy orange trousers");
+        assertSplit(box, 1, true, "b", "aggy orange trousers");
+        assertSplit(box, 0, true, "b", "aggy orange trousers");
+        assertSplit(box, -1, true, "b", "aggy orange trousers");
+        
+        //                                            0    5   10
+        //                                           /    /    /
+        box = new StaticTextBox(this.context, root, "red  green");
+        assertSplit(box, 11, false, "red  green", null);
+        assertSplit(box, 10, false, "red  green", null);
+        assertSplit(box, 9, false, "red  ", "green");
+        assertSplit(box, 5, false, "red  ", "green");
+        
+        //
+        // This is the way it should work from a formatting point-of-view, but
+        // it could be problematic when it gets to positioning the caret, e.g.
+        // if we had lots of spaces to the right of a word it would format
+        // properly, but the caret would get carried out of the formatted area.
+        // 
+//        assertSplit(box, 4, false, null, "red  green");
+//        assertSplit(box, 1, false, null, "red  green");
+//        assertSplit(box, 0, false, null, "red  green");
+//        assertSplit(box, -1, false, null, "red  green");
+
+        //
+        // This solves the caret problem at the expense of the formatting
+        // problem. It also happens to be how my initial implementation works!
+        // In the end it doesn't much matter, since Vex should collapse
+        // contiguous space into a single space character.
+        //
+        assertSplit(box, 4, false, "red ", " green");
+        assertSplit(box, 3, false, null, "red  green");
+        assertSplit(box, 1, false, null, "red  green");
+        assertSplit(box, 0, false, null, "red  green");
+        assertSplit(box, -1, false, null, "red  green");
+
+        assertSplit(box, 4, true, "red ", " green");
+        assertSplit(box, 3, true, "red", "  green");
+        assertSplit(box, 1, true, "r", "ed  green");
+        assertSplit(box, 0, true, "r", "ed  green");
+        assertSplit(box, -1, true, "r", "ed  green");
+
+    }
+    
+    private void assertSplit(StaticTextBox box, int splitPos, boolean force, String left, String right) {
+        
+        Styles styles = this.context.getStyleSheet().getStyles(box.getElement());
+        int width = g.getCharWidth();
+        
+        InlineBox.Pair pair = box.split(context, splitPos * width, force);
+
+        StaticTextBox leftBox = (StaticTextBox) pair.getLeft();
+        StaticTextBox rightBox = (StaticTextBox) pair.getRight();
+        
+        if (left == null) {
+            assertNull(leftBox); 
+        } else {
+            assertNotNull(leftBox);
+            assertEquals(left, leftBox.getText());
+            assertEquals(left.length() * width, leftBox.getWidth());
+            assertEquals(styles.getLineHeight(), leftBox.getHeight());
+        }
+        
+        if (right == null) {
+            assertNull(rightBox); 
+        } else {
+            assertNotNull(rightBox);
+            assertEquals(right, rightBox.getText());
+            assertEquals(right.length() * width, rightBox.getWidth());
+            assertEquals(styles.getLineHeight(), rightBox.getHeight());
+        }
+        
+    }
+}
+
+
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/before-after.css b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/before-after.css
new file mode 100644
index 0000000..e4817c7
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/before-after.css
@@ -0,0 +1,77 @@
+
+iia {
+  display: inline;
+}
+
+iia:after {
+  display: inline;
+  content: 'after';
+}
+
+iib {
+  display: inline;
+}
+
+iib:before {
+  display: inline;
+  content: 'before';
+}
+
+
+iba {
+  display: inline;
+}
+
+iba:after {
+  display: block;
+  content: 'AFTER';
+}
+
+ibb {
+  display: inline;
+}
+
+ibb:before {
+  display: block;
+  content: 'BEFORE';
+}
+
+
+
+bia {
+  display: block;
+}
+
+bia:after {
+  display: inline;
+  content: 'after';
+}
+
+bib {
+  display: block;
+}
+
+bib:before {
+  display: inline;
+  content: 'before';
+}
+
+
+bba {
+  display: block;
+}
+
+bba:after {
+  display: block;
+  content: 'AFTER';
+}
+
+bbb {
+  display: block;
+}
+
+bbb:before {
+  display: block;
+  content: 'BEFORE';
+}
+
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/before-after.xml b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/before-after.xml
new file mode 100644
index 0000000..586ebb1
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/before-after.xml
@@ -0,0 +1,145 @@
+<?xml version='1.0'?>
+<testcases css="before-after.css">
+
+  <test id="Block with Block Before" layoutWidth="100">
+    <doc><![CDATA[ <root><bbb>wuzzle</bbb></root> ]]></doc>
+    <result>
+      <box class="RootBox">
+        <box class="BlockElementBox" element="root">
+
+          <box class="BlockElementBox" element="bbb">
+
+            <box class="BlockPseudoElementBox">
+              <box class="ParagraphBox">
+                <box class="LineBox">
+                  <box class="StaticTextBox" text="BEFORE" />
+                </box>
+              </box>
+            </box>
+
+            <box class="ParagraphBox">
+              <box class="LineBox">
+                <box class="DocumentTextBox" text="wuzzle" />
+                <box class="PlaceholderBox"/>
+              </box>
+            </box>
+
+          </box>
+        </box>
+      </box>
+    </result>
+  </test>
+  
+  
+  
+  <test id="Block with Block After" layoutWidth="100">
+    <doc><![CDATA[ <root><bba>wuzzle</bba></root> ]]></doc>
+    <result>
+      <box class="RootBox">
+        <box class="BlockElementBox" element="root">
+
+          <box class="BlockElementBox" element="bba">
+
+            <box class="ParagraphBox">
+              <box class="LineBox">
+                <box class="DocumentTextBox" text="wuzzle" />
+                <box class="PlaceholderBox"/>
+              </box>
+            </box>
+
+            <box class="BlockPseudoElementBox">
+              <box class="ParagraphBox">
+                <box class="LineBox">
+                  <box class="StaticTextBox" text="AFTER" />
+                </box>
+              </box>
+            </box>
+
+          </box>
+        </box>
+      </box>
+    </result>
+  </test>
+
+
+  <test id="Block with Inline Before" layoutWidth="100">
+    <doc><![CDATA[ <root><bib>wuzzle</bib></root> ]]></doc>
+    <result>
+      <box class="RootBox">
+        <box class="BlockElementBox" element="root">
+
+          <box class="BlockElementBox" element="bib">
+
+            <box class="ParagraphBox">
+              <box class="LineBox">
+                <box class="StaticTextBox" text="before" />
+                <box class="DocumentTextBox" text="wuzzle" />
+                <box class="PlaceholderBox"/>
+              </box>
+            </box>
+
+          </box>
+        </box>
+      </box>
+    </result>
+  </test>
+  
+  <test id="Block with Inline After" layoutWidth="100">
+    <doc><![CDATA[ <root><bia>wuzzle</bia></root> ]]></doc>
+    <result>
+      <box class="RootBox">
+        <box class="BlockElementBox" element="root">
+
+          <box class="BlockElementBox" element="bia">
+
+            <box class="ParagraphBox">
+              <box class="LineBox">
+                <box class="DocumentTextBox" text="wuzzle" />
+                <box class="PlaceholderBox"/>
+                <box class="StaticTextBox" text="after" />
+              </box>
+            </box>
+
+          </box>
+        </box>
+      </box>
+    </result>
+  </test>
+  
+  
+<!--  
+  <test id="Inline with Block Before" layoutWidth="100">
+    <doc><![CDATA[ <root><ibb>wuzzle</ibb></root> ]]></doc>
+    <result>
+      <box class="RootBox">
+        <box class="BlockElementBox" element="root">
+
+          <box class="BlockPseudoElementBox">
+            <box class="ParagraphBox">
+              <box class="LineBox">
+                <box class="StaticTextBox" text="BEFORE" />
+              </box>
+            </box>
+          </box>
+
+          <box class="ParagraphBox">
+            <box class="LineBox">
+              <box class="InlineElementBox" element="ibb">
+                <box class="DrawableBox" />
+                <box class="DocumentTextBox" text="wuzzle" />
+                <box class="PlaceholderBox"/>
+                <box class="DrawbleBox" />
+              </box>
+              <box class="PlaceholderBox"/>
+            </box>
+          </box>
+
+        </box>
+      </box>
+    </result>
+  </test>
+-->
+  
+  
+  
+</testcases>
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/block-inline.css b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/block-inline.css
new file mode 100644
index 0000000..ba32596
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/block-inline.css
@@ -0,0 +1,15 @@
+
+root {
+  display: block;
+  font: 10pt monospaced;
+}
+
+
+b {
+  display: inline;
+}
+
+p {
+  display: block;
+}
+
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/block-inline.xml b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/block-inline.xml
new file mode 100644
index 0000000..c9a4701
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/block-inline.xml
@@ -0,0 +1,343 @@
+<?xml version='1.0'?>
+<testcases css="block-inline.css">
+
+  <test id="Empty Root" layoutWidth="100">
+    <doc><![CDATA[ <root/> ]]></doc>
+    <result>
+      <box class="RootBox">
+        <box class="BlockElementBox" element="root">
+          <box class="ParagraphBox">
+            <box class="LineBox">
+              <box class="PlaceholderBox"/>
+            </box>
+          </box>
+        </box>
+      </box>
+    </result>
+  </test>
+  
+  <test id="Simple Text" layoutWidth="100">
+    <doc><![CDATA[ <root>Hello, world!</root> ]]></doc>
+    <result>
+      <box class="RootBox">
+        <box class="BlockElementBox" element="root">
+          <box class="ParagraphBox">
+            <box class="LineBox">
+              <box class="DocumentTextBox" text="Hello, "/>
+              <box class="DocumentTextBox" text="world!"/>
+              <box class="PlaceholderBox"/>
+            </box>
+          </box>
+        </box>
+      </box>
+    </result>
+  </test>
+  
+  
+  
+  <!--
+       A simple wrap. 42 = length("Hello, ") * 6
+  -->
+  <test id="Simple Wrap" layoutWidth="42">
+    <doc><![CDATA[ <root>Hello, world!</root> ]]></doc>
+    <result>
+      <box class="RootBox">
+        <box class="BlockElementBox" element="root">
+          <box class="ParagraphBox">
+            <box class="LineBox">
+              <box class="DocumentTextBox" text="Hello, " />
+            </box>
+            <box class="LineBox">
+              <box class="DocumentTextBox" text="world!" />
+              <box class="PlaceholderBox"/>
+            </box>
+          </box>
+        </box>
+      </box>
+    </result>
+  </test>
+  
+  
+  <!-- 
+       Attempts to split the text inside the space after "Hello,".
+       The space should remain with "Hello," and not be split on to its
+       own line
+  -->
+  <test id="Split at First Space" layoutWidth="40">
+    <doc><![CDATA[ <root>Hello, world!</root> ]]></doc>
+    <result>
+      <box class="RootBox">
+        <box class="BlockElementBox" element="root">
+          <box class="ParagraphBox">
+            <box class="LineBox">
+              <box class="DocumentTextBox" text="Hello, " />
+            </box>
+            <box class="LineBox">
+              <box class="DocumentTextBox" text="world!" />
+              <box class="PlaceholderBox"/>
+            </box>
+          </box>
+        </box>
+      </box>
+    </result>
+  </test>
+  
+  
+  <test id="Empty Inline" layoutWidth="100">
+    <doc><![CDATA[ <root><b></b></root> ]]></doc>
+    <result>
+      <box class="RootBox">
+        <box class="BlockElementBox" element="root">
+          <box class="ParagraphBox">
+            <box class="LineBox">
+              <box class="PlaceholderBox"/>
+              <box class="InlineElementBox">
+                <box class="DrawableBox"/>
+                <box class="PlaceholderBox"/>
+                <box class="DrawableBox"/>
+              </box>
+              <box class="PlaceholderBox"/>
+            </box>
+          </box>
+        </box>
+      </box>
+    </result>
+  </test>
+
+  
+  <test id="Simple Inline" layoutWidth="100">
+    <doc><![CDATA[ <root><b>cat sat</b></root> ]]></doc>
+    <result>
+      <box class="RootBox">
+        <box class="BlockElementBox" element="root">
+          <box class="ParagraphBox">
+            <box class="LineBox">
+              <box class="PlaceholderBox"/>
+              <box class="InlineElementBox">
+                <box class="DrawableBox"/>
+                <box class="DocumentTextBox" text="cat " />
+                <box class="DocumentTextBox" text="sat" />
+                <box class="PlaceholderBox"/>
+                <box class="DrawableBox"/>
+              </box>
+              <box class="PlaceholderBox"/>
+            </box>
+          </box>
+        </box>
+      </box>
+    </result>
+  </test>
+
+  
+  <test id="Simple Inline Split 1" layoutWidth="36">
+    <doc><![CDATA[ <root><b>cat sat</b></root> ]]></doc>
+    <result>
+      <box class="RootBox">
+        <box class="BlockElementBox" element="root">
+          <box class="ParagraphBox">
+            <box class="LineBox">
+              <box class="PlaceholderBox"/>
+              <box class="InlineElementBox">
+                <box class="DrawableBox"/>
+                <box class="DocumentTextBox" text="cat " />
+              </box>
+            </box>
+            <box class="LineBox">
+              <box class="InlineElementBox">
+                <box class="DocumentTextBox" text="sat" />
+                <box class="PlaceholderBox"/>
+                <box class="DrawableBox"/>
+              </box>
+              <box class="PlaceholderBox"/>
+            </box>
+          </box>
+        </box>
+      </box>
+    </result>
+  </test>
+  
+  
+  <test id="Inline With Surrounding Text" layoutWidth="100">
+    <doc><![CDATA[ <root>hat <b>cat sat</b> bat</root> ]]></doc>
+    <result>
+      <box class="RootBox">
+        <box class="BlockElementBox" element="root">
+          <box class="ParagraphBox">
+            <box class="LineBox">
+              <box class="DocumentTextBox" text="hat " />
+              <box class="PlaceholderBox"/>
+              <box class="InlineElementBox">
+                <box class="DrawableBox"/>
+                <box class="DocumentTextBox" text="cat " />
+                <box class="DocumentTextBox" text="sat" />
+                <box class="PlaceholderBox"/>
+                <box class="DrawableBox"/>
+              </box>
+              <box class="DocumentTextBox" text=" " />
+              <box class="DocumentTextBox" text="bat" />
+              <box class="PlaceholderBox"/>
+            </box>
+          </box>
+        </box>
+      </box>
+    </result>
+  </test>
+  
+  
+  <test id="Inline With Surrounding Text Split 1" layoutWidth="76">
+    <doc><![CDATA[ <root>hat <b>cat sat</b> bat</root> ]]></doc>
+    <result>
+      <box class="RootBox">
+        <box class="BlockElementBox" element="root">
+          <box class="ParagraphBox">
+            <box class="LineBox">
+              <box class="DocumentTextBox" text="hat "/>
+              <box class="PlaceholderBox"/>
+              <box class="InlineElementBox">
+                <box class="DrawableBox"/>
+                <box class="DocumentTextBox" text="cat " />
+                <box class="DocumentTextBox" text="sat" />
+                <box class="PlaceholderBox"/>
+                <box class="DrawableBox"/>
+              </box>
+              <box class="DocumentTextBox" text=" " />
+            </box>
+            <box class="LineBox">
+              <box class="DocumentTextBox" text="bat" />
+              <box class="PlaceholderBox"/>
+            </box>
+          </box>
+        </box>
+      </box>
+    </result>
+  </test>
+  
+  
+  <test id="Inline With Surrounding Text Split 2" layoutWidth="75">
+    <doc><![CDATA[ <root>hat <b>cat sat</b> bat</root> ]]></doc>
+    <result>
+      <box class="RootBox">
+        <box class="BlockElementBox" element="root">
+          <box class="ParagraphBox">
+            <box class="LineBox">
+              <box class="DocumentTextBox" text="hat " />
+              <box class="PlaceholderBox"/>
+              <box class="InlineElementBox">
+                <box class="DrawableBox"/>
+                <box class="DocumentTextBox" text="cat " />
+              </box>
+            </box>
+            <box class="LineBox">
+              <box class="InlineElementBox">
+                <box class="DocumentTextBox" text="sat" />
+                <box class="PlaceholderBox"/>
+                <box class="DrawableBox"/>
+              </box>
+              <box class="DocumentTextBox" text=" " />
+              <box class="DocumentTextBox" text="bat" />
+              <box class="PlaceholderBox"/>
+            </box>
+          </box>
+        </box>
+      </box>
+    </result>
+  </test>
+
+
+  
+  <test id="Block Child w/ Inline Before" layoutWidth="75">
+    <doc><![CDATA[ <root>Paris <p>Garters</p></root> ]]></doc>
+    <result>
+      <box class="RootBox">
+        <box class="BlockElementBox" element="root">
+        
+          <box class="ParagraphBox">
+            <box class="LineBox">
+              <box class="DocumentTextBox" text="Paris" />
+              <box class="PlaceholderBox"/>
+            </box>
+          </box>
+
+          <box class="BlockElementBox">
+            <box class="ParagraphBox">
+              <box class="LineBox">
+                <box class="DocumentTextBox" text="Garters" />
+                <box class="PlaceholderBox"/>
+              </box>
+            </box>
+          </box>
+          
+        </box>
+      </box>
+    </result>
+  </test>
+
+
+  
+  <test id="Block Child w/ Inline After" layoutWidth="100">
+    <doc><![CDATA[ <root><p>Harris</p> Tweed</root> ]]></doc>
+    <result>
+      <box class="RootBox">
+        <box class="BlockElementBox" element="root">
+        
+          <box class="BlockElementBox">
+            <box class="ParagraphBox">
+              <box class="LineBox">
+                <box class="DocumentTextBox" text="Harris" />
+                <box class="PlaceholderBox"/>
+              </box>
+            </box>
+          </box>
+          
+          <box class="ParagraphBox">
+            <box class="LineBox">
+              <box class="DocumentTextBox" text="Tweed" />
+              <box class="PlaceholderBox"/>
+            </box>
+          </box>
+
+        </box>
+      </box>
+    </result>
+  </test>
+
+
+  <test id="Block Child w/ Surrounding Inlines" layoutWidth="200">
+    <doc><![CDATA[ <root>Oliver <p>Boliver</p> Butt</root> ]]></doc>
+    <result>
+      <box class="RootBox">
+        <box class="BlockElementBox" element="root">
+        
+          <box class="ParagraphBox">
+            <box class="LineBox">
+              <box class="DocumentTextBox" text="Oliver" />
+              <box class="PlaceholderBox"/>
+            </box>
+          </box>
+
+          <box class="BlockElementBox">
+            <box class="ParagraphBox">
+              <box class="LineBox">
+                <box class="DocumentTextBox" text="Boliver" />
+                <box class="PlaceholderBox"/>
+              </box>
+            </box>
+          </box>
+          
+          <box class="ParagraphBox">
+            <box class="LineBox">
+              <box class="DocumentTextBox" text="Butt" />
+              <box class="PlaceholderBox"/>
+            </box>
+          </box>
+
+        </box>
+      </box>
+    </result>
+  </test>
+
+
+  <!-- TODO test blocks inside inlines -->
+
+  
+</testcases>
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/linebreaks.css b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/linebreaks.css
new file mode 100644
index 0000000..895714a
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/linebreaks.css
@@ -0,0 +1,17 @@
+
+root {
+  display: block;
+}
+
+p {
+  display: block;
+}
+
+pre {
+  display: block;
+  white-space: pre;
+}
+
+b {
+  display: inline;
+}
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/linebreaks.xml b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/linebreaks.xml
new file mode 100644
index 0000000..12eafd8
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/linebreaks.xml
@@ -0,0 +1,253 @@
+<?xml version='1.0'?>
+<testcases css="linebreaks.css">
+
+
+  <test id="LF" layoutWidth="100">
+    <doc><![CDATA[ <root><pre>newline&#xa;end</pre></root> ]]></doc>
+    <result>
+      <box class="RootBox">
+        <box class="BlockElementBox" element="root">
+
+          <box class="BlockElementBox" element="pre">
+
+            <box class="ParagraphBox">
+              <box class="LineBox">
+                <box class="DocumentTextBox" text="newline&#10;" />
+              </box>
+              <box class="LineBox">
+                <box class="DocumentTextBox" text="end" />
+                <box class="PlaceholderBox"/>
+              </box>
+            </box>
+
+          </box>
+        </box>
+      </box>
+    </result>
+  </test>
+  
+  
+  <test id="Two LFs" layoutWidth="100">
+    <doc><![CDATA[ <root><pre>newline&#xa;&#xa;end</pre></root> ]]></doc>
+    <result>
+      <box class="RootBox">
+        <box class="BlockElementBox" element="root">
+
+          <box class="BlockElementBox" element="pre">
+
+            <box class="ParagraphBox">
+              <box class="LineBox">
+                <box class="DocumentTextBox" text="newline&#10;" />
+              </box>
+              <box class="LineBox">
+                <box class="DocumentTextBox" text="&#10;" />
+              </box>
+              <box class="LineBox">
+                <box class="DocumentTextBox" text="end" />
+                <box class="PlaceholderBox"/>
+              </box>
+            </box>
+
+          </box>
+        </box>
+      </box>
+    </result>
+  </test>
+  
+  
+  <test id="Newline w/ Surrounding Space" layoutWidth="100">
+    <doc><![CDATA[ <root><pre>newline &#xa; end</pre></root> ]]></doc>
+    <result>
+      <box class="RootBox">
+        <box class="BlockElementBox" element="root">
+
+          <box class="BlockElementBox" element="pre">
+
+            <box class="ParagraphBox">
+              <box class="LineBox">
+                <box class="DocumentTextBox" text="newline &#xa;" />
+              </box>
+              <box class="LineBox">
+                <box class="DocumentTextBox" text=" " />
+                <box class="DocumentTextBox" text="end" />
+                <box class="PlaceholderBox"/>
+              </box>
+            </box>
+
+          </box>
+        </box>
+      </box>
+    </result>
+  </test>
+  
+  
+  <test id="Carriage Return" layoutWidth="100">
+    <doc><![CDATA[ <root><pre>line1&#xd;line2</pre></root> ]]></doc>
+    <result>
+      <box class="RootBox">
+        <box class="BlockElementBox" element="root">
+
+          <box class="BlockElementBox" element="pre">
+
+            <box class="ParagraphBox">
+              <box class="LineBox">
+                <box class="DocumentTextBox" text="line1&#xa;" />
+              </box>
+              <box class="LineBox">
+                <box class="DocumentTextBox" text="line2" />
+                <box class="PlaceholderBox"/>
+              </box>
+            </box>
+
+          </box>
+        </box>
+      </box>
+    </result>
+  </test>
+  
+  
+  <test id="Two CRs" layoutWidth="100">
+    <doc><![CDATA[ <root><pre>line1&#xd;&#xd;line2</pre></root> ]]></doc>
+    <result>
+      <box class="RootBox">
+        <box class="BlockElementBox" element="root">
+
+          <box class="BlockElementBox" element="pre">
+
+            <box class="ParagraphBox">
+              <box class="LineBox">
+                <box class="DocumentTextBox" text="line1&#xa;" />
+              </box>
+              <box class="LineBox">
+                <box class="DocumentTextBox" text="&#xa;" />
+              </box>
+              <box class="LineBox">
+                <box class="DocumentTextBox" text="line2" />
+                <box class="PlaceholderBox"/>
+              </box>
+            </box>
+
+          </box>
+        </box>
+      </box>
+    </result>
+  </test>
+  
+  
+  <test id="CRLF" layoutWidth="100">
+    <doc><![CDATA[ <root><pre>line1&#xd;&#xa;line2</pre></root> ]]></doc>
+    <result>
+      <box class="RootBox">
+        <box class="BlockElementBox" element="root">
+
+          <box class="BlockElementBox" element="pre">
+
+            <box class="ParagraphBox">
+              <box class="LineBox">
+                <box class="DocumentTextBox" text="line1&#xa;" />
+              </box>
+              <box class="LineBox">
+                <box class="DocumentTextBox" text="line2" />
+                <box class="PlaceholderBox"/>
+              </box>
+            </box>
+
+          </box>
+        </box>
+      </box>
+    </result>
+  </test>
+  
+  
+  <test id="CRCRLF" layoutWidth="100">
+    <doc><![CDATA[ <root><pre>line1&#xd;&#xd;&#xa;line2</pre></root> ]]></doc>
+    <result>
+      <box class="RootBox">
+        <box class="BlockElementBox" element="root">
+
+          <box class="BlockElementBox" element="pre">
+
+            <box class="ParagraphBox">
+              <box class="LineBox">
+                <box class="DocumentTextBox" text="line1&#xa;" />
+              </box>
+              <box class="LineBox">
+                <box class="DocumentTextBox" text="&#xa;" />
+              </box>
+              <box class="LineBox">
+                <box class="DocumentTextBox" text="line2" />
+                <box class="PlaceholderBox"/>
+              </box>
+            </box>
+
+          </box>
+        </box>
+      </box>
+    </result>
+  </test>
+  
+  
+  <test id="CRLFCRLF" layoutWidth="100">
+    <doc><![CDATA[ <root><pre>line1&#xd;&#xa;&#xd;&#xa;line2</pre></root> ]]></doc>
+    <result>
+      <box class="RootBox">
+        <box class="BlockElementBox" element="root">
+
+          <box class="BlockElementBox" element="pre">
+
+            <box class="ParagraphBox">
+              <box class="LineBox">
+                <box class="DocumentTextBox" text="line1&#xa;" />
+              </box>
+              <box class="LineBox">
+                <box class="DocumentTextBox" text="&#xa;" />
+              </box>
+              <box class="LineBox">
+                <box class="DocumentTextBox" text="line2" />
+                <box class="PlaceholderBox"/>
+              </box>
+            </box>
+
+          </box>
+        </box>
+      </box>
+    </result>
+  </test>
+  
+  
+  <test id="Newline Inside Inline" layoutWidth="100">
+    <doc><![CDATA[ <root><pre><b>newline
+end</b></pre></root> ]]></doc>
+    <result>
+      <box class="RootBox">
+        <box class="BlockElementBox" element="root">
+
+          <box class="BlockElementBox" element="pre">
+
+            <box class="ParagraphBox">
+              <box class="LineBox">
+                <box class="PlaceholderBox"/>
+                <box class="InlineElementBox" element="b">
+                  <box class="DrawableBox"/>
+                  <box class="DocumentTextBox" text="newline&#10;" />
+                </box>
+              </box>
+              <box class="LineBox">
+                <box class="InlineElementBox" element="b">
+                  <box class="DocumentTextBox" text="end" />
+                  <box class="PlaceholderBox"/>
+                  <box class="DrawableBox"/>
+                </box>
+                <box class="PlaceholderBox"/>
+              </box>
+            </box>
+
+          </box>
+        </box>
+      </box>
+    </result>
+  </test>
+  
+  
+  
+</testcases>
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/tables.css b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/tables.css
new file mode 100644
index 0000000..ca4bc34
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/tables.css
@@ -0,0 +1,36 @@
+
+root {
+  display: block;
+}
+
+div {
+  display: block;
+}
+
+table {
+  display: table;
+}
+
+tbody {
+  display: table-row-group;
+}
+
+thead {
+  display: table-header-group;
+}
+
+tr {
+  display: table-row;
+}
+
+td {
+  display: table-cell;
+}
+
+b {
+  display: inline;
+}
+
+p {
+  display: block;
+}
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/tables.xml b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/tables.xml
new file mode 100644
index 0000000..4fd860e
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/tables.xml
@@ -0,0 +1,538 @@
+<?xml version='1.0'?>
+<testcases css="tables.css">
+
+  <test id="Table 1R 1C (empty)" layoutWidth="100">
+    <doc><![CDATA[ <root><table><tr><td></td></tr></table></root> ]]></doc>
+    <result>
+      <box class="RootBox">
+        <box class="BlockElementBox" element="root">
+          <box class="TableBox" element="table">
+            <box class="TableBodyBox">
+              <box class="TableRowGroupBox">
+                <box class="TableRowBox" element="tr">
+                  <box class="TableCellBox" element="td">
+                    <box class="ParagraphBox">
+                      <box class="LineBox">
+                        <box class="PlaceholderBox"/>
+                      </box>
+                    </box>
+                  </box>
+                </box>
+              </box>
+            </box>
+          </box>
+        </box>
+      </box>
+    </result>
+  </test>
+  
+  <test id="Table 1R 1C" layoutWidth="100">
+    <doc><![CDATA[ <root><table><tr><td>foo</td></tr></table></root> ]]></doc>
+    <result>
+      <box class="RootBox">
+        <box class="BlockElementBox" element="root">
+          <box class="TableBox" element="table">
+            <box class="TableBodyBox">
+              <box class="TableRowGroupBox">
+                <box class="TableRowBox" element="tr">
+                  <box class="TableCellBox" element="td">
+                    <box class="ParagraphBox">
+                      <box class="LineBox">
+                        <box class="DocumentTextBox" text="foo"/>
+                        <box class="PlaceholderBox"/>
+                      </box>
+                    </box>
+                  </box>
+                </box>
+              </box>
+            </box>
+          </box>
+        </box>
+      </box>
+    </result>
+  </test>
+  
+  <test id="Table 1R 2C" layoutWidth="100">
+    <doc><![CDATA[ <root><table><tr><td>foo</td><td>bar</td></tr></table></root> ]]></doc>
+    <result>
+      <box class="RootBox">
+        <box class="BlockElementBox" element="root">
+          <box class="TableBox" element="table">
+            <box class="TableBodyBox">
+              <box class="TableRowGroupBox">
+
+            <box class="TableRowBox" element="tr">
+
+              <box class="TableCellBox" element="td">
+                <box class="ParagraphBox">
+                  <box class="LineBox">
+                    <box class="DocumentTextBox" text="foo"/>
+                    <box class="PlaceholderBox"/>
+                  </box>
+                </box>
+              </box>
+
+              <box class="TableCellBox" element="td">
+                <box class="ParagraphBox">
+                  <box class="LineBox">
+                    <box class="DocumentTextBox" text="bar"/>
+                    <box class="PlaceholderBox"/>
+                  </box>
+                </box>
+              </box>
+
+            </box>
+            
+              </box>
+            </box>
+          </box>
+        </box>
+      </box>
+    </result>
+  </test>
+  
+  <test id="Table 1R 1 Anonymous Cell" layoutWidth="100">
+    <doc><![CDATA[ <root><table><tr>foo</tr></table></root> ]]></doc>
+    <result>
+      <box class="RootBox">
+        <box class="BlockElementBox" element="root">
+          <box class="TableBox" element="table">
+            <box class="TableBodyBox">
+              <box class="TableRowGroupBox">
+
+            <box class="TableRowBox" element="tr">
+              <box class="TableCellBox">
+                <box class="ParagraphBox">
+                  <box class="LineBox">
+                    <box class="DocumentTextBox" text="foo"/>
+                    <box class="PlaceholderBox"/>
+                  </box>
+                </box>
+              </box>
+            </box>
+
+              </box>
+            </box>
+          </box>
+        </box>
+      </box>
+    </result>
+  </test>
+  
+  <test id="Anonymous Cells 2" layoutWidth="100">
+    <doc><![CDATA[ <root><table><tr>foo <b>bar</b></tr></table></root> ]]></doc>
+    <result>
+      <box class="RootBox">
+        <box class="BlockElementBox" element="root">
+          <box class="TableBox" element="table">
+            <box class="TableBodyBox">
+              <box class="TableRowGroupBox">
+
+            <box class="TableRowBox" element="tr">
+              <box class="TableCellBox">
+                <box class="ParagraphBox">
+                  <box class="LineBox">
+                    <box class="DocumentTextBox" text="foo "/>
+                    <box class="PlaceholderBox"/>
+                    <box class="InlineElementBox" element="b">
+                      <box class="DrawableBox"/>
+                      <box class="DocumentTextBox" text="bar"/>
+                      <box class="PlaceholderBox"/>
+                      <box class="DrawableBox"/>
+                    </box>
+                    <box class="PlaceholderBox"/>
+                  </box>
+                </box>
+              </box>
+            </box>
+
+              </box>
+            </box>
+          </box>
+        </box>
+      </box>
+    </result>
+  </test>
+  
+  <test id="Anonymous Cells 3" layoutWidth="100">
+    <doc><![CDATA[ <root><table><tr>foo <p>bar</p></tr></table></root> ]]></doc>
+    <result>
+      <box class="RootBox">
+        <box class="BlockElementBox" element="root">
+          <box class="TableBox" element="table">
+            <box class="TableBodyBox">
+              <box class="TableRowGroupBox">
+          
+          
+            <box class="TableRowBox" element="tr">
+              <box class="TableCellBox">
+                
+                <box class="ParagraphBox">
+                  <box class="LineBox">
+                    <box class="DocumentTextBox" text="foo"/>
+                    <box class="PlaceholderBox"/>
+                  </box>
+                </box>
+                
+                <box class="BlockElementBox" element="p">
+                  <box class="ParagraphBox">
+                    <box class="LineBox">
+                      <box class="DocumentTextBox" text="bar"/>
+                      <box class="PlaceholderBox"/>
+                    </box>
+                  </box>
+                </box>
+                
+              </box>
+            </box>
+            
+              </box>
+            </box>
+          </box>
+        </box>
+      </box>
+    </result>
+  </test>
+  
+  <test id="Anonymous Cells 4" layoutWidth="100">
+    <doc><![CDATA[ <root><table><tr>foo <td>bar</td></tr></table></root> ]]></doc>
+    <result>
+      <box class="RootBox">
+        <box class="BlockElementBox" element="root">
+          <box class="TableBox" element="table">
+            <box class="TableBodyBox">
+              <box class="TableRowGroupBox">
+
+            <box class="TableRowBox" element="tr">
+            
+              <box class="TableCellBox">
+                <box class="ParagraphBox">
+                  <box class="LineBox">
+                    <box class="DocumentTextBox" text="foo"/>
+                    <box class="PlaceholderBox"/>
+                  </box>
+                </box>
+              </box>
+              
+              <box class="TableCellBox" element="td">
+                <box class="ParagraphBox">
+                  <box class="LineBox">
+                    <box class="DocumentTextBox" text="bar"/>
+                    <box class="PlaceholderBox"/>
+                  </box>
+                </box>
+              </box>
+                
+            </box>
+            
+              </box>
+            </box>
+          </box>
+        </box>
+      </box>
+    </result>
+  </test>
+  
+  
+  <test id="Anonymous Cells 5" layoutWidth="100">
+    <doc><![CDATA[ <root><table><tr><td>bar</td> foo</tr></table></root> ]]></doc>
+    <result>
+      <box class="RootBox">
+        <box class="BlockElementBox" element="root">
+          <box class="TableBox" element="table">
+            <box class="TableBodyBox">
+              <box class="TableRowGroupBox">
+
+            <box class="TableRowBox" element="tr">
+            
+              <box class="TableCellBox" element="td">
+                <box class="ParagraphBox">
+                  <box class="LineBox">
+                    <box class="DocumentTextBox" text="bar"/>
+                    <box class="PlaceholderBox"/>
+                  </box>
+                </box>
+              </box>
+                
+              <box class="TableCellBox">
+                <box class="ParagraphBox">
+                  <box class="LineBox">
+                    <box class="DocumentTextBox" text="foo"/>
+                    <box class="PlaceholderBox"/>
+                  </box>
+                </box>
+              </box>
+              
+            </box>
+            
+              </box>
+            </box>
+          </box>
+        </box>
+      </box>
+    </result>
+  </test>
+
+
+  <test id="Empty Table" layoutWidth="100">
+    <doc><![CDATA[ <root><table/></root> ]]></doc>
+    <result>
+      <box class="RootBox">
+        <box class="BlockElementBox" element="root">
+          <box class="TableBox">
+          </box>
+        </box>
+      </box>
+    </result>
+  </test>
+  
+  
+  <test id="Anonymous Table and Row - Empty" layoutWidth="100">
+    <doc><![CDATA[ <root><td></td></root> ]]></doc>
+    <result>
+      <box class="RootBox">
+        <box class="BlockElementBox" element="root">
+          <box class="TableBox">
+            <box class="TableBodyBox">
+              <box class="TableRowGroupBox">
+
+            <box class="TableRowBox">
+            
+              <box class="TableCellBox" element="td">
+                <box class="ParagraphBox">
+                  <box class="LineBox">
+                    <box class="PlaceholderBox"/>
+                  </box>
+                </box>
+              </box>
+                
+            </box>
+            
+              </box>
+            </box>
+          </box>
+        </box>
+      </box>
+    </result>
+  </test>
+  
+  <test id="Anonymous Table and Row" layoutWidth="100">
+    <doc><![CDATA[ <root><td>bar</td></root> ]]></doc>
+    <result>
+      <box class="RootBox">
+        <box class="BlockElementBox" element="root">
+          <box class="TableBox">
+            <box class="TableBodyBox">
+              <box class="TableRowGroupBox">
+
+            <box class="TableRowBox">
+            
+              <box class="TableCellBox" element="td">
+                <box class="ParagraphBox">
+                  <box class="LineBox">
+                    <box class="DocumentTextBox" text="bar"/>
+                    <box class="PlaceholderBox"/>
+                  </box>
+                </box>
+              </box>
+                
+            </box>
+            
+              </box>
+            </box>
+          </box>
+        </box>
+      </box>
+    </result>
+  </test>
+  
+  <test id="Anonymous Table and Cell" layoutWidth="100">
+    <doc><![CDATA[ <root><tr>bar</tr></root> ]]></doc>
+    <result>
+      <box class="RootBox">
+        <box class="BlockElementBox" element="root">
+          <box class="TableBox">
+            <box class="TableBodyBox">
+              <box class="TableRowGroupBox">
+
+            <box class="TableRowBox" element="tr">
+            
+              <box class="TableCellBox">
+                <box class="ParagraphBox">
+                  <box class="LineBox">
+                    <box class="DocumentTextBox" text="bar"/>
+                    <box class="PlaceholderBox"/>
+                  </box>
+                </box>
+              </box>
+                
+            </box>
+            
+              </box>
+            </box>
+          </box>
+        </box>
+      </box>
+    </result>
+  </test>
+  
+  
+  <test id="Anonymous Row and Cell" layoutWidth="100">
+    <doc><![CDATA[ <root><table>bar</table></root> ]]></doc>
+    <result>
+      <box class="RootBox">
+        <box class="BlockElementBox" element="root">
+          <box class="TableBox" element="table">
+            <box class="TableBodyBox">
+              <box class="TableRowGroupBox">
+
+            <box class="TableRowBox">
+            
+              <box class="TableCellBox">
+                <box class="ParagraphBox">
+                  <box class="LineBox">
+                    <box class="DocumentTextBox" text="bar"/>
+                    <box class="PlaceholderBox"/>
+                  </box>
+                </box>
+              </box>
+                
+            </box>
+            
+              </box>
+            </box>
+          </box>
+        </box>
+      </box>
+    </result>
+  </test>
+
+  
+  <test id="Empty table-row-group" layoutWidth="100">
+    <doc><![CDATA[ <root><table><tbody/></table></root> ]]></doc>
+    <result>
+      <box class="RootBox">
+        <box class="BlockElementBox" element="root">
+          <box class="TableBox" element="table">
+            <box class="TableBodyBox">
+              <box class="TableRowGroupBox" element="tbody">
+
+              </box>
+            </box>
+          </box>
+        </box>
+      </box>
+    </result>
+  </test>
+
+
+  
+  <test id="table-row-group With Content" layoutWidth="100">
+    <doc><![CDATA[ <root><table><tbody>bar</tbody></table></root> ]]></doc>
+    <result>
+      <box class="RootBox">
+        <box class="BlockElementBox" element="root">
+          <box class="TableBox" element="table">
+            <box class="TableBodyBox">
+              <box class="TableRowGroupBox" element="tbody">
+
+            <box class="TableRowBox">
+            
+              <box class="TableCellBox">
+                <box class="ParagraphBox">
+                  <box class="LineBox">
+                    <box class="DocumentTextBox" text="bar"/>
+                    <box class="PlaceholderBox"/>
+                  </box>
+                </box>
+              </box>
+                
+            </box>
+            
+              </box>
+            </box>
+          </box>
+        </box>
+      </box>
+    </result>
+  </test>
+  
+  
+  <test id="Empty table-header-group" layoutWidth="100">
+    <doc><![CDATA[ <root><table><thead/></table></root> ]]></doc>
+    <result>
+      <box class="RootBox">
+        <box class="BlockElementBox" element="root">
+          <box class="TableBox" element="table">
+            <box class="TableBodyBox">
+              <box class="TableRowGroupBox" element="thead">
+
+              </box>
+            </box>
+          </box>
+        </box>
+      </box>
+    </result>
+  </test>
+
+
+  
+  <test id="table-header-group With Content" layoutWidth="100">
+    <doc><![CDATA[ <root><table><thead>bar</thead></table></root> ]]></doc>
+    <result>
+      <box class="RootBox">
+        <box class="BlockElementBox" element="root">
+          <box class="TableBox" element="table">
+            <box class="TableBodyBox">
+              <box class="TableRowGroupBox" element="thead">
+
+            <box class="TableRowBox">
+            
+              <box class="TableCellBox">
+                <box class="ParagraphBox">
+                  <box class="LineBox">
+                    <box class="DocumentTextBox" text="bar"/>
+                    <box class="PlaceholderBox"/>
+                  </box>
+                </box>
+              </box>
+                
+            </box>
+            
+              </box>
+            </box>
+          </box>
+        </box>
+      </box>
+    </result>
+  </test>
+  
+  
+  <test id="Table inna Table" layoutWidth="100">
+    <doc><![CDATA[ <root><table><table/></table></root> ]]></doc>
+    <result>
+      <box class="RootBox">
+        <box class="BlockElementBox" element="root">
+          <box class="TableBox" element="table">
+            <box class="TableBodyBox">
+              <box class="TableRowGroupBox">
+
+                <box class="TableRowBox">
+            
+                  <box class="TableCellBox">
+                    <box class="TableBox">
+                    </box>
+                  </box>
+                
+                </box>
+            
+              </box>
+            </box>
+          </box>
+        </box>
+      </box>
+    </result>
+  </test>
+  
+  
+</testcases>
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/test.css b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/test.css
new file mode 100644
index 0000000..42ccd67
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/internal/layout/test.css
@@ -0,0 +1,46 @@
+/*
+ * Style sheet used for layout unit testing.
+ */
+root {
+  display: block;
+  font: 10pt monospaced;
+}
+
+
+beforeBlock {
+  display: block;
+}
+
+beforeBlock:before {
+  display: block;
+  content: 'Before';
+}
+
+b {
+  display: inline;
+}
+
+p {
+  display: block;
+}
+
+.blockAfter:after {
+  display: block;
+  content: 'AFTER';
+}
+
+.blockBefore:before {
+  display: block;
+  content: 'BEFORE';
+}
+
+.inlineAfter:after {
+  display: inline;
+  content: 'after';
+}
+
+.inlineBefore:before {
+  display: inline;
+  content: 'before';
+}
+
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/tests/VEXCoreTestPlugin.java b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/tests/VEXCoreTestPlugin.java
new file mode 100644
index 0000000..4b98420
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.core.tests/src/org/eclipse/wst/xml/vex/core/tests/VEXCoreTestPlugin.java
@@ -0,0 +1,50 @@
+package org.eclipse.wst.xml.vex.core.tests;
+
+import org.eclipse.ui.plugin.AbstractUIPlugin;
+import org.osgi.framework.BundleContext;
+
+/**
+ * The activator class controls the plug-in life cycle
+ */
+public class VEXCoreTestPlugin extends AbstractUIPlugin {
+
+	// The plug-in ID
+	public static final String PLUGIN_ID = "org.eclipse.wst.xml.vex.core.tests";
+
+	// The shared instance
+	private static VEXCoreTestPlugin plugin;
+	
+	/**
+	 * The constructor
+	 */
+	public VEXCoreTestPlugin() {
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext)
+	 */
+	public void start(BundleContext context) throws Exception {
+		super.start(context);
+		plugin = this;
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext)
+	 */
+	public void stop(BundleContext context) throws Exception {
+		plugin = null;
+		super.stop(context);
+	}
+
+	/**
+	 * Returns the shared instance
+	 *
+	 * @return the shared instance
+	 */
+	public static VEXCoreTestPlugin getDefault() {
+		return plugin;
+	}
+
+}
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/.classpath b/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/.classpath
new file mode 100644
index 0000000..64c5e31
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/>
+	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/.project b/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/.project
new file mode 100644
index 0000000..8f21bd3
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/.project
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>org.eclipse.wst.xml.vex.ui.tests</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>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.pde.PluginNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/.settings/org.eclipse.jdt.core.prefs b/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..9640e53
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,7 @@
+#Wed Oct 01 02:46:26 GMT 2008
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5
+org.eclipse.jdt.core.compiler.compliance=1.5
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.5
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/META-INF/MANIFEST.MF b/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..017c090
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/META-INF/MANIFEST.MF
@@ -0,0 +1,13 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: VEX UI Test Plugin
+Bundle-SymbolicName: org.eclipse.wst.xml.vex.ui.tests
+Bundle-Version: 0.5.0.qualifier
+Bundle-Activator: org.eclipse.wst.xml.vex.ui.tests.Activator
+Bundle-Vendor: Eclipse
+Require-Bundle: org.eclipse.ui,
+ org.eclipse.core.runtime,
+ org.eclipse.wst.xml.vex.ui;bundle-version="0.5.0",
+ org.junit;bundle-version="3.8.1"
+Bundle-RequiredExecutionEnvironment: J2SE-1.5
+Bundle-ActivationPolicy: lazy
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/build.properties b/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/build.properties
new file mode 100644
index 0000000..34d2e4d
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/build.properties
@@ -0,0 +1,4 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/src/org/eclipse/wst/xml/vex/ui/internal/tests/AssociationTest.java b/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/src/org/eclipse/wst/xml/vex/ui/internal/tests/AssociationTest.java
new file mode 100644
index 0000000..0197660
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/src/org/eclipse/wst/xml/vex/ui/internal/tests/AssociationTest.java
@@ -0,0 +1,298 @@
+/*******************************************************************************
+ * 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.ui.internal.tests;
+
+import java.util.Collection;
+
+import org.eclipse.wst.xml.vex.ui.internal.editor.Association;
+
+import junit.framework.TestCase;
+
+public class AssociationTest extends TestCase {
+
+    public void testAddRemove() throws Exception {
+        
+        Association assoc = new Association();
+        
+        Collection c;
+        
+        c = assoc.getRightsForLeft("l0");
+        assertNotNull(c);
+        assertEquals(0, c.size());
+        
+        c = assoc.getLeftsForRight("r0");
+        assertNotNull(c);
+        assertEquals(0, c.size());
+        
+        assoc.add("l0", "r0");
+        
+        c = assoc.getRightsForLeft("l0");
+        assertNotNull(c);
+        assertEquals(1, c.size());
+        assertEquals("r0", c.toArray()[0]);
+        
+        c = assoc.getLeftsForRight("r0");
+        assertNotNull(c);
+        assertEquals(1, c.size());
+        assertEquals("l0", c.toArray()[0]);
+        
+        assoc.add("l0", "r1");
+        assoc.add("l1", "r0");
+        assoc.add("l1", "r1");
+        
+        c = assoc.getRightsForLeft("l0");
+        assertNotNull(c);
+        assertEquals(2, c.size());
+        assertTrue(c.contains("r0"));
+        assertTrue(c.contains("r1"));
+        
+        c = assoc.getRightsForLeft("l1");
+        assertNotNull(c);
+        assertEquals(2, c.size());
+        assertTrue(c.contains("r0"));
+        assertTrue(c.contains("r1"));
+        
+        c = assoc.getLeftsForRight("r0");
+        assertNotNull(c);
+        assertEquals(2, c.size());
+        assertTrue(c.contains("l0"));
+        assertTrue(c.contains("l1"));
+        
+        c = assoc.getLeftsForRight("r1");
+        assertNotNull(c);
+        assertEquals(2, c.size());
+        assertTrue(c.contains("l0"));
+        assertTrue(c.contains("l1"));
+        
+        assoc.remove("l0", "r0");
+
+        c = assoc.getRightsForLeft("l0");
+        assertNotNull(c);
+        assertEquals(1, c.size());
+        assertTrue(c.contains("r1"));
+        
+        c = assoc.getRightsForLeft("l1");
+        assertNotNull(c);
+        assertEquals(2, c.size());
+        assertTrue(c.contains("r0"));
+        assertTrue(c.contains("r1"));
+        
+        c = assoc.getLeftsForRight("r0");
+        assertNotNull(c);
+        assertEquals(1, c.size());
+        assertTrue(c.contains("l1"));
+        
+        c = assoc.getLeftsForRight("r1");
+        assertNotNull(c);
+        assertEquals(2, c.size());
+        assertTrue(c.contains("l0"));
+        assertTrue(c.contains("l1"));
+        
+        assoc.remove("l0", "r1");
+        
+        c = assoc.getRightsForLeft("l0");
+        assertNotNull(c);
+        assertEquals(0, c.size());
+        
+        c = assoc.getRightsForLeft("l1");
+        assertNotNull(c);
+        assertEquals(2, c.size());
+        assertTrue(c.contains("r0"));
+        assertTrue(c.contains("r1"));
+        
+        c = assoc.getLeftsForRight("r0");
+        assertNotNull(c);
+        assertEquals(1, c.size());
+        assertTrue(c.contains("l1"));
+        
+        c = assoc.getLeftsForRight("r1");
+        assertNotNull(c);
+        assertEquals(1, c.size());
+        assertTrue(c.contains("l1"));
+        
+        assoc.remove("l1", "r0");
+        
+        c = assoc.getRightsForLeft("l0");
+        assertNotNull(c);
+        assertEquals(0, c.size());
+        
+        c = assoc.getRightsForLeft("l1");
+        assertNotNull(c);
+        assertEquals(1, c.size());
+        assertTrue(c.contains("r1"));
+        
+        c = assoc.getLeftsForRight("r0");
+        assertNotNull(c);
+        assertEquals(0, c.size());
+        
+        c = assoc.getLeftsForRight("r1");
+        assertNotNull(c);
+        assertEquals(1, c.size());
+        assertTrue(c.contains("l1"));
+        
+        assoc.remove("l1", "r1");
+        
+        c = assoc.getRightsForLeft("l0");
+        assertNotNull(c);
+        assertEquals(0, c.size());
+        
+        c = assoc.getRightsForLeft("l1");
+        assertNotNull(c);
+        assertEquals(0, c.size());
+        
+        c = assoc.getLeftsForRight("r0");
+        assertNotNull(c);
+        assertEquals(0, c.size());
+        
+        c = assoc.getLeftsForRight("r1");
+        assertNotNull(c);
+        assertEquals(0, c.size());
+    }
+    
+    public void testRemoveLeft() throws Exception {
+
+        Association assoc = new Association();
+        
+        Collection c;
+        
+        assoc.add("l0", "r0");
+        assoc.add("l0", "r1");
+        assoc.add("l1", "r0");
+        assoc.add("l1", "r1");
+
+        
+        // These should be no-ops
+        assoc.removeLeft("r0");
+        assoc.removeLeft("r1");
+        assoc.removeRight("l0");
+        assoc.removeRight("l1");
+        
+        c = assoc.getRightsForLeft("l0");
+        assertNotNull(c);
+        assertEquals(2, c.size());
+        assertTrue(c.contains("r0"));
+        assertTrue(c.contains("r1"));
+        
+        c = assoc.getRightsForLeft("l1");
+        assertNotNull(c);
+        assertEquals(2, c.size());
+        assertTrue(c.contains("r0"));
+        assertTrue(c.contains("r1"));
+        
+        c = assoc.getLeftsForRight("r0");
+        assertNotNull(c);
+        assertEquals(2, c.size());
+        assertTrue(c.contains("l0"));
+        assertTrue(c.contains("l1"));
+        
+        c = assoc.getLeftsForRight("r1");
+        assertNotNull(c);
+        assertEquals(2, c.size());
+        assertTrue(c.contains("l0"));
+        assertTrue(c.contains("l1"));
+        
+
+        assoc.removeLeft("l0");
+        
+        c = assoc.getRightsForLeft("l0");
+        assertNotNull(c);
+        assertEquals(0, c.size());
+        
+        c = assoc.getRightsForLeft("l1");
+        assertNotNull(c);
+        assertEquals(2, c.size());
+        assertTrue(c.contains("r0"));
+        assertTrue(c.contains("r1"));
+        
+        c = assoc.getLeftsForRight("r0");
+        assertNotNull(c);
+        assertEquals(1, c.size());
+        assertTrue(c.contains("l1"));
+        
+        c = assoc.getLeftsForRight("r1");
+        assertNotNull(c);
+        assertEquals(1, c.size());
+        assertTrue(c.contains("l1"));
+        
+        
+        assoc.removeLeft("l1");
+
+        c = assoc.getRightsForLeft("l0");
+        assertNotNull(c);
+        assertEquals(0, c.size());
+        
+        c = assoc.getRightsForLeft("l1");
+        assertNotNull(c);
+        assertEquals(0, c.size());
+        
+        c = assoc.getLeftsForRight("r0");
+        assertNotNull(c);
+        assertEquals(0, c.size());
+        
+        c = assoc.getLeftsForRight("r1");
+        assertNotNull(c);
+        assertEquals(0, c.size());
+    }
+
+
+    public void testRemoveRight() throws Exception {
+
+        Association assoc = new Association();
+        
+        Collection c;
+        
+        assoc.add("l0", "r0");
+        assoc.add("l0", "r1");
+        assoc.add("l1", "r0");
+        assoc.add("l1", "r1");
+
+        assoc.removeRight("r0");
+        
+        c = assoc.getRightsForLeft("l0");
+        assertNotNull(c);
+        assertEquals(1, c.size());
+        assertTrue(c.contains("r1"));
+        
+        c = assoc.getRightsForLeft("l1");
+        assertNotNull(c);
+        assertEquals(1, c.size());
+        assertTrue(c.contains("r1"));
+        
+        c = assoc.getLeftsForRight("r0");
+        assertNotNull(c);
+        assertEquals(0, c.size());
+        
+        c = assoc.getLeftsForRight("r1");
+        assertNotNull(c);
+        assertEquals(2, c.size());
+        assertTrue(c.contains("l0"));
+        assertTrue(c.contains("l1"));
+        
+        
+        assoc.removeRight("r1");
+
+        c = assoc.getRightsForLeft("l0");
+        assertNotNull(c);
+        assertEquals(0, c.size());
+        
+        c = assoc.getRightsForLeft("l1");
+        assertNotNull(c);
+        assertEquals(0, c.size());
+        
+        c = assoc.getLeftsForRight("r0");
+        assertNotNull(c);
+        assertEquals(0, c.size());
+        
+        c = assoc.getLeftsForRight("r1");
+        assertNotNull(c);
+        assertEquals(0, c.size());
+    }
+}
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/src/org/eclipse/wst/xml/vex/ui/internal/tests/ResourceTrackerTest.java b/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/src/org/eclipse/wst/xml/vex/ui/internal/tests/ResourceTrackerTest.java
new file mode 100644
index 0000000..676fd92
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/src/org/eclipse/wst/xml/vex/ui/internal/tests/ResourceTrackerTest.java
@@ -0,0 +1,23 @@
+/*******************************************************************************
+ * 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.ui.internal.tests;
+
+import junit.framework.TestCase;
+
+/**
+ * Test the ResourceTracker class.
+ */
+public class ResourceTrackerTest extends TestCase {
+    
+    public void testAll() throws Exception {
+        
+    }
+}
diff --git a/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/src/org/eclipse/wst/xml/vex/ui/tests/Activator.java b/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/src/org/eclipse/wst/xml/vex/ui/tests/Activator.java
new file mode 100644
index 0000000..dd66697
--- /dev/null
+++ b/sourceediting/tests/org.eclipse.wst.xml.vex.ui.tests/src/org/eclipse/wst/xml/vex/ui/tests/Activator.java
@@ -0,0 +1,50 @@
+package org.eclipse.wst.xml.vex.ui.tests;
+
+import org.eclipse.ui.plugin.AbstractUIPlugin;
+import org.osgi.framework.BundleContext;
+
+/**
+ * The activator class controls the plug-in life cycle
+ */
+public class Activator extends AbstractUIPlugin {
+
+	// The plug-in ID
+	public static final String PLUGIN_ID = "org.eclipse.wst.xml.vex.ui.tests";
+
+	// The shared instance
+	private static Activator plugin;
+	
+	/**
+	 * The constructor
+	 */
+	public Activator() {
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext)
+	 */
+	public void start(BundleContext context) throws Exception {
+		super.start(context);
+		plugin = this;
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext)
+	 */
+	public void stop(BundleContext context) throws Exception {
+		plugin = null;
+		super.stop(context);
+	}
+
+	/**
+	 * Returns the shared instance
+	 *
+	 * @return the shared instance
+	 */
+	public static Activator getDefault() {
+		return plugin;
+	}
+
+}