You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@corinthia.apache.org by ja...@apache.org on 2015/08/17 10:50:12 UTC

[20/28] incubator-corinthia git commit: included MOC compiler for Qt implementation

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9bf02bb2/experiments/editorFramework/src/Javascript_Layer_0/Selection.js
----------------------------------------------------------------------
diff --git a/experiments/editorFramework/src/Javascript_Layer_0/Selection.js b/experiments/editorFramework/src/Javascript_Layer_0/Selection.js
new file mode 100644
index 0000000..c4f8efb
--- /dev/null
+++ b/experiments/editorFramework/src/Javascript_Layer_0/Selection.js
@@ -0,0 +1,1430 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// FIXME: cursor does not display correctly if it is after a space at the end of the line
+
+var Selection_isMarked;
+var Selection_get;
+var Selection_set;
+var Selection_clear;
+
+var Selection_update;
+var Selection_selectAll;
+var Selection_selectParagraph;
+var Selection_selectWordAtCursor;
+var Selection_dragSelectionBegin;
+var Selection_dragSelectionUpdate;
+var Selection_moveStartLeft;
+var Selection_moveStartRight;
+var Selection_moveEndLeft;
+var Selection_moveEndRight;
+var Selection_setSelectionStartAtCoords;
+var Selection_setSelectionEndAtCoords;
+var Selection_setTableSelectionEdgeAtCoords;
+var Selection_setEmptySelectionAt;
+var Selection_deleteRangeContents;
+var Selection_deleteContents;
+var Selection_clearSelection;
+var Selection_preserveWhileExecuting;
+var Selection_posAtStartOfWord;
+var Selection_posAtEndOfWord;
+var Selection_preferElementPositions;
+var Selection_print;
+
+(function() {
+
+    var HANDLE_NONE = 0;
+    var HANDLE_START = 1;
+    var HANDLE_END = 2;
+
+    var activeHandle = HANDLE_NONE;
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    //                                                                                            //
+    //                                 Selection getter and setter                                //
+    //                                                                                            //
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+
+    var Selection_setInternal;
+
+    (function() {
+
+        var selection = new Object();
+
+        Selection_isMarked = function()
+        {
+            if (selection.value == null)
+                return null;
+            else
+                return selection.value.isMarked;
+        }
+
+        // public
+        Selection_get = function()
+        {
+            if (selection.value == null)
+                return null;
+            else
+                return new Range(selection.value.startNode,selection.value.startOffset,
+                                 selection.value.endNode,selection.value.endOffset);
+        }
+
+        // public
+        Selection_setInternal =
+            function(newStartNode,newStartOffset,newEndNode,newEndOffset,isMarked)
+        {
+            var range = new Range(newStartNode,newStartOffset,newEndNode,newEndOffset);
+            if (!Range_isForwards(range))
+                range = new Range(newEndNode,newEndOffset,newStartNode,newStartOffset);
+            range = boundaryCompliantRange(range);
+
+            UndoManager_setProperty(selection,"value",
+                                    { startNode: range.start.node,
+                                      startOffset: range.start.offset,
+                                      endNode: range.end.node,
+                                      endOffset: range.end.offset,
+                                      isMarked: isMarked });
+        }
+
+        Selection_set = function(newStartNode,newStartOffset,newEndNode,newEndOffset,
+                                 keepActiveHandle,isMarked)
+        {
+            Selection_setInternal(newStartNode,newStartOffset,newEndNode,newEndOffset,isMarked);
+            Selection_update();
+            if (!keepActiveHandle)
+                activeHandle = HANDLE_NONE;
+        }
+
+        // public
+        Selection_clear = function()
+        {
+            UndoManager_setProperty(selection,"value",null);
+            Selection_update();
+        }
+    })();
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    //                                                                                            //
+    //                                  Other selection functions                                 //
+    //                                                                                            //
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+
+    var selectionDivs = new Array();
+    var selectionHighlights = new Array();
+    var tableSelection = null;
+
+    // private
+    updateTableSelection = function(selRange)
+    {
+        tableSelection = Tables_regionFromRange(selRange);
+        if (tableSelection == null)
+            return false;
+
+        Range_trackWhileExecuting(selRange,function() {
+
+            removeSelectionHighlights(getRangeData(null));
+
+            var sel = tableSelection;
+
+            var topLeftTD = Table_get(sel.structure,sel.top,sel.left);
+            var bottomRightTD = Table_get(sel.structure,sel.bottom,sel.right);
+
+            var topLeftRect = topLeftTD.element.getBoundingClientRect();
+            var bottomRightRect = bottomRightTD.element.getBoundingClientRect();
+
+            var left = topLeftRect.left;
+            var top = topLeftRect.top;
+
+            var bottom = bottomRightRect.bottom;
+            var right = bottomRightRect.right;
+
+            var x = left;
+            var y = top;
+            var width = right - left;
+            var height = bottom - top;
+
+            x += window.scrollX;
+            y += window.scrollY;
+
+            var div = makeSelectionDiv();
+            DOM_setAttribute(div,"class",Keys.SELECTION_HIGHLIGHT);
+            DOM_setStyleProperties(div,{ "position": "absolute",
+                                         "left": x+"px",
+                                         "top": y+"px",
+                                         "width": width+"px",
+                                         "height": height+"px",
+                                         "background-color": "rgb(201,221,238)",
+                                         "z-index": -1 });
+
+            setTableEdges(x,y,width,height);
+            setEditorHandles({ type: "table", x: x, y: y, width: width, height: height });
+        });
+
+        Selection_setInternal(selRange.start.node,selRange.start.offset,
+                              selRange.end.node,selRange.end.offset);
+
+        return true;
+    }
+
+    function makeSelectionDiv()
+    {
+        var div = DOM_createElement(document,"DIV");
+        DOM_appendChild(document.body,div);
+        selectionDivs.push(div);
+        return div;
+    }
+
+    function setTableEdges(x,y,width,height)
+    {
+        var left = makeSelectionDiv();
+        var right = makeSelectionDiv();
+        var top = makeSelectionDiv();
+        var bottom = makeSelectionDiv();
+
+        var thick = 2;
+        width++;
+        height++;
+        setBoxCoords(left,x-thick,y-thick,thick,height+2*thick);
+        setBoxCoords(right,x+width,y-thick,thick,height+2*thick);
+        setBoxCoords(top,x-thick,y-thick,width+2*thick,thick);
+        setBoxCoords(bottom,x-thick,y+height,width+2*thick,thick);
+
+        function setBoxCoords(box,x,y,width,height)
+        {
+            DOM_setStyleProperties(box,{ "position": "absolute",
+                                         "left": x+"px",
+                                         "top": y+"px",
+                                         "width": width+"px",
+                                         "height": height+"px",
+                                         "background-color": "blue",
+                                         "z-index": 1 });
+        }
+    }
+
+    var editorHandles = { type: "none" };
+    function setEditorHandles(info)
+    {
+        var oldEditorHandles = editorHandles;
+        editorHandles = info;
+        UndoManager_addAction(function() {
+            setEditorHandles(oldEditorHandles);
+        });
+        if (info.type == "cursor") {
+            Editor_setCursor(info.left,info.top,info.width,info.height);
+        }
+        else if (info.type == "selection") {
+            if (!Selection_isMarked()) {
+                Editor_setSelectionHandles(info.x1,info.y1,
+                                           info.height1,info.x2,info.y2,info.height2);
+            }
+            Editor_setSelectionBounds(info.boundsLeft,info.boundsTop,
+                                      info.boundsRight,info.boundsBottom);
+        }
+        else if (info.type == "none") {
+            Editor_clearSelectionHandlesAndCursor();
+        }
+        else if (info.type == "table") {
+            Editor_setTableSelection(info.x,info.y,info.width,info.height);
+        }
+        else {
+            throw new Error("setEditorHandles: unknown type "+type);
+        }
+    }
+
+    function getPrevHighlightText(node)
+    {
+        if ((node.previousSibling != null) &&
+            isSelectionHighlight(node.previousSibling) &&
+            (node.previousSibling.lastChild != null) &&
+            (node.previousSibling.lastChild.nodeType == Node.TEXT_NODE))
+            return node.previousSibling.lastChild;
+        else
+            return null;
+    }
+
+    function getNextHighlightText(node)
+    {
+        if ((node.nextSibling != null) &&
+            isSelectionHighlight(node.nextSibling) &&
+            (node.nextSibling.firstChild != null) &&
+            (node.nextSibling.firstChild.nodeType == Node.TEXT_NODE))
+            return node.nextSibling.firstChild;
+        else
+            return null;
+    }
+
+    function getTextNodeBefore(node)
+    {
+        var prev = node.previousSibling;
+        if ((prev != null) && (prev.nodeType == Node.TEXT_NODE)) {
+            return prev;
+        }
+        else {
+            var text = DOM_createTextNode(document,"");
+            DOM_insertBefore(node.parentNode,text,node);
+            return text;
+        }
+    }
+
+    function getTextNodeAfter(node)
+    {
+        var next = node.nextSibling;
+        if ((next != null) && (next.nodeType == Node.TEXT_NODE)) {
+            return next;
+        }
+        else {
+            var text = DOM_createTextNode(document,"");
+            DOM_insertBefore(node.parentNode,text,node.nextSibling);
+            return text;
+        }
+    }
+
+    function setSelectionHighlights(highlights)
+    {
+        UndoManager_addAction(setSelectionHighlights,selectionHighlights);
+        selectionHighlights = highlights;
+    }
+
+    function createSelectionHighlights(data)
+    {
+        var newHighlights = arrayCopy(selectionHighlights);
+
+        var outermost = data.outermost;
+        for (var i = 0; i < outermost.length; i++) {
+            recurse(outermost[i]);
+        }
+
+        setSelectionHighlights(newHighlights);
+
+        function recurse(node)
+        {
+            if (isSpecialBlockNode(node)) {
+                if (!isSelectionHighlight(node.parentNode)) {
+                    var wrapped = DOM_wrapNode(node,"DIV");
+                    DOM_setAttribute(wrapped,"class",Keys.SELECTION_CLASS);
+                    newHighlights.push(wrapped);
+                }
+            }
+            else if (isNoteNode(node)) {
+                if (!isSelectionHighlight(node.parentNode)) {
+                    var wrapped = DOM_wrapNode(node,"SPAN");
+                    DOM_setAttribute(wrapped,"class",Keys.SELECTION_CLASS);
+                    newHighlights.push(wrapped);
+                }
+            }
+            else if (node.nodeType == Node.TEXT_NODE) {
+                createTextHighlight(node,data,newHighlights);
+            }
+            else {
+                var next;
+                for (var child = node.firstChild; child != null; child = next) {
+                    next = child.nextSibling;
+                    recurse(child);
+                }
+            }
+        }
+    }
+
+    function createTextHighlight(node,data,newHighlights)
+    {
+        var selRange = data.range;
+        if (isSelectionHighlight(node.parentNode)) {
+
+            if ((node == selRange.end.node) && (node.nodeValue.length > selRange.end.offset)) {
+                var destTextNode = getTextNodeAfter(node.parentNode);
+                DOM_moveCharacters(node,
+                                   selRange.end.offset,
+                                   node.nodeValue.length,
+                                   destTextNode,0,
+                                   true,false);
+            }
+            if ((node == selRange.start.node) && (selRange.start.offset > 0)) {
+                var destTextNode = getTextNodeBefore(node.parentNode);
+                DOM_moveCharacters(node,
+                                   0,
+                                   selRange.start.offset,
+                                   destTextNode,destTextNode.nodeValue.length,
+                                   false,true);
+            }
+
+            return;
+        }
+
+        var anext;
+        for (var a = node; a != null; a = anext) {
+            anext = a.parentNode;
+            if (isSelectionHighlight(a))
+                DOM_removeNodeButKeepChildren(a);
+        }
+
+        if (node == selRange.end.node) {
+            if (isWhitespaceString(node.nodeValue.substring(0,selRange.end.offset)))
+                return;
+            Formatting_splitTextAfter(selRange.end,
+                                      function() { return true; });a
+        }
+
+
+        if (node == selRange.start.node) {
+            if (isWhitespaceString(node.nodeValue.substring(selRange.start.offset)))
+                return;
+            Formatting_splitTextBefore(selRange.start,
+                                       function() { return true; });
+        }
+
+        var prevText = getPrevHighlightText(node);
+        var nextText = getNextHighlightText(node);
+
+        if ((prevText != null) && containsSelection(data.nodeSet,prevText)) {
+            DOM_moveCharacters(node,0,node.nodeValue.length,
+                               prevText,prevText.nodeValue.length,true,false);
+            DOM_deleteNode(node);
+        }
+        else if ((nextText != null) && containsSelection(data.nodeSet,nextText)) {
+            DOM_moveCharacters(node,0,node.nodeValue.length,
+                               nextText,0,false,true);
+            DOM_deleteNode(node);
+        }
+        else if (!isWhitespaceTextNode(node)) {
+            // Call moveCharacters() with an empty range, to force any tracked positions
+            // that are at the end of prevText or the start of nextText to move into this
+            // node
+            if (prevText != null) {
+                DOM_moveCharacters(prevText,
+                                   prevText.nodeValue.length,prevText.nodeValue.length,
+                                   node,0);
+            }
+            if (nextText != null) {
+                DOM_moveCharacters(nextText,0,0,node,node.nodeValue.length);
+            }
+
+            var wrapped = DOM_wrapNode(node,"SPAN");
+            DOM_setAttribute(wrapped,"class",Keys.SELECTION_CLASS);
+            newHighlights.push(wrapped);
+        }
+    }
+
+    function getRangeData(selRange)
+    {
+        var nodeSet = new NodeSet();
+        var nodes;
+        var outermost;
+        if (selRange != null) {
+            outermost = Range_getOutermostNodes(selRange);
+            nodes = Range_getAllNodes(selRange);
+            for (var i = 0; i < nodes.length; i++)
+                nodeSet.add(nodes[i]);
+        }
+        else {
+            nodes = new Array();
+            outermost = new Array();
+        }
+        return { range: selRange, nodeSet: nodeSet, nodes: nodes, outermost: outermost };
+    }
+
+    function removeSelectionHighlights(data,force)
+    {
+        var selectedSet = data.nodeSet;
+
+        var remainingHighlights = new Array();
+        var checkMerge = new Array();
+        for (var i = 0; i < selectionHighlights.length; i++) {
+            var span = selectionHighlights[i];
+            if ((span.parentNode != null) && (force || !containsSelection(selectedSet,span))) {
+                if (span.firstChild != null)
+                    checkMerge.push(span.firstChild);
+                if (span.lastChild != null)
+                    checkMerge.push(span.lastChild);
+
+                DOM_removeNodeButKeepChildren(span);
+            }
+            else if (span.parentNode != null) {
+                remainingHighlights.push(span);
+            }
+        }
+        setSelectionHighlights(remainingHighlights);
+
+        for (var i = 0; i < checkMerge.length; i++) {
+            // if not already merged
+            if ((checkMerge[i] != null) && (checkMerge[i].parentNode != null)) {
+                Formatting_mergeWithNeighbours(checkMerge[i],{});
+            }
+        }
+    }
+
+    function containsSelection(selectedSet,node)
+    {
+        if (selectedSet.contains(node))
+            return true;
+        for (var child = node.firstChild; child != null; child = child.nextSibling) {
+            if (containsSelection(selectedSet,child))
+                return true;
+        }
+        return false;
+    }
+
+    Selection_update = function()
+    {
+        var selRange = Selection_get();
+        var selMarked = Selection_isMarked();
+
+        Range_trackWhileExecuting(selRange,function() {
+            // Remove table selection DIVs
+            for (var i = 0; i < selectionDivs.length; i++)
+                DOM_deleteNode(selectionDivs[i]);
+            selectionDivs = new Array();
+        });
+
+        if (selRange == null) {
+            DOM_ignoreMutationsWhileExecuting(function() {
+                removeSelectionHighlights(getRangeData(null));
+            });
+            return;
+        }
+
+        Range_assertValid(selRange,"Selection");
+
+        if (Range_isEmpty(selRange)) {
+            // We just have a cursor
+
+            Range_trackWhileExecuting(selRange,function() {
+                DOM_ignoreMutationsWhileExecuting(function() {
+                    removeSelectionHighlights(getRangeData(selRange));
+                });
+            });
+            // Selection may have changed as a result of removeSelectionHighlights()
+            Selection_setInternal(selRange.start.node,selRange.start.offset,
+                                  selRange.end.node,selRange.end.offset,
+                                  selMarked);
+            selRange = Selection_get(); // since setInternal can theoretically change it
+
+            // If we can't find the cursor rect for some reason, just don't update the position.
+            // This is better than using an incorrect position or throwing an exception.
+            var rect = Position_displayRectAtPos(selRange.end);
+            if (rect != null) {
+                var left = rect.left + window.scrollX;
+                var top = rect.top + window.scrollY;
+                var height = rect.height;
+                var width = rect.width ? rect.width : 2;
+                setEditorHandles({ type: "cursor",
+                                   left: left,
+                                   top: top,
+                                   width: width,
+                                   height: height});
+            }
+            return;
+        }
+
+        if (updateTableSelection(selRange))
+            return;
+
+        var rects = Range_getClientRects(selRange);
+
+        if ((rects != null) && (rects.length > 0)) {
+            var boundsLeft = null;
+            var boundsRight = null;
+            var boundsTop = null;
+            var boundsBottom = null
+
+            for (var i = 0; i < rects.length; i++) {
+                var left = rects[i].left + window.scrollX;
+                var top = rects[i].top + window.scrollY;
+                var width = rects[i].width;
+                var height = rects[i].height;
+                var right = left + width;
+                var bottom = top + height;
+
+                if (boundsLeft == null) {
+                    boundsLeft = left;
+                    boundsTop = top;
+                    boundsRight = right;
+                    boundsBottom = bottom;
+                }
+                else {
+                    if (boundsLeft > left)
+                        boundsLeft = left;
+                    if (boundsRight < right)
+                        boundsRight = right;
+                    if (boundsTop > top)
+                        boundsTop = top;
+                    if (boundsBottom < bottom)
+                        boundsBottom = bottom;
+                }
+            }
+
+            Range_trackWhileExecuting(selRange,function() {
+                DOM_ignoreMutationsWhileExecuting(function() {
+                    var data = getRangeData(selRange);
+                    createSelectionHighlights(data);
+                    removeSelectionHighlights(data);
+                });
+            });
+
+            // Selection may have changed as a result of create/removeSelectionHighlights()
+            Selection_setInternal(selRange.start.node,selRange.start.offset,
+                                  selRange.end.node,selRange.end.offset,
+                                  selMarked);
+
+            var firstRect = rects[0];
+            var lastRect = rects[rects.length-1];
+
+            var x1 = firstRect.left + window.scrollX;
+            var y1 = firstRect.top + window.scrollY;
+            var height1 = firstRect.height;
+            var x2 = lastRect.right + window.scrollX;
+            var y2 = lastRect.top + window.scrollY;
+            var height2 = lastRect.height;
+
+            setEditorHandles({ type: "selection",
+                               x1: x1,
+                               y1: y1,
+                               height1: height1,
+                               x2: x2,
+                               y2: y2,
+                               height2: height2,
+                               boundsLeft: boundsLeft,
+                               boundsTop: boundsTop,
+                               boundsRight: boundsRight,
+                               boundsBottom: boundsBottom });;
+
+        }
+        else {
+            setEditorHandles({ type: "none" });
+        }
+        return;
+
+        function getAbsoluteOffset(node)
+        {
+            var offsetLeft = 0;
+            var offsetTop = 0;
+            for (; node != null; node = node.parentNode) {
+                if (node.offsetLeft != null)
+                    offsetLeft += node.offsetLeft;
+                if (node.offsetTop != null)
+                    offsetTop += node.offsetTop;
+            }
+            return { offsetLeft: offsetLeft, offsetTop: offsetTop };
+        }
+    }
+
+    // public
+    Selection_selectAll = function()
+    {
+        Selection_set(document.body,0,document.body,document.body.childNodes.length);
+    }
+
+    // public
+    Selection_selectParagraph = function()
+    {
+        var selRange = Selection_get();
+        if (selRange == null)
+            return;
+        var startNode = Position_closestActualNode(selRange.start);
+        while (!isParagraphNode(startNode) && !isContainerNode(startNode))
+            startNode = startNode.parentNode;
+
+        var endNode = Position_closestActualNode(selRange.end);
+        while (!isParagraphNode(endNode) && !isContainerNode(endNode))
+            endNode = endNode.parentNode;
+
+        var startPos = new Position(startNode,0);
+        var endPos = new Position(endNode,DOM_maxChildOffset(endNode));
+        startPos = Position_closestMatchForwards(startPos,Position_okForMovement);
+        endPos = Position_closestMatchBackwards(endPos,Position_okForMovement);
+
+        Selection_set(startPos.node,startPos.offset,endPos.node,endPos.offset);
+    }
+
+    // private
+    function getPunctuationCharsForRegex()
+    {
+        var escaped = "^$\\.*+?()[]{}|"; // From ECMAScript regexp spec (PatternCharacter)
+        var unescaped = "";
+        for (var i = 32; i <= 127; i++) {
+            var c = String.fromCharCode(i);
+            if ((escaped.indexOf(c) < 0) && !c.match(/[\w\d]/))
+                unescaped += c;
+        }
+        return unescaped + escaped.replace(/(.)/g,"\\$1");
+    }
+
+    // The following regular expressions are used by selectWordAtCursor(). We initialise them at
+    // startup to avoid repeatedly initialising them.
+    var punctuation = getPunctuationCharsForRegex();
+    var wsPunctuation = "\\s"+punctuation;
+
+    // Note: We use a blacklist of punctuation characters here instead of a whitelist of "word"
+    // characters, as the \w character class in javascript regular expressions only matches
+    // characters in english words. By using a blacklist, and assuming every other character is
+    // part of a word, we can select words containing non-english characters. This isn't a perfect
+    // solution, because there are many unicode characters that represent punctuation as well, but
+    // at least we handle the common ones here.
+
+    var reOtherEnd = new RegExp("["+wsPunctuation+"]*$");
+    var reOtherStart = new RegExp("^["+wsPunctuation+"]*");
+    var reWordOtherEnd = new RegExp("[^"+wsPunctuation+"]*["+wsPunctuation+"]*$");
+    var reWordOtherStart = new RegExp("^["+wsPunctuation+"]*[^"+wsPunctuation+"]*");
+
+    var reWordStart = new RegExp("^[^"+wsPunctuation+"]+");
+    var reWordEnd = new RegExp("[^"+wsPunctuation+"]+$");
+
+    Selection_posAtStartOfWord = function(pos)
+    {
+        var node = pos.node;
+        var offset = pos.offset;
+
+        if (node.nodeType == Node.TEXT_NODE) {
+            var before = node.nodeValue.substring(0,offset);
+            var matches = before.match(reWordEnd);
+            if (matches) {
+                var wordStart = offset - matches[0].length;
+                return new Position(node,wordStart);
+            }
+        }
+
+        return pos;
+    }
+
+    Selection_posAtEndOfWord = function(pos)
+    {
+        var node = pos.node;
+        var offset = pos.offset;
+
+        if (node.nodeType == Node.TEXT_NODE) {
+            var after = node.nodeValue.substring(offset);
+            var matches = after.match(reWordStart);
+            if (matches) {
+                var wordEnd = offset + matches[0].length;
+                return new Position(node,wordEnd);
+            }
+        }
+
+        return pos;
+    }
+
+    function rangeOfWordAtPos(pos)
+    {
+        var node = pos.node;
+        var offset = pos.offset;
+
+        if (node.nodeType == Node.TEXT_NODE) {
+            var before = node.nodeValue.substring(0,offset);
+            var after = node.nodeValue.substring(offset);
+
+            var otherBefore = before.match(reOtherEnd)[0];
+            var otherAfter = after.match(reOtherStart)[0];
+
+            var wordOtherBefore = before.match(reWordOtherEnd)[0];
+            var wordOtherAfter = after.match(reWordOtherStart)[0];
+
+            var startOffset = offset;
+            var endOffset = offset;
+
+            var haveWordBefore = (wordOtherBefore.length != otherBefore.length);
+            var haveWordAfter = (wordOtherAfter.length != otherAfter.length);
+
+            if ((otherBefore.length == 0) && (otherAfter.length == 0)) {
+                startOffset = offset - wordOtherBefore.length;
+                endOffset = offset + wordOtherAfter.length;
+            }
+            else if (haveWordBefore && !haveWordAfter) {
+                startOffset = offset - wordOtherBefore.length;
+            }
+            else if (haveWordAfter && !haveWordBefore) {
+                endOffset = offset + wordOtherAfter.length;
+            }
+            else if (otherBefore.length <= otherAfter.length) {
+                startOffset = offset - wordOtherBefore.length;
+            }
+            else {
+                endOffset = offset + wordOtherAfter.length;
+            }
+
+            return new Range(node,startOffset,node,endOffset);
+        }
+        else if (node.nodeType == Node.ELEMENT_NODE) {
+            var nodeBefore = node.childNodes[offset-1];
+            var nodeAfter = node.childNodes[offset];
+
+            if ((nodeBefore != null) && !isWhitespaceTextNode(nodeBefore))
+                return new Range(node,offset-1,node,offset);
+            else if ((nodeAfter != null) && !isWhitespaceTextNode(nodeAfter))
+                return new Range(node,offset,node,offset+1);
+        }
+
+        return null;
+    }
+
+    // public
+    Selection_selectWordAtCursor = function()
+    {
+        var selRange = Selection_get();
+        if (selRange == null)
+            return;
+
+        var pos = Position_closestMatchBackwards(selRange.end,Position_okForMovement);
+        var range = rangeOfWordAtPos(pos);
+        if (range != null) {
+            Selection_set(range.start.node,range.start.offset,range.end.node,range.end.offset);
+        }
+    }
+
+    // public
+    Selection_dragSelectionBegin = function(x,y,selectWord)
+    {
+        var pos = Position_closestMatchForwards(Position_atPoint(x,y),Position_okForMovement);
+
+        if (pos == null) {
+            Selection_clear();
+            return "error";
+        }
+
+        Selection_set(pos.node,pos.offset,pos.node,pos.offset);
+
+        if (selectWord)
+            Selection_selectWordAtCursor();
+
+        return "end";
+    }
+
+    var selectionHandleEnd = true;
+
+    function toStartOfWord(pos)
+    {
+        if (Input_isAtWordBoundary(pos,"backward"))
+            return pos;
+        var boundary = Input_toWordBoundary(pos,"backward");
+        return (boundary != null) ? boundary : pos;
+    }
+
+    function toEndOfWord(pos)
+    {
+        if (Input_isAtWordBoundary(pos,"forward"))
+            return pos;
+        var boundary = Input_toWordBoundary(pos,"forward");
+        return (boundary != null) ? boundary : pos;
+    }
+
+    // public
+    Selection_dragSelectionUpdate = function(x,y,selectWord)
+    {
+        y = Cursor_scrollDocumentForY(y);
+
+        var pos = Position_closestMatchForwards(Position_atPoint(x,y),Position_okForMovement);
+        var selRange = Selection_get();
+        if ((pos == null) || (selRange == null))
+            return "none";
+
+        var start = selRange.start;
+        var end = selRange.end;
+
+        if (selectionHandleEnd) {
+            if (Position_compare(pos,start) < 0) {
+                if (selectWord)
+                    pos = toStartOfWord(pos);
+                selectionHandleEnd = false;
+            }
+            else {
+                if (selectWord)
+                    pos = toEndOfWord(pos);
+            }
+            Selection_set(start.node,start.offset,pos.node,pos.offset);
+        }
+        else {
+            if (Position_compare(pos,end) > 0) {
+                if (selectWord)
+                    pos = toEndOfWord(pos);
+                selectionHandleEnd = true;
+            }
+            else {
+                if (selectWord)
+                    pos = toStartOfWord(pos);
+            }
+            Selection_set(pos.node,pos.offset,end.node,end.offset);
+        }
+
+        return selectionHandleEnd ? "end" : "start";
+    }
+
+    function moveBoundary(command)
+    {
+        var range = Selection_get();
+        if (range == null)
+            return;
+
+        var pos = null;
+        if (command == "start-left")
+            range.start = pos = Position_prevMatch(range.start,Position_okForMovement);
+        else if (command == "start-right")
+            range.start = pos = Position_nextMatch(range.start,Position_okForMovement);
+        else if (command == "end-left")
+            range.end = pos = Position_prevMatch(range.end,Position_okForMovement);
+        else if (command == "end-right")
+            range.end = pos = Position_nextMatch(range.end,Position_okForMovement);
+
+        if ((range.start != null) && (range.end != null)) {
+            var result;
+            range = Range_forwards(range);
+            Selection_set(range.start.node,range.start.offset,range.end.node,range.end.offset);
+            if (range.end == pos)
+                return "end";
+            else if (range.end == pos)
+                return "start";
+        }
+        return null;
+    }
+
+    // public
+    Selection_moveStartLeft = function()
+    {
+        return moveBoundary("start-left");
+    }
+
+    // public
+    Selection_moveStartRight = function()
+    {
+        return moveBoundary("start-right");
+    }
+
+    // public
+    Selection_moveEndLeft = function()
+    {
+        return moveBoundary("end-left");
+    }
+
+    // public
+    Selection_moveEndRight = function()
+    {
+        return moveBoundary("end-right");
+    }
+
+    // public
+    Selection_setSelectionStartAtCoords = function(x,y)
+    {
+        var position = Position_closestMatchForwards(Position_atPoint(x,y),Position_okForMovement);
+        if (position != null) {
+            position = Position_closestMatchBackwards(position,Position_okForMovement);
+            var selRange = Selection_get();
+            var newRange = new Range(position.node,position.offset,
+                                     selRange.end.node,selRange.end.offset);
+            if (Range_isForwards(newRange)) {
+                Selection_set(newRange.start.node,newRange.start.offset,
+                              newRange.end.node,newRange.end.offset);
+            }
+        }
+    }
+
+    // public
+    Selection_setSelectionEndAtCoords = function(x,y)
+    {
+        var position = Position_closestMatchForwards(Position_atPoint(x,y),Position_okForMovement);
+        if (position != null) {
+            position = Position_closestMatchBackwards(position,Position_okForMovement);
+            var selRange = Selection_get();
+            var newRange = new Range(selRange.start.node,selRange.start.offset,
+                                     position.node,position.offset);
+            if (Range_isForwards(newRange)) {
+                Selection_set(newRange.start.node,newRange.start.offset,
+                              newRange.end.node,newRange.end.offset);
+            }
+        }
+    }
+
+    // public
+    Selection_setTableSelectionEdgeAtCoords = function(edge,x,y)
+    {
+        if (tableSelection == null)
+            return;
+
+        var structure = tableSelection.structure;
+        var pointInfo = findCellInTable(structure,x,y);
+        if (pointInfo == null)
+            return;
+
+        if (edge == "topLeft") {
+            if (pointInfo.row <= tableSelection.bottom)
+                tableSelection.top = pointInfo.row;
+            if (pointInfo.col <= tableSelection.right)
+                tableSelection.left = pointInfo.col;
+        }
+        else if (edge == "bottomRight") {
+            if (pointInfo.row >= tableSelection.top)
+                tableSelection.bottom = pointInfo.row;
+            if (pointInfo.col >= tableSelection.left)
+                tableSelection.right = pointInfo.col;
+        }
+
+        // FIXME: handle the case where there is no cell at the specified row and column
+        var topLeftCell = Table_get(structure,tableSelection.top,tableSelection.left);
+        var bottomRightCell = Table_get(structure,tableSelection.bottom,tableSelection.right);
+
+        var topLeftNode = topLeftCell.element.parentNode;
+        var topLeftOffset = DOM_nodeOffset(topLeftCell.element);
+        var bottomRightNode = bottomRightCell.element.parentNode;
+        var bottomRightOffset = DOM_nodeOffset(bottomRightCell.element)+1;
+
+        Selection_set(topLeftNode,topLeftOffset,bottomRightNode,bottomRightOffset);
+
+        // FIXME: this could possibly be optimised
+        function findCellInTable(structure,x,y)
+        {
+            for (var r = 0; r < structure.numRows; r++) {
+                for (var c = 0; c < structure.numCols; c++) {
+                    var cell = Table_get(structure,r,c);
+                    if (cell != null) {
+                        var rect = cell.element.getBoundingClientRect();
+                        if ((x >= rect.left) && (x <= rect.right) &&
+                            (y >= rect.top) && (y <= rect.bottom))
+                            return cell;
+                    }
+                }
+            }
+            return null;
+        }
+    }
+
+    // public
+    Selection_setEmptySelectionAt = function(node,offset)
+    {
+        Selection_set(node,offset,node,offset);
+    }
+
+    // private
+    function deleteTextSelection(selRange,keepEmpty)
+    {
+        var nodes = Range_getOutermostNodes(selRange);
+        for (var i = 0; i < nodes.length; i++) {
+            var node = nodes[i];
+
+            var removeWholeNode = false;
+
+            if ((node == selRange.start.node) &&
+                (node == selRange.end.node)) {
+                var startOffset = selRange.start.offset;
+                var endOffset = selRange.end.offset;
+                if ((node.nodeType == Node.TEXT_NODE) &&
+                    ((startOffset > 0) || (endOffset < node.nodeValue.length))) {
+                    DOM_deleteCharacters(node,startOffset,endOffset);
+                }
+                else {
+                    removeWholeNode = true;
+                }
+            }
+            else if (node == selRange.start.node) {
+                var offset = selRange.start.offset;
+                if ((node.nodeType == Node.TEXT_NODE) && (offset > 0)) {
+                    DOM_deleteCharacters(node,offset);
+                }
+                else {
+                    removeWholeNode = true;
+                }
+            }
+            else if (node == selRange.end.node) {
+                var offset = selRange.end.offset;
+                if ((node.nodeType == Node.TEXT_NODE) && (offset < node.nodeValue.length)) {
+                    DOM_deleteCharacters(node,0,offset);
+                }
+                else {
+                    removeWholeNode = true;
+                }
+            }
+            else {
+                removeWholeNode = true;
+            }
+
+            if (removeWholeNode) {
+                switch (node._type) {
+                case HTML_TD:
+                case HTML_TH:
+                    DOM_deleteAllChildren(node);
+                    break;
+                default:
+                    DOM_deleteNode(node);
+                    break;
+                }
+            }
+        }
+
+        var detail = Range_detail(selRange);
+
+        var sameTextNode = (selRange.start.node == selRange.end.node) &&
+                           (selRange.start.node.nodeType == Node.TEXT_NODE);
+
+        if ((detail.startAncestor != null) && (detail.endAncestor != null) &&
+            (detail.startAncestor.nextSibling == detail.endAncestor) &&
+            !sameTextNode) {
+            prepareForMerge(detail);
+            DOM_mergeWithNextSibling(detail.startAncestor,
+                                          Formatting_MERGEABLE_BLOCK_AND_INLINE);
+            if (isParagraphNode(detail.startAncestor) &&
+                (detail.startAncestor._type != HTML_DIV))
+                removeParagraphDescendants(detail.startAncestor);
+        }
+
+        if (!keepEmpty) {
+            var startNode = selRange.start.node;
+            var endNode = selRange.end.node;
+            if (startNode.parentNode != null)
+                delEmpty(selRange,startNode);
+            if (endNode.parentNode != null)
+                delEmpty(selRange,endNode);
+        }
+
+        Cursor_updateBRAtEndOfParagraph(Range_singleNode(selRange));
+    }
+
+    function delEmpty(selRange,node)
+    {
+        while ((node != document.body) &&
+               (node.nodeType == Node.ELEMENT_NODE) &&
+               (node.firstChild == null)) {
+
+            if (isTableCell(node) || isTableCell(node.parentNode))
+                return;
+
+            if (!fixPositionOutside(selRange.start,node))
+                break;
+            if (!fixPositionOutside(selRange.end,node))
+                break;
+
+            var parent = node.parentNode;
+            Range_trackWhileExecuting(selRange,function() {
+                DOM_deleteNode(node);
+            });
+            node = parent;
+        }
+    }
+
+    function fixPositionOutside(pos,node)
+    {
+        if (pos.node == node) {
+            var before = new Position(node.parentNode,DOM_nodeOffset(node));
+            var after = new Position(node.parentNode,DOM_nodeOffset(node)+1);
+            before = Position_prevMatch(before,Position_okForMovement);
+            after = Position_nextMatch(after,Position_okForMovement);
+
+            if (before != null) {
+                pos.node = before.node;
+                pos.offset = before.offset;
+            }
+            else if (after != null) {
+                pos.node = after.node;
+                pos.offset = after.offset;
+            }
+            else {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    Selection_deleteRangeContents = function(range,keepEmpty)
+    {
+        Range_trackWhileExecuting(range,function() {
+            DOM_ignoreMutationsWhileExecuting(function() {
+                removeSelectionHighlights(getRangeData(range),true);
+            });
+
+            var region = Tables_regionFromRange(range);
+            if (region != null)
+                Tables_deleteRegion(region);
+            else
+                deleteTextSelection(range,keepEmpty);
+        });
+
+        Selection_set(range.start.node,range.start.offset,range.start.node,range.start.offset);
+    }
+
+    Selection_deleteContents = function(keepEmpty)
+    {
+        var range = Selection_get();
+        if (range == null)
+            return;
+        Selection_deleteRangeContents(range,keepEmpty);
+    }
+
+    // private
+    function removeParagraphDescendants(parent)
+    {
+        var next;
+        for (var child = parent.firstChild; child != null; child = next) {
+            next = child.nextSibling;
+            removeParagraphDescendants(child);
+            if (isParagraphNode(child))
+                DOM_removeNodeButKeepChildren(child);
+        }
+    }
+
+    // private
+    function findFirstParagraph(node)
+    {
+        if (isParagraphNode(node))
+            return node;
+        if (node._type == HTML_LI) {
+            var nonWhitespaceInline = false;
+
+            for (var child = node.firstChild; child != null; child = child.nextSibling) {
+                if (isInlineNode(child) && !isWhitespaceTextNode(child))
+                    nonWhitespaceInline = true;
+
+                if (isParagraphNode(child)) {
+                    if (nonWhitespaceInline)
+                        return putPrecedingSiblingsInParagraph(node,child);
+                    return child;
+                }
+                else if (isListNode(child)) {
+                    if (nonWhitespaceInline)
+                        return putPrecedingSiblingsInParagraph(node,child);
+                    return findFirstParagraph(child);
+                }
+            }
+            if (nonWhitespaceInline)
+                return putPrecedingSiblingsInParagraph(node,null);
+        }
+        return null;
+
+        function putPrecedingSiblingsInParagraph(parent,node)
+        {
+            var p = DOM_createElement(document,"P");
+            while (parent.firstChild != node)
+                DOM_appendChild(p,parent.firstChild);
+            return p;
+        }
+    }
+
+    // private
+    function prepareForMerge(detail)
+    {
+        if (isParagraphNode(detail.startAncestor) && isInlineNode(detail.endAncestor)) {
+            var name = detail.startAncestor.nodeName; // check-ok
+            var newParagraph = DOM_createElement(document,name);
+            DOM_insertBefore(detail.endAncestor.parentNode,newParagraph,detail.endAncestor);
+            DOM_appendChild(newParagraph,detail.endAncestor);
+            detail.endAncestor = newParagraph;
+        }
+        else if (isInlineNode(detail.startAncestor) && isParagraphNode(detail.endAncestor)) {
+            var name = detail.endAncestor.nodeName; // check-ok
+            var newParagraph = DOM_createElement(document,name);
+            DOM_insertBefore(detail.startAncestor.parentNode,newParagraph,
+                             detail.startAncestor.nextSibling);
+            DOM_appendChild(newParagraph,detail.startAncestor);
+            detail.startAncestor = newParagraph;
+        }
+        else if (isParagraphNode(detail.startAncestor) &&
+                 isListNode(detail.endAncestor) &&
+                 (detail.endAncestor.firstChild._type == HTML_LI)) {
+            var list = detail.endAncestor;
+            var li = detail.endAncestor.firstChild;
+
+            var paragraph = findFirstParagraph(li);
+            if (paragraph != null) {
+                DOM_insertBefore(list.parentNode,paragraph,list);
+                var name = detail.startAncestor.nodeName; // check-ok
+                DOM_replaceElement(paragraph,name);
+            }
+            if (!nodeHasContent(li))
+                DOM_deleteNode(li);
+            if (firstChildElement(list) == null)
+                DOM_deleteNode(list);
+        }
+        else if (isParagraphNode(detail.endAncestor) &&
+                 isListNode(detail.startAncestor) &&
+                 (detail.startAncestor.lastChild._type == HTML_LI)) {
+            var list = detail.startAncestor;
+            var li = detail.startAncestor.lastChild;
+            var p = detail.endAncestor;
+            var oldLastChild = li.lastChild;
+            while (p.firstChild != null)
+                DOM_insertBefore(li,p.firstChild,null);
+            DOM_deleteNode(p);
+            if (oldLastChild != null) {
+                DOM_mergeWithNextSibling(oldLastChild,
+                                              Formatting_MERGEABLE_BLOCK_AND_INLINE);
+            }
+        }
+
+        if ((detail.startAncestor.lastChild != null) && (detail.endAncestor.firstChild != null)) {
+            var childDetail = new Object();
+            childDetail.startAncestor = detail.startAncestor.lastChild;
+            childDetail.endAncestor = detail.endAncestor.firstChild;
+            prepareForMerge(childDetail);
+        }
+    }
+
+    // public
+    Selection_clearSelection = function()
+    {
+        Selection_clear();
+    }
+
+    // public
+    Selection_preserveWhileExecuting = function(fun)
+    {
+        var range = Selection_get();
+
+        // Since the selection may have changed as a result of changes to the document, we
+        // have to call clear() or set() so that undo history is saved
+        if (range == null) {
+            result = fun();
+            Selection_clear();
+        }
+        else {
+            result = Range_trackWhileExecuting(range,fun);
+            Selection_set(range.start.node,range.start.offset,range.end.node,range.end.offset);
+        }
+        return result;
+    }
+
+    Selection_preferElementPositions = function()
+    {
+        var range = Selection_get();
+        if (range == null)
+            return;
+        range.start = Position_preferElementPosition(range.start);
+        range.end = Position_preferElementPosition(range.end);
+        Selection_set(range.start.node,range.start.offset,
+                      range.end.node,range.end.offset);
+    }
+
+    function getBoundaryContainer(node,topAncestor)
+    {
+        var container = document.body;
+        for (; node != topAncestor.parentNode; node = node.parentNode) {
+            switch (node._type) {
+            case HTML_FIGURE:
+            case HTML_TABLE:
+                container = node;
+                break;
+            }
+        }
+        return container;
+    }
+
+    function boundaryCompliantRange(range)
+    {
+        if (range == null)
+            return null;
+
+        var detail = Range_detail(range);
+        var start = range.start;
+        var end = range.end;
+        var startNode = Position_closestActualNode(start);
+        var endNode = Position_closestActualNode(end);
+        var startContainer = getBoundaryContainer(startNode.parentNode,detail.commonAncestor);
+        var endContainer = getBoundaryContainer(endNode.parentNode,detail.commonAncestor);
+
+        if (startContainer != endContainer) {
+
+            var doStart = false;
+            var doEnd = false;
+
+            if (nodeHasAncestor(startContainer,endContainer)) {
+                doStart = true;
+            }
+            else if (nodeHasAncestor(endContainer,startContainer)) {
+                doEnd = true;
+            }
+            else {
+                doStart = true;
+                doEnd = true;
+            }
+
+            if (doStart && (startContainer != document.body))
+                start = new Position(startContainer.parentNode,DOM_nodeOffset(startContainer));
+            if (doEnd && (endContainer != document.body))
+                end = new Position(endContainer.parentNode,DOM_nodeOffset(endContainer)+1);
+        }
+        return new Range(start.node,start.offset,end.node,end.offset);
+
+        function nodeHasAncestor(node,ancestor)
+        {
+            for (; node != null; node = node.parentNode) {
+                if (node == ancestor)
+                    return true;
+            }
+            return false;
+        }
+    }
+
+    Selection_print = function()
+    {
+        debug("");
+        debug("");
+        debug("");
+        debug("================================================================================");
+
+        var sel = Selection_get();
+        if (sel == null) {
+            debug("No selection");
+            return;
+        }
+
+        printSelectionElement(document.body,"");
+
+        function printSelectionElement(node,indent)
+        {
+            var className = DOM_getAttribute(node,"class");
+            if (className != null)
+                debug(indent+node.nodeName+" ("+className+")");
+            else
+                debug(indent+node.nodeName);
+
+            var child = node.firstChild;
+            var offset = 0;
+            while (true) {
+
+                var isStart = ((sel.start.node == node) && (sel.start.offset == offset));
+                var isEnd = ((sel.end.node == node) && (sel.end.offset == offset));
+                if (isStart && isEnd)
+                    debug(indent+"    []");
+                else if (isStart)
+                    debug(indent+"    [");
+                else if (isEnd)
+                    debug(indent+"    ]");
+
+                if (child == null)
+                    break;
+
+                if (child.nodeType == Node.ELEMENT_NODE)
+                    printSelectionElement(child,indent+"    ");
+                else
+                    printSelectionText(child,indent+"    ");
+
+                child = child.nextSibling;
+                offset++;
+            }
+        }
+
+        function printSelectionText(node,indent)
+        {
+            var value = node.nodeValue;
+
+            if (sel.end.node == node) {
+                var afterSelection = value.substring(sel.end.offset);
+                value = value.substring(0,sel.end.offset) + "]" + afterSelection;
+            }
+
+            if (sel.start.node == node) {
+                var beforeSelection = value.substring(0,sel.start.offset);
+                value = beforeSelection + "[" + value.substring(sel.start.offset);
+            }
+
+            debug(indent+JSON.stringify(value));
+        }
+    }
+
+})();

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9bf02bb2/experiments/editorFramework/src/Javascript_Layer_0/StringBuilder.js
----------------------------------------------------------------------
diff --git a/experiments/editorFramework/src/Javascript_Layer_0/StringBuilder.js b/experiments/editorFramework/src/Javascript_Layer_0/StringBuilder.js
new file mode 100644
index 0000000..9332b79
--- /dev/null
+++ b/experiments/editorFramework/src/Javascript_Layer_0/StringBuilder.js
@@ -0,0 +1,21 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+function StringBuilder()
+{
+    this.str = "";
+}

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9bf02bb2/experiments/editorFramework/src/Javascript_Layer_0/Styles.js
----------------------------------------------------------------------
diff --git a/experiments/editorFramework/src/Javascript_Layer_0/Styles.js b/experiments/editorFramework/src/Javascript_Layer_0/Styles.js
new file mode 100644
index 0000000..c09f41d
--- /dev/null
+++ b/experiments/editorFramework/src/Javascript_Layer_0/Styles.js
@@ -0,0 +1,179 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+var Styles_getRule;
+var Styles_nextSelectorAfter;
+var Styles_getParagraphClass;
+var Styles_setParagraphClass;
+var Styles_headingNumbering;
+var Styles_getCSSText;
+var Styles_setCSSText;
+var Styles_getBuiltinCSSURL;
+var Styles_init;
+
+(function() {
+
+    var rules = new Object();
+    var paragraphClass = null;
+
+    Styles_getRule = function(selector)
+    {
+        return rules[selector];
+    }
+
+    Styles_nextSelectorAfter = function(element)
+    {
+        var selector = element.nodeName.toLowerCase();
+        var className = DOM_getAttribute(element,"class");
+        if (className != null)
+            selector = selector+"."+className;
+
+        var nextElementName = null;
+        var nextClassName = null;
+
+        var rule = Styles_getRule(selector);
+        if (rule != null) {
+            var nextSelector = rule["-uxwrite-next"];
+            if (nextSelector != null) {
+                try {
+                    nextSelector = JSON.parse(nextSelector);
+                    if (typeof(nextSelector) != "string")
+                        nextSelector = null;
+                }
+                catch (e) {
+                    nextSelector = null;
+                }
+            }
+            if (nextSelector != null) {
+                var dotIndex = nextSelector.indexOf(".");
+                if (dotIndex >= 0) {
+                    nextElementName = nextSelector.substring(0,dotIndex);
+                    nextClassName = nextSelector.substring(dotIndex+1);
+                }
+                else {
+                    nextElementName = nextSelector;
+                }
+            }
+        }
+
+        if ((nextElementName == null) ||
+            (ElementTypes[nextElementName] == null) ||
+            (!PARAGRAPH_ELEMENTS[ElementTypes[nextElementName]])) {
+            nextElementName = null;
+            nextClassName = null;
+        }
+
+        if (isHeadingNode(element)) {
+            nextElementName = "p";
+            nextClassName = Styles_getParagraphClass();
+        }
+
+        if (nextElementName == null)
+            return null;
+        else if (nextClassName == null)
+            return nextElementName;
+        else
+            return nextElementName+"."+nextClassName;
+    }
+
+    Styles_getParagraphClass = function()
+    {
+        return paragraphClass;
+    }
+
+    Styles_setParagraphClass = function(cls)
+    {
+        paragraphClass = cls;
+    }
+
+    Styles_headingNumbering = function()
+    {
+        return ((rules["h1::before"] != null) &&
+                (rules["h1::before"]["content"] != null));
+    }
+
+    Styles_getCSSText = function()
+    {
+        var head = DOM_documentHead(document);
+        var cssText = "";
+        for (var child = head.firstChild; child != null; child = child.nextSibling) {
+            if (child._type == HTML_STYLE) {
+                for (var t = child.firstChild; t != null; t = t.nextSibling) {
+                    if (t._type == HTML_TEXT)
+                        cssText += t.nodeValue;
+                }
+            }
+        }
+        return cssText;
+    }
+
+    Styles_setCSSText = function(cssText,cssRules)
+    {
+        UndoManager_newGroup("Update styles");
+        var head = DOM_documentHead(document);
+        var next;
+        for (var child = head.firstChild; child != null; child = next) {
+            next = child.nextSibling;
+            if (child._type == HTML_STYLE)
+                DOM_deleteNode(child);
+        }
+        var style = DOM_createElement(document,"STYLE");
+        DOM_appendChild(style,DOM_createTextNode(document,cssText));
+        DOM_appendChild(head,style);
+        rules = cssRules; // FIXME: undo support? (must coordinate with ObjC code)
+        Outline_scheduleUpdateStructure();
+        return {}; // Objective C caller expects JSON result
+    }
+
+    function addBuiltinStylesheet(cssURL)
+    {
+        var head = DOM_documentHead(document);
+        for (var child = head.firstChild; child != null; child = child.nextSibling) {
+            if ((child._type == HTML_LINK) &&
+                (child.getAttribute("rel") == "stylesheet") &&
+                (child.getAttribute("href") == cssURL)) {
+                // Link element was already added by HTMLInjectionProtocol
+                return;
+            }
+        }
+
+        // HTMLInjectionProtocol was unable to find <head> element and insert the stylesheet link,
+        // so add it ourselves
+        var link = DOM_createElement(document,"LINK");
+        DOM_setAttribute(link,"rel","stylesheet");
+        DOM_setAttribute(link,"href",cssURL);
+        DOM_insertBefore(head,link,head.firstChild);
+    }
+
+    var builtinCSSURL = null;
+
+    Styles_getBuiltinCSSURL = function()
+    {
+        return builtinCSSURL;
+    }
+
+    // public
+    Styles_init = function(cssURL)
+    {
+        if (cssURL != null)
+            builtinCSSURL = cssURL;
+
+        if (builtinCSSURL != null)
+            addBuiltinStylesheet(builtinCSSURL);
+    }
+
+})();