corinthia-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From pmke...@apache.org
Subject [45/92] [abbrv] [partial] incubator-corinthia git commit: Add editing code from UX Write
Date Wed, 17 Dec 2014 13:28:55 GMT
http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/03bd5af0/Editor/src/Preview.js
----------------------------------------------------------------------
diff --git a/Editor/src/Preview.js b/Editor/src/Preview.js
new file mode 100644
index 0000000..9267b3f
--- /dev/null
+++ b/Editor/src/Preview.js
@@ -0,0 +1,136 @@
+// Copyright 2011-2014 UX Productivity Pty Ltd
+//
+// Licensed 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/03bd5af0/Editor/src/Range.js
----------------------------------------------------------------------
diff --git a/Editor/src/Range.js b/Editor/src/Range.js
new file mode 100644
index 0000000..883bbdc
--- /dev/null
+++ b/Editor/src/Range.js
@@ -0,0 +1,563 @@
+// Copyright 2011-2014 UX Productivity Pty Ltd
+//
+// Licensed 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/03bd5af0/Editor/src/Scan.js
----------------------------------------------------------------------
diff --git a/Editor/src/Scan.js b/Editor/src/Scan.js
new file mode 100644
index 0000000..88938a6
--- /dev/null
+++ b/Editor/src/Scan.js
@@ -0,0 +1,176 @@
+// Copyright 2011-2014 UX Productivity Pty Ltd
+//
+// Licensed 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;
+    }
+
+})();

http://git-wip-us.apache.org/repos/asf/incubator-corinthia/blob/03bd5af0/Editor/src/Selection.js
----------------------------------------------------------------------
diff --git a/Editor/src/Selection.js b/Editor/src/Selection.js
new file mode 100644
index 0000000..42158f3
--- /dev/null
+++ b/Editor/src/Selection.js
@@ -0,0 +1,1420 @@
+// Copyright 2011-2014 UX Productivity Pty Ltd
+//
+// Licensed 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 (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/03bd5af0/Editor/src/StringBuilder.js
----------------------------------------------------------------------
diff --git a/Editor/src/StringBuilder.js b/Editor/src/StringBuilder.js
new file mode 100644
index 0000000..9c7460c
--- /dev/null
+++ b/Editor/src/StringBuilder.js
@@ -0,0 +1,18 @@
+// Copyright 2011-2014 UX Productivity Pty Ltd
+//
+// Licensed 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 = "";
+}


Mime
View raw message