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:13 UTC

[21/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/Position.js
----------------------------------------------------------------------
diff --git a/experiments/editorFramework/src/Javascript_Layer_0/Position.js b/experiments/editorFramework/src/Javascript_Layer_0/Position.js
new file mode 100644
index 0000000..ce4bc18
--- /dev/null
+++ b/experiments/editorFramework/src/Javascript_Layer_0/Position.js
@@ -0,0 +1,1164 @@
+// 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 Position;
+var Position_assertValid;
+var Position_prev;
+var Position_next;
+var Position_trackWhileExecuting;
+var Position_closestActualNode;
+var Position_okForInsertion;
+var Position_okForMovement;
+var Position_prevMatch;
+var Position_nextMatch;
+var Position_closestMatchForwards;
+var Position_closestMatchBackwards;
+var Position_track;
+var Position_untrack;
+var Position_rectAtPos;
+var Position_noteAncestor;
+var Position_captionAncestor;
+var Position_figureOrTableAncestor;
+var Position_displayRectAtPos;
+var Position_preferTextPosition;
+var Position_preferElementPosition;
+var Position_compare;
+var Position_atPoint;
+
+(function() {
+
+    // public
+    Position = function(node,offset)
+    {
+        if (node == document.documentElement)
+            throw new Error("node is root element");
+        Object.defineProperty(this,"self",{value: {}});
+        var self = this.self;
+        self.this = this;
+        self.node = node;
+        self.offset = offset;
+        self.origOffset = offset;
+        self.tracking = 0;
+        this.posId = null;
+        this.targetX = null;
+
+        Object.defineProperty(this,"node",{
+            get: function() { return this.self.node },
+            set: setNode,
+            enumerable: true });
+        Object.defineProperty(this,"offset",{
+            get: function() { return this.self.offset },
+            set: function(value) { this.self.offset = value },
+            enumerable: true});
+        Object.defineProperty(this,"origOffset",{
+            get: function() { return this.self.origOffset },
+            set: function(value) { this.self.origOffset = value },
+            enumerable: true});
+
+        Object.preventExtensions(this);
+    }
+
+    function actuallyStartTracking(self)
+    {
+        DOM_addTrackedPosition(self.this);
+    }
+
+    function actuallyStopTracking(self)
+    {
+        DOM_removeTrackedPosition(self.this);
+    }
+
+    function startTracking(self)
+    {
+        if (self.tracking == 0)
+            actuallyStartTracking(self);
+        self.tracking++;
+    }
+
+    function stopTracking(self)
+    {
+        self.tracking--;
+        if (self.tracking == 0)
+            actuallyStopTracking(self);
+    }
+
+    function setNode(node)
+    {
+        var self = this.self;
+        if (self.tracking > 0)
+            actuallyStopTracking(self);
+
+        self.node = node;
+
+        if (self.tracking > 0)
+            actuallyStartTracking(self);
+    }
+
+    function setNodeAndOffset(self,node,offset)
+    {
+        self.this.node = node;
+        self.this.offset = offset;
+    }
+
+    // public
+    Position.prototype.toString = function()
+    {
+        var self = this.self;
+        var result;
+        if (self.node.nodeType == Node.TEXT_NODE) {
+            var extra = "";
+            if (self.offset > self.node.nodeValue.length) {
+                for (var i = self.node.nodeValue.length; i < self.offset; i++)
+                    extra += "!";
+            }
+            var id = "";
+            if (window.debugIds)
+                id = self.node._nodeId+":";
+            result = id+JSON.stringify(self.node.nodeValue.slice(0,self.offset)+extra+"|"+
+                                       self.node.nodeValue.slice(self.offset));
+        }
+        else {
+            result = "("+nodeString(self.node)+","+self.offset+")";
+        }
+        if (this.posId != null)
+            result = "["+this.posId+"]"+result;
+        return result;
+    }
+
+    function positionSpecial(pos,forwards,backwards)
+    {
+        var node = pos.node;
+        var offset = pos.offset;
+
+        var prev = node.childNodes[offset-1];
+        var next = node.childNodes[offset];
+
+        // Moving left from the start of a caption - go to the end of the table
+        if ((node._type == HTML_CAPTION) && backwards && (prev == null))
+            return new Position(node.parentNode,node.parentNode.childNodes.length);
+
+        // Moving right from the end of a caption - go after the table
+        if ((node._type == HTML_CAPTION) && forwards && (next == null))
+            return new Position(node.parentNode.parentNode,DOM_nodeOffset(node.parentNode)+1);
+
+        // Moving left from just after a table - go to the end of the caption (if there is one)
+        if ((prev != null) && (prev._type == HTML_TABLE) && backwards) {
+            var firstChild = firstChildElement(prev);
+            if ((firstChild._type == HTML_CAPTION))
+                return new Position(firstChild,firstChild.childNodes.length);
+        }
+
+        // Moving right from just before a table - bypass the the caption (if there is one)
+        if ((next != null) && (next._type == HTML_TABLE) && forwards) {
+            var firstChild = firstChildElement(next);
+            if (firstChild._type == HTML_CAPTION)
+                return new Position(next,DOM_nodeOffset(firstChild)+1);
+        }
+
+        // Moving right from the end of a table - go to the start of the caption (if there is one)
+        if ((node._type == HTML_TABLE) && (next == null) && forwards) {
+            var firstChild = firstChildElement(node);
+            if (firstChild._type == HTML_CAPTION)
+                return new Position(firstChild,0);
+        }
+
+        // Moving left just after a caption node - skip the caption
+        if ((prev != null) && (prev._type == HTML_CAPTION) && backwards)
+            return new Position(node,offset-1);
+
+        return null;
+    }
+
+    // public
+    Position_assertValid = function(pos,description)
+    {
+        if (description == null)
+            description = "Position";
+
+        for (var ancestor = pos.node; ancestor != document.body; ancestor = ancestor.parentNode) {
+            if (ancestor == null)
+                throw new Error(description+" node "+pos.node.nodeName+" is not in tree");
+        }
+
+        var max;
+        if (pos.node.nodeType == Node.ELEMENT_NODE)
+            max = pos.node.childNodes.length;
+        else if (pos.node.nodeType == Node.TEXT_NODE)
+            max = pos.node.nodeValue.length;
+        else
+            throw new Error(description+" has invalid node type "+pos.node.nodeType);
+
+        if ((pos.offset < 0) || (pos.offset > max)) {
+            throw new Error(description+" (in "+pos.node.nodeName+") has invalid offset "+
+                            pos.offset+" (max allowed is "+max+")");
+        }
+    }
+
+    // public
+    Position_prev = function(pos)
+    {
+        if (pos.node.nodeType == Node.ELEMENT_NODE) {
+            var r = positionSpecial(pos,false,true);
+            if (r != null)
+                return r;
+            if (pos.offset == 0) {
+                return upAndBack(pos);
+            }
+            else {
+                var child = pos.node.childNodes[pos.offset-1];
+                return new Position(child,DOM_maxChildOffset(child));
+            }
+        }
+        else if (pos.node.nodeType == Node.TEXT_NODE) {
+            if (pos.offset > 0)
+                return new Position(pos.node,pos.offset-1);
+            else
+                return upAndBack(pos);
+        }
+        else {
+            return null;
+        }
+
+        function upAndBack(pos)
+        {
+            if (pos.node == pos.node.ownerDocument.body)
+                return null;
+            else
+                return new Position(pos.node.parentNode,DOM_nodeOffset(pos.node));
+        }
+    }
+
+    // public
+    Position_next = function(pos)
+    {
+        if (pos.node.nodeType == Node.ELEMENT_NODE) {
+            var r = positionSpecial(pos,true,false);
+            if (r != null)
+                return r;
+            if (pos.offset == pos.node.childNodes.length)
+                return upAndForwards(pos);
+            else
+                return new Position(pos.node.childNodes[pos.offset],0);
+        }
+        else if (pos.node.nodeType == Node.TEXT_NODE) {
+            if (pos.offset < pos.node.nodeValue.length)
+                return new Position(pos.node,pos.offset+1);
+            else
+                return upAndForwards(pos);
+        }
+        else {
+            return null;
+        }
+
+        function upAndForwards(pos)
+        {
+            if (pos.node == pos.node.ownerDocument.body)
+                return null;
+            else
+                return new Position(pos.node.parentNode,DOM_nodeOffset(pos.node)+1);
+        }
+    }
+
+    // public
+    Position_trackWhileExecuting = function(positions,fun)
+    {
+        for (var i = 0; i < positions.length; i++)
+            startTracking(positions[i].self);
+        try {
+            return fun();
+        }
+        finally {
+            for (var i = 0; i < positions.length; i++)
+                stopTracking(positions[i].self);
+        }
+    }
+
+    // public
+    Position_closestActualNode = function(pos,preferElement)
+    {
+        var node = pos.node;
+        var offset = pos.offset;
+        if ((node.nodeType != Node.ELEMENT_NODE) || (node.firstChild == null))
+            return node;
+        else if (offset == 0)
+            return node.firstChild;
+        else if (offset >= node.childNodes.length)
+            return node.lastChild;
+
+        var prev = node.childNodes[offset-1];
+        var next = node.childNodes[offset];
+        if (preferElement &&
+            (next.nodeType != Node.ELEMENT_NODE) &&
+            (prev.nodeType == Node.ELEMENT_NODE)) {
+            return prev;
+        }
+        else {
+            return next;
+        }
+    }
+
+    // public
+    Position_okForInsertion = function(pos)
+    {
+        return Position_okForMovement(pos,true);
+    }
+
+    function nodeCausesLineBreak(node)
+    {
+        return ((node._type == HTML_BR) || !isInlineNode(node));
+    }
+
+    function spacesUntilNextContent(node)
+    {
+        var spaces = 0;
+        while (true) {
+            if (node.firstChild) {
+                node = node.firstChild;
+            }
+            else if (node.nextSibling) {
+                node = node.nextSibling;
+            }
+            else {
+                while ((node.parentNode != null) && (node.parentNode.nextSibling == null)) {
+                    node = node.parentNode;
+                    if (nodeCausesLineBreak(node))
+                        return null;
+                }
+                if (node.parentNode == null)
+                    node = null;
+                else
+                    node = node.parentNode.nextSibling;
+            }
+
+            if ((node == null) || nodeCausesLineBreak(node))
+                return null;
+            if (isOpaqueNode(node))
+                return spaces;
+            if (node.nodeType == Node.TEXT_NODE) {
+                if (isWhitespaceTextNode(node)) {
+                    spaces += node.nodeValue.length;
+                }
+                else {
+                    var matches = node.nodeValue.match(/^\s+/);
+                    if (matches == null)
+                        return spaces;
+                    spaces += matches[0].length;
+                    return spaces;
+                }
+            }
+        }
+    }
+
+    // public
+    Position_okForMovement = function(pos,insertion)
+    {
+        var node = pos.node;
+        var offset = pos.offset;
+        var type = node._type;
+
+        if (isOpaqueNode(node))
+            return false;
+
+        for (var ancestor = node; ancestor != null; ancestor = ancestor.parentNode) {
+            var ancestorType = node._type;
+            if (ancestorType == HTML_FIGCAPTION)
+                break;
+            else if (ancestorType == HTML_FIGURE)
+                return false;
+        }
+
+        if (node.nodeType == Node.TEXT_NODE) {
+            var value = node.nodeValue;
+
+            // If there are multiple adjacent text nodes, consider them as one (adjusting the
+            // offset appropriately)
+
+            var firstNode = node;
+            var lastNode = node;
+
+            while ((firstNode.previousSibling != null) &&
+                   (firstNode.previousSibling.nodeType == Node.TEXT_NODE)) {
+                firstNode = firstNode.previousSibling;
+                value = firstNode.nodeValue + value;
+                offset += firstNode.nodeValue.length;
+            }
+
+            while ((lastNode.nextSibling != null) &&
+                   (lastNode.nextSibling.nodeType == Node.TEXT_NODE)) {
+                lastNode = lastNode.nextSibling;
+                value += lastNode.nodeValue;
+            }
+
+            var prevChar = value.charAt(offset-1);
+            var nextChar = value.charAt(offset);
+            var havePrevChar = ((prevChar != null) && !isWhitespaceString(prevChar));
+            var haveNextChar = ((nextChar != null) && !isWhitespaceString(nextChar));
+            if (havePrevChar && haveNextChar) {
+                var prevCode = value.charCodeAt(offset-1);
+                var nextCode = value.charCodeAt(offset);
+                if ((prevCode >= 0xD800) && (prevCode <= 0xDBFF) &&
+                    (nextCode >= 0xDC00) && (nextCode <= 0xDFFF)) {
+                    return false; // In middle of surrogate pair
+                }
+                return true;
+            }
+
+            if (isWhitespaceString(value)) {
+                if (offset == 0) {
+                    if ((node == firstNode) &&
+                        (firstNode.previousSibling == null) && (lastNode.nextSibling == null))
+                        return true;
+                    if ((node.nextSibling != null) && (node.nextSibling._type == HTML_BR))
+                        return true;
+                    if ((node.firstChild == null) &&
+                        (node.previousSibling == null) &&
+                        (node.nextSibling == null)) {
+                        return true;
+                    }
+                    if (insertion && (node.previousSibling != null) &&
+                        isInlineNode(node.previousSibling) &&
+                        !isOpaqueNode(node.previousSibling) &&
+                        (node.previousSibling._type != HTML_BR))
+                        return true;
+                }
+                return false;
+            }
+
+            if (insertion)
+                return true;
+
+            var precedingText = value.substring(0,offset);
+            if (isWhitespaceString(precedingText)) {
+                return (haveNextChar &&
+                        ((node.previousSibling == null) ||
+                         (node.previousSibling._type == HTML_BR) ||
+                         isNoteNode(node.previousSibling) ||
+                         (isParagraphNode(node.previousSibling)) ||
+                         (getNodeText(node.previousSibling).match(/\s$/)) ||
+                         isItemNumber(node.previousSibling) ||
+                         ((precedingText.length > 0))));
+            }
+
+            var followingText = value.substring(offset);
+            if (isWhitespaceString(followingText)) {
+                return (havePrevChar &&
+                        ((node.nextSibling == null) ||
+                         isNoteNode(node.nextSibling) ||
+                         (followingText.length > 0) ||
+                         (spacesUntilNextContent(node) != 0)));
+            }
+
+            return (havePrevChar || haveNextChar);
+        }
+        else if (node.nodeType == Node.ELEMENT_NODE) {
+            if (node.firstChild == null) {
+                switch (type) {
+                case HTML_LI:
+                case HTML_TH:
+                case HTML_TD:
+                    return true;
+                default:
+                    if (PARAGRAPH_ELEMENTS[type])
+                        return true;
+                    else
+                        break;
+                }
+            }
+
+            var prevNode = node.childNodes[offset-1];
+            var nextNode = node.childNodes[offset];
+            var prevType = (prevNode != null) ? prevNode._type : 0;
+            var nextType = (nextNode != null) ? nextNode._type : 0;
+
+            var prevIsNote = (prevNode != null) && isNoteNode(prevNode);
+            var nextIsNote = (nextNode != null) && isNoteNode(nextNode);
+            if (((nextNode == null) || !nodeHasContent(nextNode)) && prevIsNote)
+                return true;
+            if (((prevNode == null) || !nodeHasContent(prevNode)) && nextIsNote)
+                return true;
+            if (prevIsNote && nextIsNote)
+                return true;
+
+            if ((prevNode == null) && (nextNode == null) &&
+                (CONTAINERS_ALLOWING_CHILDREN[type] ||
+                (isInlineNode(node) && !isOpaqueNode(node) && (type != HTML_BR))))
+                return true;
+
+            if ((prevNode != null) && isSpecialBlockNode(prevNode))
+                return true;
+            if ((nextNode != null) && isSpecialBlockNode(nextNode))
+                return true;
+
+            if ((nextNode != null) && isItemNumber(nextNode))
+                return false;
+            if ((prevNode != null) && isItemNumber(prevNode))
+                return ((nextNode == null) || isWhitespaceTextNode(nextNode));
+
+            if ((nextNode != null) && (nextType == HTML_BR))
+                return ((prevType == 0) || (prevType != HTML_TEXT));
+
+            if ((prevNode != null) && (isOpaqueNode(prevNode) || (prevType == HTML_TABLE))) {
+
+                switch (nextType) {
+                case 0:
+                case HTML_TEXT:
+                case HTML_TABLE:
+                    return true;
+                default:
+                    return isOpaqueNode(nextNode);
+                }
+            }
+            if ((nextNode != null) && (isOpaqueNode(nextNode) || (nextType == HTML_TABLE))) {
+                switch (prevType) {
+                case 0:
+                case HTML_TEXT:
+                case HTML_TABLE:
+                    return true;
+                default:
+                    return isOpaqueNode(prevNode);
+                }
+            }
+        }
+
+        return false;
+    }
+
+    Position_prevMatch = function(pos,fun)
+    {
+        do {
+            pos = Position_prev(pos);
+        } while ((pos != null) && !fun(pos));
+        return pos;
+    }
+
+    Position_nextMatch = function(pos,fun)
+    {
+        do {
+            pos = Position_next(pos);
+        } while ((pos != null) && !fun(pos));
+        return pos;
+    }
+
+    function findEquivalentValidPosition(pos,fun)
+    {
+        var node = pos.node;
+        var offset = pos.offset;
+        if (node.nodeType == Node.ELEMENT_NODE) {
+            var before = node.childNodes[offset-1];
+            var after = node.childNodes[offset];
+            if ((before != null) && (before.nodeType == Node.TEXT_NODE)) {
+                var candidate = new Position(before,before.nodeValue.length);
+                if (fun(candidate))
+                    return candidate;
+            }
+            if ((after != null) && (after.nodeType == Node.TEXT_NODE)) {
+                var candidate = new Position(after,0);
+                if (fun(candidate))
+                    return candidate;
+            }
+        }
+
+        if ((pos.node.nodeType == Node.TEXT_NODE) &&
+            isWhitespaceString(pos.node.nodeValue.slice(pos.offset))) {
+            var str = pos.node.nodeValue;
+            var whitespace = str.match(/\s+$/);
+            if (whitespace) {
+                var adjusted = new Position(pos.node,
+                                            str.length - whitespace[0].length + 1);
+                return adjusted;
+            }
+        }
+        return pos;
+    }
+
+    // public
+    Position_closestMatchForwards = function(pos,fun)
+    {
+        if (pos == null)
+            return null;
+
+        if (!fun(pos))
+            pos = findEquivalentValidPosition(pos,fun);
+
+        if (fun(pos))
+            return pos;
+
+        var next = Position_nextMatch(pos,fun);
+        if (next != null)
+            return next;
+
+        var prev = Position_prevMatch(pos,fun);
+        if (prev != null)
+            return prev;
+
+        return new Position(document.body,document.body.childNodes.length);
+    }
+
+    // public
+    Position_closestMatchBackwards = function(pos,fun)
+    {
+        if (pos == null)
+            return null;
+
+        if (!fun(pos))
+            pos = findEquivalentValidPosition(pos,fun);
+
+        if (fun(pos))
+            return pos;
+
+        var prev = Position_prevMatch(pos,fun);
+        if (prev != null)
+            return prev;
+
+        var next = Position_nextMatch(pos,fun);
+        if (next != null)
+            return next;
+
+        return new Position(document.body,0);
+    }
+
+    Position_track = function(pos)
+    {
+        startTracking(pos.self);
+    }
+
+    Position_untrack = function(pos)
+    {
+        stopTracking(pos.self);
+    }
+
+    Position_rectAtPos = function(pos)
+    {
+        if (pos == null)
+            return null;
+        var range = new Range(pos.node,pos.offset,pos.node,pos.offset);
+        var rects = Range_getClientRects(range);
+
+        if ((rects.length > 0) && !rectIsEmpty(rects[0])) {
+            return rects[0];
+        }
+
+        if (isParagraphNode(pos.node) && (pos.offset == 0)) {
+            var rect = pos.node.getBoundingClientRect();
+            if (!rectIsEmpty(rect))
+                return rect;
+        }
+
+        return null;
+    }
+
+    function posAtStartOfParagraph(pos,paragraph)
+    {
+        return ((pos.node == paragraph.node) &&
+                (pos.offset == paragraph.startOffset));
+    }
+
+    function posAtEndOfParagraph(pos,paragraph)
+    {
+        return ((pos.node == paragraph.node) &&
+                (pos.offset == paragraph.endOffset));
+    }
+
+    function zeroWidthRightRect(rect)
+    {
+        return { left: rect.right, // 0 width
+                 right: rect.right,
+                 top: rect.top,
+                 bottom: rect.bottom,
+                 width: 0,
+                 height: rect.height };
+    }
+
+    function zeroWidthLeftRect(rect)
+    {
+        return { left: rect.left,
+                 right: rect.left, // 0 width
+                 top: rect.top,
+                 bottom: rect.bottom,
+                 width: 0,
+                 height: rect.height };
+    }
+
+    function zeroWidthMidRect(rect)
+    {
+        var mid = rect.left + rect.width/2;
+        return { left: mid,
+                 right: mid, // 0 width
+                 top: rect.top,
+                 bottom: rect.bottom,
+                 width: 0,
+                 height: rect.height };
+    }
+
+    Position_noteAncestor = function(pos)
+    {
+        var node = Position_closestActualNode(pos);
+        for (; node != null; node = node.parentNode) {
+            if (isNoteNode(node))
+                return node;
+        }
+        return null;
+    }
+
+    Position_captionAncestor = function(pos)
+    {
+        var node = Position_closestActualNode(pos);
+        for (; node != null; node = node.parentNode) {
+            if ((node._type == HTML_FIGCAPTION) || (node._type == HTML_CAPTION))
+                return node;
+        }
+        return null;
+    }
+
+    Position_figureOrTableAncestor = function(pos)
+    {
+        var node = Position_closestActualNode(pos);
+        for (; node != null; node = node.parentNode) {
+            if ((node._type == HTML_FIGURE) || (node._type == HTML_TABLE))
+                return node;
+        }
+        return null;
+    }
+
+    function exactRectAtPos(pos)
+    {
+        var node = pos.node;
+        var offset = pos.offset;
+
+        if (node.nodeType == Node.ELEMENT_NODE) {
+            if (offset > node.childNodes.length)
+                throw new Error("Invalid offset: "+offset+" of "+node.childNodes.length);
+
+            var before = node.childNodes[offset-1];
+            var after = node.childNodes[offset];
+
+            // Cursor is immediately before table -> return table rect
+            if ((before != null) && isSpecialBlockNode(before))
+                return zeroWidthRightRect(before.getBoundingClientRect());
+
+            // Cursor is immediately after table -> return table rect
+            else if ((after != null) && isSpecialBlockNode(after))
+                return zeroWidthLeftRect(after.getBoundingClientRect());
+
+            // Start of empty paragraph
+            if ((node.nodeType == Node.ELEMENT_NODE) && (offset == 0) &&
+                isParagraphNode(node) && !nodeHasContent(node)) {
+                return zeroWidthLeftRect(node.getBoundingClientRect());
+            }
+
+            return null;
+        }
+        else if (node.nodeType == Node.TEXT_NODE) {
+            // First see if the client rects returned by the range gives us a valid value. This
+            // won't be the case if the cursor is surrounded by both sides on whitespace.
+            var result = rectAtRightOfRange(new Range(node,offset,node,offset));
+            if (result != null)
+                return result;
+
+            if (offset > 0) {
+                // Try and get the rect of the previous character; the cursor goes after that
+                var result = rectAtRightOfRange(new Range(node,offset-1,node,offset));
+                if (result != null)
+                    return result;
+            }
+
+            return null;
+        }
+        else {
+            return null;
+        }
+
+        function rectAtRightOfRange(range)
+        {
+            var rects = Range_getClientRects(range);
+            if ((rects == null) || (rects.length == 0) || (rects[rects.length-1].height == 0))
+                return null;
+            return zeroWidthRightRect(rects[rects.length-1]);
+        }
+    }
+
+    function tempSpaceRect(parentNode,nextSibling)
+    {
+        var space = DOM_createTextNode(document,String.fromCharCode(160));
+        DOM_insertBefore(parentNode,space,nextSibling);
+        var range = new Range(space,0,space,1);
+        var rects = Range_getClientRects(range);
+        DOM_deleteNode(space);
+        if (rects.length > 0)
+            return rects[0];
+        else
+            return nil;
+    }
+
+    Position_displayRectAtPos = function(pos)
+    {
+        rect = exactRectAtPos(pos);
+        if (rect != null)
+            return rect;
+
+        var noteNode = Position_noteAncestor(pos);
+        if ((noteNode != null) && !nodeHasContent(noteNode)) // In empty footnote or endnote
+            return zeroWidthMidRect(noteNode.getBoundingClientRect());
+
+        // If we're immediately before or after a footnote or endnote, calculate the rect by
+        // temporarily inserting a space character, and getting the rect at the start of that.
+        // This avoids us instead getting a rect inside the note, which is what would otherwise
+        // happen if there was no adjacent text node outside the note.
+        if ((pos.node.nodeType == Node.ELEMENT_NODE)) {
+            var before = pos.node.childNodes[pos.offset-1];
+            var after = pos.node.childNodes[pos.offset];
+            if (((before != null) && isNoteNode(before)) ||
+                ((after != null) && isNoteNode(after))) {
+                var rect = tempSpaceRect(pos.node,pos.node.childNodes[pos.offset]);
+                if (rect != null)
+                    return zeroWidthLeftRect(rect);
+            }
+        }
+
+        var captionNode = Position_captionAncestor(pos);
+        if ((captionNode != null) && !nodeHasContent(captionNode)) {
+            // Even if an empty caption has generated content (e.g. "Figure X: ") preceding it,
+            // we can't directly get the rect of that generated content. So we temporarily insert
+            // a text node containing a single space character, get the position to the right of
+            // that character, and then remove the text node.
+            var rect = tempSpaceRect(captionNode,null);
+            if (rect != null)
+                return zeroWidthRightRect(rect);
+        }
+
+        var paragraph = Text_findParagraphBoundaries(pos);
+
+        var backRect = null;
+        for (var backPos = pos; backPos != null; backPos = Position_prev(backPos)) {
+            backRect = exactRectAtPos(backPos);
+            if ((backRect != null) || posAtStartOfParagraph(backPos,paragraph))
+                break;
+        }
+
+        var forwardRect = null;
+        for (var forwardPos = pos; forwardPos != null; forwardPos = Position_next(forwardPos)) {
+            forwardRect = exactRectAtPos(forwardPos);
+            if ((forwardRect != null) || posAtEndOfParagraph(forwardPos,paragraph))
+                break;
+        }
+
+        if (backRect != null) {
+            return backRect;
+        }
+        else if (forwardRect != null) {
+            return forwardRect;
+        }
+        else {
+            // Fallback, e.g. for empty LI elements
+            var node = pos.node;
+            if (node.nodeType == Node.TEXT_NODE)
+                node = node.parentNode;
+            return zeroWidthLeftRect(node.getBoundingClientRect());
+        }
+    }
+
+    Position_equal = function(a,b)
+    {
+        if ((a == null) && (b == null))
+            return true;
+        if ((a != null) && (b != null) &&
+            (a.node == b.node) && (a.offset == b.offset))
+            return true;
+        return false;
+    }
+
+    Position_preferTextPosition = function(pos)
+    {
+        var node = pos.node;
+        var offset = pos.offset;
+        if (node.nodeType == Node.ELEMENT_NODE) {
+            var before = node.childNodes[offset-1];
+            var after = node.childNodes[offset];
+            if ((before != null) && (before.nodeType == Node.TEXT_NODE))
+                return new Position(before,before.nodeValue.length);
+            if ((after != null) && (after.nodeType == Node.TEXT_NODE))
+                return new Position(after,0);
+        }
+        return pos;
+    }
+
+    Position_preferElementPosition = function(pos)
+    {
+        if (pos.node.nodeType == Node.TEXT_NODE) {
+            if (pos.node.parentNode == null)
+                throw new Error("Position "+pos+" has no parent node");
+            if (pos.offset == 0)
+                return new Position(pos.node.parentNode,DOM_nodeOffset(pos.node));
+            if (pos.offset == pos.node.nodeValue.length)
+                return new Position(pos.node.parentNode,DOM_nodeOffset(pos.node)+1);
+        }
+        return pos;
+    }
+
+    Position_compare = function(first,second)
+    {
+        if ((first.node == second.node) && (first.offset == second.offset))
+            return 0;
+
+        var doc = first.node.ownerDocument;
+        if ((first.node.parentNode == null) && (first.node != doc.documentElement))
+            throw new Error("First node has been removed from document");
+        if ((second.node.parentNode == null) && (second.node != doc.documentElement))
+            throw new Error("Second node has been removed from document");
+
+        if (first.node == second.node)
+            return first.offset - second.offset;
+
+        var firstParent = null;
+        var firstChild = null;
+        var secondParent = null;
+        var secondChild = null;
+
+        if (second.node.nodeType == Node.ELEMENT_NODE) {
+            secondParent = second.node;
+            secondChild = second.node.childNodes[second.offset];
+        }
+        else {
+            secondParent = second.node.parentNode;
+            secondChild = second.node;
+        }
+
+        if (first.node.nodeType == Node.ELEMENT_NODE) {
+            firstParent = first.node;
+            firstChild = first.node.childNodes[first.offset];
+        }
+        else {
+            firstParent = first.node.parentNode;
+            firstChild = first.node;
+            if (firstChild == secondChild)
+                return 1;
+        }
+
+        var firstC = firstChild;
+        var firstP = firstParent;
+        while (firstP != null) {
+
+            var secondC = secondChild;
+            var secondP = secondParent;
+            while (secondP != null) {
+
+                if (firstP == secondC)
+                    return 1;
+
+                if (firstP == secondP) {
+                    // if secondC is last child, firstC must be secondC or come before it
+                    if (secondC == null)
+                        return -1;
+                    for (var n = firstC; n != null; n = n.nextSibling) {
+                        if (n == secondC)
+                            return -1;
+                    }
+                    return 1;
+                }
+
+                secondC = secondP;
+                secondP = secondP.parentNode;
+            }
+
+            firstC = firstP;
+            firstP = firstP.parentNode;
+        }
+        throw new Error("Could not find common ancestor");
+    }
+
+    // This function works around a bug in WebKit where caretRangeFromPoint sometimes returns an
+    // incorrect node (the last text node in the document). In a previous attempt to fix this bug,
+    // we first checked if the point was in the elements bounding rect, but this meant that it
+    // wasn't possible to place the cursor at the nearest node, if the click location was not
+    // exactly on a node.
+
+    // Now we instead check to see if the result of elementFromPoint is the same as the parent node
+    // of the text node returned by caretRangeFromPoint. If it isn't, then we assume that the latter
+    // result is incorrect, and return null.
+
+    // In the circumstances where this bug was observed, the last text node in the document was
+    // being returned from caretRangeFromPoint in some cases. In the typical case, this is going to
+    // be inside a paragraph node, but elementNodeFromPoint was returning the body element. The
+    // check we do now comparing the results of the two functions fixes this case, but won't work as
+    // intended if the document's last text node is a direct child of the body (as it may be in some
+    // HTML documents that users open).
+
+    function posOutsideSelection(pos)
+    {
+        pos = Position_preferElementPosition(pos);
+
+        if (!isSelectionSpan(pos.node))
+            return pos;
+
+        if (pos.offset == 0)
+            return new Position(pos.node.parentNode,DOM_nodeOffset(pos.node));
+        else if (pos.offset == pos.node.childNodes.length)
+            return new Position(pos.node.parentNode,DOM_nodeOffset(pos.node)+1);
+        else
+            return pos;
+    }
+
+    Position_atPoint = function(x,y)
+    {
+        // In general, we can use document.caretRangeFromPoint(x,y) to determine the location of the
+        // cursor based on screen coordinates. However, this doesn't work if the screen coordinates
+        // are outside the bounding box of the document's body. So when this is true, we find either
+        // the first or last non-whitespace text node, calculate a y value that is half-way between
+        // the top and bottom of its first or last rect (respectively), and use that instead. This
+        // results in the cursor being placed on the first or last line when the user taps outside
+        // the document bounds.
+
+        var bodyRect = document.body.getBoundingClientRect();
+        var boundaryRect = null;
+        if (y <= bodyRect.top)
+            boundaryRect = findFirstTextRect();
+        else if (y >= bodyRect.bottom)
+            boundaryRect = findLastTextRect();
+
+        if (boundaryRect != null)
+            y = boundaryRect.top + boundaryRect.height/2;
+
+        // We get here if the coordinates are inside the document's bounding rect, or if getting the
+        // position from the first or last rect failed for some reason.
+
+        var range = document.caretRangeFromPoint(x,y);
+        if (range == null)
+            return null;
+
+        var pos = new Position(range.startContainer,range.startOffset);
+        pos = Position_preferElementPosition(pos);
+
+        if (pos.node.nodeType == Node.ELEMENT_NODE) {
+            var outside = posOutsideSelection(pos);
+            var prev = outside.node.childNodes[outside.offset-1];
+            var next = outside.node.childNodes[outside.offset];
+
+            if ((prev != null) && nodeMayContainPos(prev) && elementContainsPoint(prev,x,y))
+                return new Position(prev,0);
+
+            if ((next != null) && nodeMayContainPos(next) && elementContainsPoint(next,x,y))
+                return new Position(next,0);
+
+            if (next != null) {
+                var nextNode = outside.node;
+                var nextOffset = outside.offset+1;
+
+                if (isSelectionSpan(next) && (next.firstChild != null)) {
+                    nextNode = next;
+                    nextOffset = 1;
+                    next = next.firstChild;
+                }
+
+                if ((next != null) && isEmptyNoteNode(next)) {
+                    var rect = next.getBoundingClientRect();
+                    if (x > rect.right)
+                        return new Position(nextNode,nextOffset);
+                }
+            }
+        }
+
+        pos = adjustPositionForFigure(pos);
+
+        return pos;
+    }
+
+    // This is used for nodes that can potentially be the right match for a hit test, but for
+    // which caretRangeFromPoint() returns the wrong result
+    function nodeMayContainPos(node)
+    {
+        return ((node._type == HTML_IMG) || isEmptyNoteNode(node));
+    }
+
+    function elementContainsPoint(element,x,y)
+    {
+        var rect = element.getBoundingClientRect();
+        return ((x >= rect.left) && (x <= rect.right) &&
+                (y >= rect.top) && (y <= rect.bottom));
+    }
+
+    function isEmptyParagraphNode(node)
+    {
+        return ((node._type == HTML_P) &&
+                (node.lastChild != null) &&
+                (node.lastChild._type == HTML_BR) &&
+                !nodeHasContent(node));
+    }
+
+    function findLastTextRect()
+    {
+        var node = lastDescendant(document.body);
+
+        while ((node != null) &&
+               ((node.nodeType != Node.TEXT_NODE) || isWhitespaceTextNode(node))) {
+            if (isEmptyParagraphNode(node))
+                return node.getBoundingClientRect();
+            node = prevNode(node);
+        }
+
+        if (node != null) {
+            var domRange = document.createRange();
+            domRange.setStart(node,0);
+            domRange.setEnd(node,node.nodeValue.length);
+            var rects = domRange.getClientRects();
+            if ((rects != null) && (rects.length > 0))
+                return rects[rects.length-1];
+        }
+        return null;
+    }
+
+    function findFirstTextRect()
+    {
+        var node = firstDescendant(document.body);
+
+        while ((node != null) &&
+               ((node.nodeType != Node.TEXT_NODE) || isWhitespaceTextNode(node))) {
+            if (isEmptyParagraphNode(node))
+                return node.getBoundingClientRect();
+            node = nextNode(node);
+        }
+
+        if (node != null) {
+            var domRange = document.createRange();
+            domRange.setStart(node,0);
+            domRange.setEnd(node,node.nodeValue.length);
+            var rects = domRange.getClientRects();
+            if ((rects != null) && (rects.length > 0))
+                return rects[0];
+        }
+        return null;
+    }
+
+    function adjustPositionForFigure(position)
+    {
+        if (position == null)
+            return null;
+        if (position.node._type == HTML_FIGURE) {
+            var prev = position.node.childNodes[position.offset-1];
+            var next = position.node.childNodes[position.offset];
+            if ((prev != null) && (prev._type == HTML_IMG)) {
+                position = new Position(position.node.parentNode,
+                                        DOM_nodeOffset(position.node)+1);
+            }
+            else if ((next != null) && (next._type == HTML_IMG)) {
+                position = new Position(position.node.parentNode,
+                                        DOM_nodeOffset(position.node));
+            }
+        }
+        return position;
+    }
+
+})();

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9bf02bb2/experiments/editorFramework/src/Javascript_Layer_0/PostponedActions.js
----------------------------------------------------------------------
diff --git a/experiments/editorFramework/src/Javascript_Layer_0/PostponedActions.js b/experiments/editorFramework/src/Javascript_Layer_0/PostponedActions.js
new file mode 100644
index 0000000..d445536
--- /dev/null
+++ b/experiments/editorFramework/src/Javascript_Layer_0/PostponedActions.js
@@ -0,0 +1,55 @@
+// 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 PostponedActions_add;
+var PostponedActions_perform;
+
+(function() {
+
+    function PostponedAction(fun,undoDisabled)
+    {
+        this.fun = fun;
+        this.undoDisabled = undoDisabled;
+    }
+
+    var actions = new Array();
+
+    PostponedActions_add = function(action)
+    {
+        actions.push(new PostponedAction(action,UndoManager_isDisabled()));
+    }
+
+    PostponedActions_perform = function()
+    {
+        var count = 0;
+        while (actions.length > 0) {
+            if (count >= 10)
+                throw new Error("Too many postponed actions");
+            var actionsToPerform = actions;
+            actions = new Array();
+            for (var i = 0; i < actionsToPerform.length; i++) {
+                var action = actionsToPerform[i];
+                if (action.undoDisabled)
+                    UndoManager_disableWhileExecuting(action.fun);
+                else
+                    action.fun();
+            }
+            count++;
+        }
+    }
+
+})();

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9bf02bb2/experiments/editorFramework/src/Javascript_Layer_0/Preview.js
----------------------------------------------------------------------
diff --git a/experiments/editorFramework/src/Javascript_Layer_0/Preview.js b/experiments/editorFramework/src/Javascript_Layer_0/Preview.js
new file mode 100644
index 0000000..97f9bf1
--- /dev/null
+++ b/experiments/editorFramework/src/Javascript_Layer_0/Preview.js
@@ -0,0 +1,139 @@
+// 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 Preview_showForStyle;
+
+(function(){
+
+    var previewText =
+        "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec in diam \n"+
+        "mauris. Integer in lorem sit amet dolor lacinia aliquet. Cras vehicula odio \n"+
+        "non enim euismod nec congue lorem varius. Sed eu libero arcu, eget tempus \n"+
+        "augue. Vivamus varius risus ac libero sagittis eu ultricies lectus \n"+
+        "consequat. Integer gravida accumsan fermentum. Morbi erat ligula, volutpat \n"+
+        "non accumsan sed, pellentesque quis purus. Vestibulum vestibulum tincidunt \n"+
+        "lectus non pellentesque. Quisque porttitor sollicitudin tellus, id porta \n"+
+        "velit interdum sit amet. Cras quis sem orci, vel convallis magna. \n"+
+        "Pellentesque congue, libero et iaculis volutpat, enim turpis sodales dui, \n"+
+        "lobortis pharetra lectus dolor at sem. Nullam aliquam, odio ac laoreet \n"+
+        "vulputate, ligula nunc euismod leo, vel bibendum magna leo ut orci. In \n"+
+        "tortor turpis, pellentesque nec cursus ut, consequat non ipsum. Praesent \n"+
+        "venenatis, leo in pulvinar pharetra, eros nisi convallis elit, vitae luctus \n"+
+        "magna velit ut lorem."
+
+    function setTableCellContents(node)
+    {
+        if (isTableCell(node)) {
+            DOM_deleteAllChildren(node);
+            DOM_appendChild(node,DOM_createTextNode(document,"Cell contents"));
+        }
+        else {
+            for (var child = node.firstChild; child != null; child = child.nextSibling)
+                setTableCellContents(child);
+        }
+    }
+
+    function showForStyle(styleId,uiName,titleText)
+    {
+        var elementName = null;
+        var className = null;
+
+        var dotPos = styleId.indexOf(".");
+        if (dotPos >= 0) {
+            elementName = styleId.substring(0,dotPos);
+            className = styleId.substring(dotPos+1);
+        }
+        else {
+            elementName = styleId;
+            className = null;
+        }
+
+        var title = DOM_createTextNode(document,titleText);
+        var text = DOM_createTextNode(document,previewText);
+
+        Selection_clear();
+        DOM_deleteAllChildren(document.body);
+
+        if (PARAGRAPH_ELEMENTS[ElementTypes[elementName]]) {
+            var paragraph1 = createParagraphElement(elementName,className);
+            var paragraph2 = createParagraphElement(elementName,className);
+            DOM_appendChild(paragraph1,title);
+            DOM_appendChild(paragraph2,text);
+            DOM_appendChild(document.body,paragraph1);
+            DOM_appendChild(document.body,paragraph2);
+
+            if (className != null) {
+                DOM_setAttribute(paragraph1,"class",className);
+                DOM_setAttribute(paragraph2,"class",className);
+            }
+        }
+        else if (elementName == "span") {
+            var p1 = DOM_createElement(document,"P");
+            var p2 = DOM_createElement(document,"P");
+            var span1 = DOM_createElement(document,"SPAN");
+            var span2 = DOM_createElement(document,"SPAN");
+
+            if (className != null) {
+                DOM_setAttribute(span1,"class",className);
+                DOM_setAttribute(span2,"class",className);
+            }
+
+            DOM_appendChild(span1,title);
+            DOM_appendChild(span2,text);
+
+            DOM_appendChild(p1,span1);
+            DOM_appendChild(p2,span2);
+
+            DOM_appendChild(document.body,p1);
+            DOM_appendChild(document.body,p2);
+        }
+        else if ((elementName == "table") || (elementName == "caption")) {
+            // FIXME: cater for different table styles
+            Selection_selectAll();
+            Tables_insertTable(3,3,"66%",true,"Table caption");
+            Selection_clear();
+            var table = document.getElementsByTagName("TABLE")[0];
+            setTableCellContents(table);
+            if ((elementName == "table") && (className != null))
+                DOM_setAttribute(table,"class",className);
+        }
+        else if ((elementName == "figure") || (elementName == "figcaption")) {
+            Selection_selectAll();
+            Figures_insertFigure("SampleFigure.svg","75%",true,"TCP 3-way handshake");
+            Selection_clear();
+        }
+        else if (elementName == "body") {
+            // We use BR here instead of separate paragraphs, since we don't want the properties
+            // for the P element to be applied
+            DOM_appendChild(document.body,title);
+            DOM_appendChild(document.body,DOM_createElement(document,"BR"));
+            DOM_appendChild(document.body,DOM_createElement(document,"BR"));
+            DOM_appendChild(document.body,text);
+        }
+
+        function createParagraphElement(elementName,className)
+        {
+            var element = DOM_createElement(document,elementName);
+            if (className != null)
+                DOM_setAttribute(element,"class",className);
+            return element;
+        }
+    }
+
+    Preview_showForStyle = showForStyle;
+
+})();

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9bf02bb2/experiments/editorFramework/src/Javascript_Layer_0/README
----------------------------------------------------------------------
diff --git a/experiments/editorFramework/src/Javascript_Layer_0/README b/experiments/editorFramework/src/Javascript_Layer_0/README
new file mode 100644
index 0000000..10fd5f9
--- /dev/null
+++ b/experiments/editorFramework/src/Javascript_Layer_0/README
@@ -0,0 +1 @@
+the javascript files doing the actual in file editing and rendering.

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9bf02bb2/experiments/editorFramework/src/Javascript_Layer_0/Range.js
----------------------------------------------------------------------
diff --git a/experiments/editorFramework/src/Javascript_Layer_0/Range.js b/experiments/editorFramework/src/Javascript_Layer_0/Range.js
new file mode 100644
index 0000000..8d7c655
--- /dev/null
+++ b/experiments/editorFramework/src/Javascript_Layer_0/Range.js
@@ -0,0 +1,566 @@
+// 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 Range;
+
+var Range_assertValid;
+var Range_isEmpty;
+var Range_trackWhileExecuting;
+var Range_expand;
+var Range_isForwards;
+var Range_getAllNodes;
+var Range_singleNode;
+var Range_ensureInlineNodesInParagraph;
+var Range_ensureValidHierarchy;
+var Range_forwards;
+var Range_detail;
+var Range_getOutermostNodes;
+var Range_getClientRects;
+var Range_cloneContents;
+var Range_hasContent;
+var Range_getText;
+
+(function() {
+
+    Range = function(startNode,startOffset,endNode,endOffset)
+    {
+        this.start = new Position(startNode,startOffset);
+        this.end = new Position(endNode,endOffset);
+    }
+
+    Range_assertValid = function(range,description)
+    {
+        if (description == null)
+            description = "Range";
+        if (range == null)
+            throw new Error(description+" is null");
+        Position_assertValid(range.start,description+" start");
+        Position_assertValid(range.end,description+" end");
+    }
+
+    Range_isEmpty = function(range)
+    {
+        return ((range.start.node == range.end.node) &&
+                (range.start.offset == range.end.offset));
+    }
+
+    Range.prototype.toString = function()
+    {
+        return this.start.toString() + " - " + this.end.toString();
+    }
+
+    Range_trackWhileExecuting = function(range,fun)
+    {
+        if (range == null)
+            return fun();
+        else
+            return Position_trackWhileExecuting([range.start,range.end],fun);
+    }
+
+    Range_expand = function(range)
+    {
+        var doc = range.start.node.ownerDocument;
+        while ((range.start.offset == 0) && (range.start.node != doc.body)) {
+            var offset = DOM_nodeOffset(range.start.node);
+            range.start.node = range.start.node.parentNode;
+            range.start.offset = offset;
+        }
+
+        while ((range.end.offset == DOM_maxChildOffset(range.end.node)) &&
+               (range.end.node != doc.body)) {
+            var offset = DOM_nodeOffset(range.end.node);
+            range.end.node = range.end.node.parentNode;
+            range.end.offset = offset+1;
+        }
+    }
+
+    Range_isForwards = function(range)
+    {
+        return (Position_compare(range.start,range.end) <= 0);
+    }
+
+    Range_getAllNodes = function(range,atLeastOne)
+    {
+        var result = new Array();
+        var outermost = Range_getOutermostNodes(range,atLeastOne);
+        for (var i = 0; i < outermost.length; i++)
+            addRecursive(outermost[i]);
+        return result;
+
+        function addRecursive(node)
+        {
+            result.push(node);
+            for (var child = node.firstChild; child != null; child = child.nextSibling)
+                addRecursive(child);
+        }
+    }
+
+    Range_singleNode = function(range)
+    {
+        return Position_closestActualNode(range.start,true);
+    }
+
+    Range_ensureInlineNodesInParagraph = function(range)
+    {
+        Range_trackWhileExecuting(range,function() {
+            var nodes = Range_getAllNodes(range,true);
+            for (var i = 0; i < nodes.length; i++)
+                Hierarchy_ensureInlineNodesInParagraph(nodes[i]);
+        });
+    }
+
+    Range_ensureValidHierarchy = function(range,allowDirectInline)
+    {
+        Range_trackWhileExecuting(range,function() {
+            var nodes = Range_getAllNodes(range,true);
+            for (var i = nodes.length-1; i >= 0; i--)
+                Hierarchy_ensureValidHierarchy(nodes[i],true,allowDirectInline);
+        });
+    }
+
+    Range_forwards = function(range)
+    {
+        if (Range_isForwards(range)) {
+            return range;
+        }
+        else {
+            var reverse = new Range(range.end.node,range.end.offset,
+                                    range.start.node,range.start.offset);
+            if (!Range_isForwards(reverse))
+                throw new Error("Both range "+range+" and its reverse are not forwards");
+            return reverse;
+        }
+    }
+
+    Range_detail = function(range)
+    {
+        if (!Range_isForwards(range)) {
+            var reverse = new Range(range.end.node,range.end.offset,
+                                    range.start.node,range.start.offset);
+            if (!Range_isForwards(reverse))
+                throw new Error("Both range "+range+" and its reverse are not forwards");
+            return Range_detail(reverse);
+        }
+
+        var detail = new Object();
+        var start = range.start;
+        var end = range.end;
+
+        // Start location
+        if (start.node.nodeType == Node.ELEMENT_NODE) {
+            detail.startParent = start.node;
+            detail.startChild = start.node.childNodes[start.offset];
+        }
+        else {
+            detail.startParent = start.node.parentNode;
+            detail.startChild = start.node;
+        }
+
+        // End location
+        if (end.node.nodeType == Node.ELEMENT_NODE) {
+            detail.endParent = end.node;
+            detail.endChild = end.node.childNodes[end.offset];
+        }
+        else if (end.offset == 0) {
+            detail.endParent = end.node.parentNode;
+            detail.endChild = end.node;
+        }
+        else {
+            detail.endParent = end.node.parentNode;
+            detail.endChild = end.node.nextSibling;
+        }
+
+        // Common ancestor
+        var startP = detail.startParent;
+        var startC = detail.startChild;
+        while (startP != null) {
+            var endP = detail.endParent;
+            var endC = detail.endChild
+            while (endP != null) {
+                if (startP == endP) {
+                    detail.commonAncestor = startP;
+                    detail.startAncestor = startC;
+                    detail.endAncestor = endC;
+                    // Found it
+                    return detail;
+                }
+                endC = endP;
+                endP = endP.parentNode;
+            }
+            startC = startP;
+            startP = startP.parentNode;
+        }
+        throw new Error("Start and end of range have no common ancestor");
+    }
+
+    Range_getOutermostNodes = function(range,atLeastOne,info)
+    {
+        var beforeNodes = new Array();
+        var middleNodes = new Array();
+        var afterNodes = new Array();
+
+        if (info != null) {
+            info.beginning = beforeNodes;
+            info.middle = middleNodes;
+            info.end = afterNodes;
+        }
+
+        if (Range_isEmpty(range))
+            return atLeastOne ? [Range_singleNode(range)] : [];
+
+        // Note: start and end are *points* - they are always *in between* nodes or characters, never
+        // *at* a node or character.
+        // Everything after the end point is excluded from the selection
+        // Everything after the start point, but before the end point, is included in the selection
+
+        // We use (parent,child) pairs so that we have a way to represent a point that comes after all
+        // the child nodes in a container - in which case the child is null. The parent, however, is
+        // always non-null;
+
+        var detail = Range_detail(range);
+        if (detail.commonAncestor == null)
+            return atLeastOne ? [Range_singleNode(range)] : [];
+        var startParent = detail.startParent;
+        var startChild = detail.startChild;
+        var endParent = detail.endParent;
+        var endChild = detail.endChild;
+        var commonParent = detail.commonAncestor;
+        var startAncestor = detail.startAncestor;
+        var endAncestor = detail.endAncestor;
+
+        // Add start nodes
+        var topParent = startParent;
+        var topChild = startChild;
+        while (topParent != commonParent) {
+            if (topChild != null)
+                beforeNodes.push(topChild);
+
+            while (((topChild == null) || (topChild.nextSibling == null)) &&
+                   (topParent != commonParent)) {
+                topChild = topParent;
+                topParent = topParent.parentNode;
+            }
+            if (topParent != commonParent)
+                topChild = topChild.nextSibling;
+        }
+
+        // Add middle nodes
+        if (startAncestor != endAncestor) {
+            var c = startAncestor;
+            if ((c != null) && (c != startChild))
+                c = c.nextSibling;
+            for (; c != endAncestor; c = c.nextSibling)
+                middleNodes.push(c);
+        }
+
+        // Add end nodes
+        var bottomParent = endParent;
+        var bottomChild = endChild;
+        while (true) {
+
+            while ((getPreviousSibling(bottomParent,bottomChild) == null) &&
+                   (bottomParent != commonParent)) {
+                bottomChild = bottomParent;
+                bottomParent = bottomParent.parentNode;
+            }
+            if (bottomParent != commonParent)
+                bottomChild = getPreviousSibling(bottomParent,bottomChild);
+
+            if (bottomParent == commonParent)
+                break;
+
+            afterNodes.push(bottomChild);
+        }
+        afterNodes = afterNodes.reverse();
+
+        var result = new Array();
+
+        Array.prototype.push.apply(result,beforeNodes);
+        Array.prototype.push.apply(result,middleNodes);
+        Array.prototype.push.apply(result,afterNodes);
+
+        if (result.length == 0)
+            return atLeastOne ? [Range_singleNode(range)] : [];
+        else
+            return result;
+
+        function getPreviousSibling(parent,child)
+        {
+            if (child != null)
+                return child.previousSibling;
+            else if (parent.lastChild != null)
+                return parent.lastChild;
+            else
+                return null;
+        }
+
+        function isAncestorLocation(ancestorParent,ancestorChild,
+                                    descendantParent,descendantChild)
+        {
+            while ((descendantParent != null) &&
+                   ((descendantParent != ancestorParent) || (descendantChild != ancestorChild))) {
+                descendantChild = descendantParent;
+                descendantParent = descendantParent.parentNode;
+            }
+
+            return ((descendantParent == ancestorParent) &&
+                    (descendantChild == ancestorChild));
+        }
+    }
+
+    Range_getClientRects = function(range)
+    {
+        var nodes = Range_getOutermostNodes(range,true);
+
+        // WebKit in iOS 5.0 and 5.1 has a bug where if the selection spans multiple paragraphs,
+        // the complete rect for paragraphs other than the first is returned, instead of just the
+        // portions of it that are actually in the range. To get around this problem, we go through
+        // each text node individually and collect all the rects.
+        var result = new Array();
+        var doc = range.start.node.ownerDocument;
+        var domRange = doc.createRange();
+        for (var nodeIndex = 0; nodeIndex < nodes.length; nodeIndex++) {
+            var node = nodes[nodeIndex];
+            if (node.nodeType == Node.TEXT_NODE) {
+                var startOffset = (node == range.start.node) ? range.start.offset : 0;
+                var endOffset = (node == range.end.node) ? range.end.offset : node.nodeValue.length;
+                domRange.setStart(node,startOffset);
+                domRange.setEnd(node,endOffset);
+                var rects = domRange.getClientRects();
+                for (var rectIndex = 0; rectIndex < rects.length; rectIndex++) {
+                    var rect = rects[rectIndex];
+                    if (Main_clientRectsBug) {
+                        // Apple Bug ID 14682166 - getClientRects() returns coordinates relative
+                        // to top of document, when it should instead return coordinates relative
+                        // to the current client view (that is, taking into account scroll offsets)
+                        result.push({ left: rect.left - window.scrollX,
+                                      right: rect.right - window.scrollX,
+                                      top: rect.top - window.scrollY,
+                                      bottom: rect.bottom - window.scrollY,
+                                      width: rect.width,
+                                      height: rect.height });
+                    }
+                    else {
+                        result.push(rect);
+                    }
+                }
+            }
+            else if (node.nodeType == Node.ELEMENT_NODE) {
+                result.push(node.getBoundingClientRect());
+            }
+        }
+        return result;
+    }
+
+    Range_cloneContents = function(range)
+    {
+        var nodeSet = new NodeSet();
+        var ancestorSet = new NodeSet();
+        var detail = Range_detail(range);
+        var outermost = Range_getOutermostNodes(range);
+
+        var haveContent = false;
+        for (var i = 0; i < outermost.length; i++) {
+            if (!isWhitespaceTextNode(outermost[i]))
+                haveContent = true;
+            nodeSet.add(outermost[i]);
+            for (var node = outermost[i]; node != null; node = node.parentNode)
+                ancestorSet.add(node);
+        }
+
+        if (!haveContent)
+            return new Array();
+
+        var clone = recurse(detail.commonAncestor);
+
+        var ancestor = detail.commonAncestor;
+        while (isInlineNode(ancestor)) {
+            var ancestorClone = DOM_cloneNode(ancestor.parentNode,false);
+            DOM_appendChild(ancestorClone,clone);
+            ancestor = ancestor.parentNode;
+            clone = ancestorClone;
+        }
+
+        var childArray = new Array();
+        switch (clone._type) {
+        case HTML_UL:
+        case HTML_OL:
+            childArray.push(clone);
+            break;
+        default:
+            for (var child = clone.firstChild; child != null; child = child.nextSibling)
+                childArray.push(child);
+            Formatting_pushDownInlineProperties(childArray);
+            break;
+        }
+
+        return childArray;
+
+        function recurse(parent)
+        {
+            var clone = DOM_cloneNode(parent,false);
+            for (var child = parent.firstChild; child != null; child = child.nextSibling) {
+                if (nodeSet.contains(child)) {
+                    if ((child.nodeType == Node.TEXT_NODE) &&
+                        (child == range.start.node) &&
+                        (child == range.end.node)) {
+                        var substring = child.nodeValue.substring(range.start.offset,
+                                                                  range.end.offset);
+                        DOM_appendChild(clone,DOM_createTextNode(document,substring));
+                    }
+                    else if ((child.nodeType == Node.TEXT_NODE) &&
+                             (child == range.start.node)) {
+                        var substring = child.nodeValue.substring(range.start.offset);
+                        DOM_appendChild(clone,DOM_createTextNode(document,substring));
+                    }
+                    else if ((child.nodeType == Node.TEXT_NODE) &&
+                             (child == range.end.node)) {
+                        var substring = child.nodeValue.substring(0,range.end.offset);
+                        DOM_appendChild(clone,DOM_createTextNode(document,substring));
+                    }
+                    else {
+                        DOM_appendChild(clone,DOM_cloneNode(child,true));
+                    }
+                }
+                else if (ancestorSet.contains(child)) {
+                    DOM_appendChild(clone,recurse(child));
+                }
+            }
+            return clone;
+        }
+    }
+
+    Range_hasContent = function(range)
+    {
+        var outermost = Range_getOutermostNodes(range);
+        for (var i = 0; i < outermost.length; i++) {
+            var node = outermost[i];
+            if (node.nodeType == Node.TEXT_NODE) {
+                var value = node.nodeValue;
+                if ((node == range.start.node) && (node == range.end.node)) {
+                    if (!isWhitespaceString(value.substring(range.start.offset,range.end.offset)))
+                        return true;
+                }
+                else if (node == range.start.node) {
+                    if (!isWhitespaceString(value.substring(range.start.offset)))
+                        return true;
+                }
+                else if (node == range.end.node) {
+                    if (!isWhitespaceString(value.substring(0,range.end.offset)))
+                        return true;
+                }
+                else {
+                    if (!isWhitespaceString(value))
+                        return true;
+                }
+            }
+            else if (node.nodeType == Node.ELEMENT_NODE) {
+                if (nodeHasContent(node))
+                    return true;
+            }
+        }
+        return false;
+    }
+
+    Range_getText = function(range)
+    {
+        range = Range_forwards(range);
+
+        var start = range.start;
+        var end = range.end;
+
+        var startNode = start.node;
+        var startOffset = start.offset;
+
+        if (start.node.nodeType == Node.ELEMENT_NODE) {
+            if ((start.node.offset == start.node.childNodes.length) &&
+                (start.node.offset > 0))
+                startNode = nextNodeAfter(start.node);
+            else
+                startNode = start.node.childNodes[start.offset];
+            startOffset = 0;
+        }
+
+        var endNode = end.node;
+        var endOffset = end.offset;
+
+        if (end.node.nodeType == Node.ELEMENT_NODE) {
+            if ((end.node.offset == end.node.childNodes.length) &&
+                (end.node.offset > 0))
+                endNode = nextNodeAfter(end.node);
+            else
+                endNode = end.node.childNodes[end.offset];
+            endOffset = 0;
+        }
+
+        if ((startNode == null) || (endNode == null))
+            return "";
+
+        var components = new Array();
+        var node = startNode;
+        var significantParagraph = true;
+        while (true) {
+            if (node == null)
+                throw new Error("Cannot find end node");
+
+            if (node.nodeType == Node.TEXT_NODE) {
+
+                if (!significantParagraph && !isWhitespaceString(node.nodeValue)) {
+                    significantParagraph = true;
+                    components.push("\n");
+                }
+
+                if (significantParagraph) {
+                    var str;
+                    if ((node == startNode) && (node == endNode))
+                        str = node.nodeValue.substring(startOffset,endOffset);
+                    else if (node == startNode)
+                        str = node.nodeValue.substring(startOffset);
+                    else if (node == endNode)
+                        str = node.nodeValue.substring(0,endOffset);
+                    else
+                        str = node.nodeValue;
+                    str = str.replace(/\s+/g," ");
+                    components.push(str);
+                }
+            }
+
+            if (node == endNode)
+                break;
+
+
+            var next = nextNode(node,entering,exiting);
+            node = next;
+        }
+        return components.join("");
+
+        function entering(n)
+        {
+            if (isParagraphNode(n)) {
+                significantParagraph = true;
+                components.push("\n");
+            }
+        }
+
+        function exiting(n)
+        {
+            if (isParagraphNode(n))
+                significantParagraph = false;
+        }
+    }
+
+})();

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/9bf02bb2/experiments/editorFramework/src/Javascript_Layer_0/Scan.js
----------------------------------------------------------------------
diff --git a/experiments/editorFramework/src/Javascript_Layer_0/Scan.js b/experiments/editorFramework/src/Javascript_Layer_0/Scan.js
new file mode 100644
index 0000000..e6cf4f5
--- /dev/null
+++ b/experiments/editorFramework/src/Javascript_Layer_0/Scan.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 Scan_reset;
+var Scan_next;
+var Scan_addMatch;
+var Scan_showMatch;
+var Scan_replaceMatch;
+var Scan_removeMatch;
+var Scan_goToMatch;
+
+(function() {
+
+    function Match(matchId,startPos,endPos)
+    {
+        this.matchId = matchId;
+        this.startPos = startPos;
+        this.endPos = endPos;
+        this.spans = new Array();
+    }
+
+    var matchesById = new Object();
+    var nextMatchId = 1;
+
+    var curPos = null;
+    var curParagraph = null;
+
+    Scan_reset = function()
+    {
+        curPos = new Position(document.body,0);
+        curParagraph = null;
+        clearMatches();
+    }
+
+    Scan_next = function() {
+        if (curPos == null)
+            return null;
+        curPos = Text_toEndOfBoundary(curPos,"paragraph");
+        if (curPos == null)
+            return null;
+
+        curParagraph = Text_analyseParagraph(curPos);
+        if (curParagraph == null)
+            return null;
+
+        curPos = Position_nextMatch(curPos,Position_okForMovement);
+
+        var sectionId = null;
+        if (isHeadingNode(curParagraph.node) &&
+            (curParagraph.startOffset == 0) &&
+            (curParagraph.endOffset == curParagraph.node.childNodes.length)) {
+            sectionId = DOM_getAttribute(curParagraph.node,"id");
+        }
+
+        return { text: curParagraph.text,
+                 sectionId: sectionId };
+    }
+
+    Scan_addMatch = function(start,end) {
+        if (curParagraph == null)
+            throw new Error("curParagraph is null");
+        if ((start < 0) || (start > curParagraph.text.length))
+            throw new Error("invalid start");
+        if ((end < start) || (end > curParagraph.text.length))
+            throw new Error("invalid end");
+
+        var matchId = nextMatchId++;
+
+        var startRun = Paragraph_runFromOffset(curParagraph,start);
+        var endRun = Paragraph_runFromOffset(curParagraph,end);
+
+        if (startRun == null)
+            throw new Error("No start run");
+        if (endRun == null)
+            throw new Error("No end run");
+
+        var startPos = new Position(startRun.node,start - startRun.start);
+        var endPos = new Position(endRun.node,end - endRun.start);
+        Position_track(startPos);
+        Position_track(endPos);
+
+        var match = new Match(matchId,startPos,endPos);
+        matchesById[matchId] = match;
+        return matchId;
+    }
+
+    Scan_showMatch = function(matchId)
+    {
+        var match = matchesById[matchId];
+        if (match == null)
+            throw new Error("Match "+matchId+" not found");
+
+        var range = new Range(match.startPos.node,match.startPos.offset,
+                              match.endPos.node,match.endPos.offset);
+        var text = Range_getText(range);
+        Formatting_splitAroundSelection(range,true);
+        var outermost = Range_getOutermostNodes(range);
+        for (var i = 0; i < outermost.length; i++) {
+            var span = DOM_wrapNode(outermost[i],"SPAN");
+            DOM_setAttribute(span,"class",Keys.MATCH_CLASS);
+            match.spans.push(span);
+        }
+    }
+
+    Scan_replaceMatch = function(matchId,replacement)
+    {
+        var match = matchesById[matchId];
+        if (match == null)
+            throw new Error("Match "+matchId+" not found");
+
+        if (match.spans.length == 0)
+            return;
+
+        var span = match.spans[0];
+
+        Selection_preserveWhileExecuting(function() {
+            var replacementNode = DOM_createTextNode(document,replacement);
+            DOM_insertBefore(span.parentNode,replacementNode,span);
+
+            for (var i = 0; i < match.spans.length; i++)
+                DOM_deleteNode(match.spans[i]);
+
+            Formatting_mergeUpwards(replacementNode,Formatting_MERGEABLE_INLINE);
+        });
+
+        delete matchesById[matchId];
+    }
+
+    function removeSpansForMatch(match)
+    {
+        for (var i = 0; i < match.spans.length; i++)
+            DOM_removeNodeButKeepChildren(match.spans[i]);
+    }
+
+    Scan_removeMatch = function(matchId)
+    {
+        removeSpansForMatch(matchesById[matchId]);
+        delete matchesById[matchId];
+    }
+
+    Scan_goToMatch = function(matchId)
+    {
+        var match = matchesById[matchId];
+        if (match == null)
+            throw new Error("Match "+matchId+" not found");
+
+        Selection_set(match.startPos.node,match.startPos.offset,
+                      match.endPos.node,match.endPos.offset);
+        Cursor_ensurePositionVisible(match.startPos,true);
+    }
+
+    function clearMatches()
+    {
+        for (var matchId in matchesById) {
+            var match = matchesById[matchId];
+            removeSpansForMatch(match);
+            Position_untrack(match.startPos);
+            Position_untrack(match.endPos);
+        }
+
+        matchesById = new Object();
+        nextMatchId = 1;
+    }
+
+})();