Reviewed by Chris
authorkocienda <kocienda@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 30 Nov 2004 00:07:04 +0000 (00:07 +0000)
committerkocienda <kocienda@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 30 Nov 2004 00:07:04 +0000 (00:07 +0000)
        Rewrite of paste code (specifically the ReplaceSelectionCommand class). Many more cases
        are handled correctly now, including selections that span multiple blocks, and cases
        where content on the pasteboard ends in newlines (or what appear to be newlines to a
        user, really block ends or BRs). I also made one small, but important change in the
        copy code to annotate the markup written to the pasteboard to support these selections
        ending in newlines.

        New header that defines a couple of constants used in copying and pasting.

        * ForwardingHeaders/editing/html_interchange.h: Added.
        * khtml/editing/html_interchange.h: Added.

        Rewrite of the ReplaceSelectionCommand. There are several new helper functions, as well
        as a new helper class, ReplacementFragment, which encapsulates information and functions
        pertaining to a document fragment that is being inserted into a document.

        * khtml/editing/htmlediting.cpp:
        (khtml::ReplacementFragment::ReplacementFragment):
        (khtml::ReplacementFragment::~ReplacementFragment):
        (khtml::ReplacementFragment::firstChild): Simple accessor.
        (khtml::ReplacementFragment::lastChild): Ditto.
        (khtml::ReplacementFragment::mergeStartNode): Looks at the nodes in a fragment and determines
        the starting node to use for merging into the block containing the start of the selection.
        (khtml::ReplacementFragment::mergeEndNode): Same as above, but for the end of the selection.
        (khtml::ReplacementFragment::pruneEmptyNodes): Simple helper.
        (khtml::ReplacementFragment::isInterchangeNewlineComment): Determines if a node is the
        special annotation comment added in by the copy code.
        (khtml::ReplacementFragment::removeNode): Simple helper.
        (khtml::isComment): Simple helper.
        (khtml::isProbablyBlock): Determines if a node is of a type that is usually rendered as a block.
        I would like to do better than this some day, but this check will hold us until I can do better.
        (khtml::ReplaceSelectionCommand::ReplaceSelectionCommand):
        (khtml::ReplaceSelectionCommand::~ReplaceSelectionCommand):
        (khtml::ReplaceSelectionCommand::doApply):
        (khtml::ReplaceSelectionCommand::completeHTMLReplacement): Figures out the right ending selection.
        * khtml/editing/htmlediting.h: Declarations for the new ReplacementFragment class.
        (khtml::ReplacementFragment::root):
        (khtml::ReplacementFragment::type):
        (khtml::ReplacementFragment::isEmpty):
        (khtml::ReplacementFragment::isSingleTextNode):
        (khtml::ReplacementFragment::isTreeFragment):
        (khtml::ReplacementFragment::hasMoreThanOneBlock):
        (khtml::ReplacementFragment::hasLogicalNewlineAtEnd):

        This smaller set of changes markup generation to add the newline annotation described in the
        comment at the start of this entry.

        * khtml/xml/dom2_rangeimpl.cpp:
        (DOM::RangeImpl::addCommentToHTMLMarkup): Simple helper.
        (DOM::RangeImpl::toHTML): Added new EAnnotateForInterchange default argument to control whether
        comment annotations are added to the markup generated.
        * khtml/xml/dom2_rangeimpl.h: Add some new declarations.
        * kwq/WebCoreBridge.mm:
        (-[WebCoreBridge markupStringFromRange:nodes:]): Request that markup resulting from call to
        DOM::RangeImpl::toHTML uses annotations when generating.

        New tests.

        * layout-tests/editing/pasteboard/paste-text-001-expected.txt: Added.
        * layout-tests/editing/pasteboard/paste-text-001.html: Added.
        * layout-tests/editing/pasteboard/paste-text-002-expected.txt: Added.
        * layout-tests/editing/pasteboard/paste-text-002.html: Added.
        * layout-tests/editing/pasteboard/paste-text-003-expected.txt: Added.
        * layout-tests/editing/pasteboard/paste-text-003.html: Added.
        * layout-tests/editing/pasteboard/paste-text-004-expected.txt: Added.
        * layout-tests/editing/pasteboard/paste-text-004.html: Added.
        * layout-tests/editing/pasteboard/paste-text-005-expected.txt: Added.
        * layout-tests/editing/pasteboard/paste-text-005.html: Added.
        * layout-tests/editing/pasteboard/paste-text-006-expected.txt: Added.
        * layout-tests/editing/pasteboard/paste-text-006.html: Added.
        * layout-tests/editing/pasteboard/paste-text-007-expected.txt: Added.
        * layout-tests/editing/pasteboard/paste-text-007.html: Added.
        * layout-tests/editing/pasteboard/paste-text-008-expected.txt: Added.
        * layout-tests/editing/pasteboard/paste-text-008.html: Added.
        * layout-tests/editing/pasteboard/paste-text-009-expected.txt: Added.
        * layout-tests/editing/pasteboard/paste-text-009.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@8087 268f45cc-cd09-0410-ab3c-d52691b4dbfc

26 files changed:
LayoutTests/editing/pasteboard/paste-text-001-expected.txt [new file with mode: 0644]
LayoutTests/editing/pasteboard/paste-text-001.html [new file with mode: 0644]
LayoutTests/editing/pasteboard/paste-text-002-expected.txt [new file with mode: 0644]
LayoutTests/editing/pasteboard/paste-text-002.html [new file with mode: 0644]
LayoutTests/editing/pasteboard/paste-text-003-expected.txt [new file with mode: 0644]
LayoutTests/editing/pasteboard/paste-text-003.html [new file with mode: 0644]
LayoutTests/editing/pasteboard/paste-text-004-expected.txt [new file with mode: 0644]
LayoutTests/editing/pasteboard/paste-text-004.html [new file with mode: 0644]
LayoutTests/editing/pasteboard/paste-text-005-expected.txt [new file with mode: 0644]
LayoutTests/editing/pasteboard/paste-text-005.html [new file with mode: 0644]
LayoutTests/editing/pasteboard/paste-text-006-expected.txt [new file with mode: 0644]
LayoutTests/editing/pasteboard/paste-text-006.html [new file with mode: 0644]
LayoutTests/editing/pasteboard/paste-text-007-expected.txt [new file with mode: 0644]
LayoutTests/editing/pasteboard/paste-text-007.html [new file with mode: 0644]
LayoutTests/editing/pasteboard/paste-text-008-expected.txt [new file with mode: 0644]
LayoutTests/editing/pasteboard/paste-text-008.html [new file with mode: 0644]
LayoutTests/editing/pasteboard/paste-text-009-expected.txt [new file with mode: 0644]
LayoutTests/editing/pasteboard/paste-text-009.html [new file with mode: 0644]
WebCore/ChangeLog-2005-08-23
WebCore/ForwardingHeaders/editing/html_interchange.h [new file with mode: 0644]
WebCore/khtml/editing/html_interchange.h [new file with mode: 0644]
WebCore/khtml/editing/htmlediting.cpp
WebCore/khtml/editing/htmlediting.h
WebCore/khtml/xml/dom2_rangeimpl.cpp
WebCore/khtml/xml/dom2_rangeimpl.h
WebCore/kwq/WebCoreBridge.mm

diff --git a/LayoutTests/editing/pasteboard/paste-text-001-expected.txt b/LayoutTests/editing/pasteboard/paste-text-001-expected.txt
new file mode 100644 (file)
index 0000000..e5f81ad
--- /dev/null
@@ -0,0 +1,19 @@
+layer at (0,0) size 800x600
+  RenderCanvas at (0,0) size 800x600
+layer at (0,0) size 800x600
+  RenderBlock {HTML} at (0,0) size 800x600
+    RenderBody {BODY} at (8,8) size 784x584
+      RenderBlock {DIV} at (0,0) size 784x56 [border: (2px solid #FF0000)]
+        RenderInline {SPAN} at (0,0) size 140x28
+          RenderText {TEXT} at (14,14) size 69x28
+            text run at (14,14) width 69: "foo bar"
+          RenderInline {SPAN} at (0,0) size 31x28
+            RenderText {TEXT} at (83,14) size 31x28
+              text run at (83,14) width 31: "bar"
+          RenderText {TEXT} at (114,14) size 40x28
+            text run at (114,14) width 40: " baz"
+        RenderText {TEXT} at (0,0) size 0x0
+selection is CARET:
+start:      position 3 of child 1 {TEXT} of child 2 {SPAN} of child 2 {SPAN} of root {DIV}
+upstream:   position 3 of child 1 {TEXT} of child 2 {SPAN} of child 2 {SPAN} of root {DIV}
+downstream: position 0 of child 3 {TEXT} of child 2 {SPAN} of root {DIV}
diff --git a/LayoutTests/editing/pasteboard/paste-text-001.html b/LayoutTests/editing/pasteboard/paste-text-001.html
new file mode 100644 (file)
index 0000000..e1b6747
--- /dev/null
@@ -0,0 +1,39 @@
+<html> 
+<head>
+
+<style>
+.editing { 
+    border: 2px solid red; 
+    padding: 12px; 
+    font-size: 24px; 
+}
+</style>
+<script src=../editing.js language="JavaScript" type="text/JavaScript" ></script>
+
+<script>
+
+function editingTest() {
+    for (i = 0; i < 4; i++)
+        moveSelectionForwardByCharacterCommand();
+    for (i = 0; i < 3; i++)
+        extendSelectionForwardByCharacterCommand();
+    copyCommand();
+    moveSelectionForwardByCharacterCommand();
+    pasteCommand();
+}
+
+</script>
+
+<title>Editing Test</title> 
+</head> 
+<body>
+<div contenteditable id="root" class="editing">
+<span id="test">foo bar baz</span>
+</div>
+
+<script>
+runEditingTest();
+</script>
+
+</body>
+</html>
diff --git a/LayoutTests/editing/pasteboard/paste-text-002-expected.txt b/LayoutTests/editing/pasteboard/paste-text-002-expected.txt
new file mode 100644 (file)
index 0000000..861fbaa
--- /dev/null
@@ -0,0 +1,30 @@
+layer at (0,0) size 800x600
+  RenderCanvas at (0,0) size 800x600
+layer at (0,0) size 800x600
+  RenderBlock {HTML} at (0,0) size 800x600
+    RenderBody {BODY} at (8,8) size 784x584
+      RenderBlock {DIV} at (0,0) size 784x56 [border: (2px solid #FF0000)]
+        RenderText {TEXT} at (14,14) size 63x28
+          text run at (14,14) width 63: "There "
+        RenderText {TEXT} at (77,14) size 285x28
+          text run at (77,14) width 285: "is a tide in the affairs of men,"
+      RenderBlock {DIV} at (0,56) size 784x56 [border: (2px solid #FF0000)]
+        RenderText {TEXT} at (14,14) size 124x28
+          text run at (14,14) width 124: "Which taken"
+        RenderText {TEXT} at (138,14) size 285x28
+          text run at (138,14) width 285: "is a tide in the affairs of men,"
+      RenderBlock {DIV} at (0,112) size 784x56 [border: (2px solid #FF0000)]
+        RenderText {TEXT} at (14,14) size 124x28
+          text run at (14,14) width 124: "Which taken"
+        RenderText {TEXT} at (138,14) size 310x28
+          text run at (138,14) width 310: " at the flood leads on to fortune."
+      RenderBlock {DIV} at (0,168) size 784x56 [border: (2px solid #FF0000)]
+        RenderText {TEXT} at (14,14) size 351x28
+          text run at (14,14) width 351: "Omitted, all the voyage of their life,"
+      RenderBlock {DIV} at (0,224) size 784x56 [border: (2px solid #FF0000)]
+        RenderText {TEXT} at (14,14) size 357x28
+          text run at (14,14) width 357: "Is bound in shallows and in miseries."
+selection is CARET:
+start:      position 11 of child 1 {TEXT} of child 3 {DIV} of root {BODY}
+upstream:   position 11 of child 1 {TEXT} of child 3 {DIV} of root {BODY}
+downstream: position 0 of child 2 {TEXT} of child 3 {DIV} of root {BODY}
diff --git a/LayoutTests/editing/pasteboard/paste-text-002.html b/LayoutTests/editing/pasteboard/paste-text-002.html
new file mode 100644 (file)
index 0000000..5161da2
--- /dev/null
@@ -0,0 +1,40 @@
+<html> 
+<head>
+
+<style>
+.editing { 
+    border: 2px solid red; 
+    padding: 12px; 
+    font-size: 24px; 
+}
+</style>
+<script src=../editing.js language="JavaScript" type="text/JavaScript" ></script>
+
+<script>
+
+function editingTest() {
+    for (i = 0; i < 6; i++)
+        moveSelectionForwardByCharacterCommand();
+    for (i = 0; i < 44; i++)
+        extendSelectionForwardByCharacterCommand();
+    copyCommand();
+    pasteCommand();
+    pasteCommand();
+}
+
+</script>
+
+<title>Editing Test</title> 
+</head> 
+<body contenteditable id="root">
+<div id="test" class="editing">There is a tide in the affairs of men,</div>
+<div class="editing">Which taken at the flood leads on to fortune.</div>
+<div class="editing">Omitted, all the voyage of their life,</div>
+<div class="editing">Is bound in shallows and in miseries.</div>
+<script>
+runEditingTest();
+</script>
+
+</body>
+</html>
diff --git a/LayoutTests/editing/pasteboard/paste-text-003-expected.txt b/LayoutTests/editing/pasteboard/paste-text-003-expected.txt
new file mode 100644 (file)
index 0000000..0fd311a
--- /dev/null
@@ -0,0 +1,38 @@
+layer at (0,0) size 800x600
+  RenderCanvas at (0,0) size 800x600
+layer at (0,0) size 800x600
+  RenderBlock {HTML} at (0,0) size 800x600
+    RenderBody {BODY} at (8,8) size 784x584
+      RenderBlock {DIV} at (0,0) size 784x364 [border: (2px solid #FF0000)]
+        RenderBlock (anonymous) at (14,14) size 756x28
+          RenderText {TEXT} at (0,0) size 63x28
+            text run at (0,0) width 63: "There "
+          RenderText {TEXT} at (63,0) size 285x28
+            text run at (63,0) width 285: "is a tide in the affairs of men,"
+        RenderBlock {DIV} at (14,42) size 756x56 [border: (2px solid #FF0000)]
+          RenderText {TEXT} at (14,14) size 434x28
+            text run at (14,14) width 434: "Which taken at the flood leads on to fortune."
+        RenderBlock {DIV} at (14,98) size 756x252 [border: (2px solid #FF0000)]
+          RenderBlock (anonymous) at (14,14) size 728x0
+          RenderBlock {DIV} at (14,14) size 728x112 [border: (2px solid #FF0000)]
+            RenderBlock (anonymous) at (14,14) size 700x28
+              RenderText {TEXT} at (0,0) size 80x28
+                text run at (0,0) width 80: "Omitted"
+              RenderText {TEXT} at (80,0) size 285x28
+                text run at (80,0) width 285: "is a tide in the affairs of men,"
+            RenderBlock {DIV} at (14,42) size 700x56 [border: (2px solid #FF0000)]
+              RenderText {TEXT} at (14,14) size 434x28
+                text run at (14,14) width 434: "Which taken at the flood leads on to fortune."
+          RenderBlock {DIV} at (14,126) size 728x112 [border: (2px solid #FF0000)]
+            RenderBlock (anonymous) at (14,14) size 700x28
+              RenderText {TEXT} at (0,0) size 80x28
+                text run at (0,0) width 80: "Omitted"
+              RenderText {TEXT} at (80,0) size 271x28
+                text run at (80,0) width 271: ", all the voyage of their life,"
+            RenderBlock {DIV} at (14,42) size 700x56 [border: (2px solid #FF0000)]
+              RenderText {TEXT} at (14,14) size 357x28
+                text run at (14,14) width 357: "Is bound in shallows and in miseries."
+selection is CARET:
+start:      position 7 of child 1 {TEXT} of child 2 {DIV} of child 4 {DIV} of child 1 {DIV} of root {BODY}
+upstream:   position 7 of child 1 {TEXT} of child 2 {DIV} of child 4 {DIV} of child 1 {DIV} of root {BODY}
+downstream: position 0 of child 2 {TEXT} of child 2 {DIV} of child 4 {DIV} of child 1 {DIV} of root {BODY}
diff --git a/LayoutTests/editing/pasteboard/paste-text-003.html b/LayoutTests/editing/pasteboard/paste-text-003.html
new file mode 100644 (file)
index 0000000..1744320
--- /dev/null
@@ -0,0 +1,44 @@
+<html> 
+<head>
+
+<style>
+.editing { 
+    border: 2px solid red; 
+    padding: 12px; 
+    font-size: 24px; 
+}
+</style>
+<script src=../editing.js language="JavaScript" type="text/JavaScript" ></script>
+
+<script>
+
+function editingTest() {
+    for (i = 0; i < 6; i++)
+        moveSelectionForwardByCharacterCommand();
+    for (i = 0; i < 86; i++)
+        extendSelectionForwardByCharacterCommand();
+    copyCommand();
+    pasteCommand();
+    pasteCommand();
+}
+
+</script>
+
+<title>Editing Test</title> 
+</head> 
+<body contenteditable id="root">
+<div id="test" class="editing">There is a tide in the affairs of men,
+<div class="editing">Which taken at the flood leads on to fortune.
+<div class="editing">Omitted, all the voyage of their life,
+<div class="editing">Is bound in shallows and in miseries.
+</div>
+</div>
+</div>
+</div>
+<script>
+runEditingTest();
+</script>
+
+</body>
+</html>
diff --git a/LayoutTests/editing/pasteboard/paste-text-004-expected.txt b/LayoutTests/editing/pasteboard/paste-text-004-expected.txt
new file mode 100644 (file)
index 0000000..4861d49
--- /dev/null
@@ -0,0 +1,23 @@
+layer at (0,0) size 800x600
+  RenderCanvas at (0,0) size 800x600
+layer at (0,0) size 800x600
+  RenderBlock {HTML} at (0,0) size 800x600
+    RenderBody {BODY} at (8,8) size 784x584
+      RenderBlock {DIV} at (0,0) size 784x56 [border: (2px solid #FF0000)]
+        RenderText {TEXT} at (14,14) size 63x28
+          text run at (14,14) width 63: "There "
+        RenderText {TEXT} at (77,14) size 285x28
+          text run at (77,14) width 285: "is a tide in the affairs of men,"
+      RenderBlock {DIV} at (0,56) size 784x56 [border: (2px solid #FF0000)]
+        RenderText {TEXT} at (14,14) size 434x28
+          text run at (14,14) width 434: "Which taken at the flood leads on to fortune."
+      RenderBlock {DIV} at (0,112) size 784x56 [border: (2px solid #FF0000)]
+        RenderText {TEXT} at (14,14) size 351x28
+          text run at (14,14) width 351: "Omitted, all the voyage of their life,"
+      RenderBlock {DIV} at (0,168) size 784x56 [border: (2px solid #FF0000)]
+        RenderText {TEXT} at (14,14) size 357x28
+          text run at (14,14) width 357: "Is bound in shallows and in miseries."
+selection is CARET:
+start:      position 0 of child 1 {TEXT} of child 2 {DIV} of root {BODY}
+upstream:   position 0 of child 2 {DIV} of root {BODY}
+downstream: position 0 of child 1 {TEXT} of child 2 {DIV} of root {BODY}
diff --git a/LayoutTests/editing/pasteboard/paste-text-004.html b/LayoutTests/editing/pasteboard/paste-text-004.html
new file mode 100644 (file)
index 0000000..7f5b02c
--- /dev/null
@@ -0,0 +1,39 @@
+<html> 
+<head>
+
+<style>
+.editing { 
+    border: 2px solid red; 
+    padding: 12px; 
+    font-size: 24px; 
+}
+</style>
+<script src=../editing.js language="JavaScript" type="text/JavaScript" ></script>
+
+<script>
+
+function editingTest() {
+    for (i = 0; i < 6; i++)
+        moveSelectionForwardByCharacterCommand();
+    for (i = 0; i < 33; i++)
+        extendSelectionForwardByCharacterCommand();
+    copyCommand();
+    pasteCommand();
+}
+
+</script>
+
+<title>Editing Test</title> 
+</head> 
+<body contenteditable id="root">
+<div id="test" class="editing">There is a tide in the affairs of men,</div>
+<div class="editing">Which taken at the flood leads on to fortune.</div>
+<div class="editing">Omitted, all the voyage of their life,</div>
+<div class="editing">Is bound in shallows and in miseries.</div>
+<script>
+runEditingTest();
+</script>
+
+</body>
+</html>
diff --git a/LayoutTests/editing/pasteboard/paste-text-005-expected.txt b/LayoutTests/editing/pasteboard/paste-text-005-expected.txt
new file mode 100644 (file)
index 0000000..cdb1c5b
--- /dev/null
@@ -0,0 +1,26 @@
+layer at (0,0) size 800x600
+  RenderCanvas at (0,0) size 800x600
+layer at (0,0) size 800x600
+  RenderBlock {HTML} at (0,0) size 800x600
+    RenderBody {BODY} at (8,8) size 784x584
+      RenderBlock {DIV} at (0,0) size 784x56 [border: (2px solid #FF0000)]
+        RenderText {TEXT} at (14,14) size 63x28
+          text run at (14,14) width 63: "There "
+        RenderText {TEXT} at (77,14) size 285x28
+          text run at (77,14) width 285: "is a tide in the affairs of men,"
+      RenderBlock {DIV} at (0,56) size 784x56 [border: (2px solid #FF0000)]
+        RenderText {TEXT} at (14,14) size 285x28
+          text run at (14,14) width 285: "is a tide in the affairs of men,"
+      RenderBlock {DIV} at (0,112) size 784x56 [border: (2px solid #FF0000)]
+        RenderText {TEXT} at (14,14) size 434x28
+          text run at (14,14) width 434: "Which taken at the flood leads on to fortune."
+      RenderBlock {DIV} at (0,168) size 784x56 [border: (2px solid #FF0000)]
+        RenderText {TEXT} at (14,14) size 351x28
+          text run at (14,14) width 351: "Omitted, all the voyage of their life,"
+      RenderBlock {DIV} at (0,224) size 784x56 [border: (2px solid #FF0000)]
+        RenderText {TEXT} at (14,14) size 357x28
+          text run at (14,14) width 357: "Is bound in shallows and in miseries."
+selection is CARET:
+start:      position 0 of child 1 {TEXT} of child 3 {DIV} of root {BODY}
+upstream:   position 0 of child 3 {DIV} of root {BODY}
+downstream: position 0 of child 1 {TEXT} of child 3 {DIV} of root {BODY}
diff --git a/LayoutTests/editing/pasteboard/paste-text-005.html b/LayoutTests/editing/pasteboard/paste-text-005.html
new file mode 100644 (file)
index 0000000..2d44388
--- /dev/null
@@ -0,0 +1,40 @@
+<html> 
+<head>
+
+<style>
+.editing { 
+    border: 2px solid red; 
+    padding: 12px; 
+    font-size: 24px; 
+}
+</style>
+<script src=../editing.js language="JavaScript" type="text/JavaScript" ></script>
+
+<script>
+
+function editingTest() {
+    for (i = 0; i < 6; i++)
+        moveSelectionForwardByCharacterCommand();
+    for (i = 0; i < 33; i++)
+        extendSelectionForwardByCharacterCommand();
+    copyCommand();
+    pasteCommand();
+    pasteCommand();
+}
+
+</script>
+
+<title>Editing Test</title> 
+</head> 
+<body contenteditable id="root">
+<div id="test" class="editing">There is a tide in the affairs of men,</div>
+<div class="editing">Which taken at the flood leads on to fortune.</div>
+<div class="editing">Omitted, all the voyage of their life,</div>
+<div class="editing">Is bound in shallows and in miseries.</div>
+<script>
+runEditingTest();
+</script>
+
+</body>
+</html>
diff --git a/LayoutTests/editing/pasteboard/paste-text-006-expected.txt b/LayoutTests/editing/pasteboard/paste-text-006-expected.txt
new file mode 100644 (file)
index 0000000..dd2511a
--- /dev/null
@@ -0,0 +1,24 @@
+layer at (0,0) size 800x600
+  RenderCanvas at (0,0) size 800x600
+layer at (0,0) size 800x600
+  RenderBlock {HTML} at (0,0) size 800x600
+    RenderBody {BODY} at (8,8) size 784x584
+      RenderBlock {DIV} at (0,0) size 784x56 [border: (2px solid #FF0000)]
+        RenderText {TEXT} at (14,14) size 348x28
+          text run at (14,14) width 348: "There is a tide in the affairs of men,"
+      RenderBlock {DIV} at (0,56) size 784x56 [border: (2px solid #FF0000)]
+        RenderText {TEXT} at (14,14) size 348x28
+          text run at (14,14) width 348: "There is a tide in the affairs of men,"
+      RenderBlock {DIV} at (0,112) size 784x56 [border: (2px solid #FF0000)]
+        RenderText {TEXT} at (14,14) size 434x28
+          text run at (14,14) width 434: "Which taken at the flood leads on to fortune."
+      RenderBlock {DIV} at (0,168) size 784x56 [border: (2px solid #FF0000)]
+        RenderText {TEXT} at (14,14) size 351x28
+          text run at (14,14) width 351: "Omitted, all the voyage of their life,"
+      RenderBlock {DIV} at (0,224) size 784x56 [border: (2px solid #FF0000)]
+        RenderText {TEXT} at (14,14) size 357x28
+          text run at (14,14) width 357: "Is bound in shallows and in miseries."
+selection is CARET:
+start:      position 0 of child 1 {TEXT} of child 3 {DIV} of root {BODY}
+upstream:   position 0 of child 3 {DIV} of root {BODY}
+downstream: position 0 of child 1 {TEXT} of child 3 {DIV} of root {BODY}
diff --git a/LayoutTests/editing/pasteboard/paste-text-006.html b/LayoutTests/editing/pasteboard/paste-text-006.html
new file mode 100644 (file)
index 0000000..175b6ef
--- /dev/null
@@ -0,0 +1,38 @@
+<html> 
+<head>
+
+<style>
+.editing { 
+    border: 2px solid red; 
+    padding: 12px; 
+    font-size: 24px; 
+}
+</style>
+<script src=../editing.js language="JavaScript" type="text/JavaScript" ></script>
+
+<script>
+
+function editingTest() {
+    for (i = 0; i < 39; i++)
+        extendSelectionForwardByCharacterCommand();
+    copyCommand();
+    pasteCommand();
+    pasteCommand();
+}
+
+</script>
+
+<title>Editing Test</title> 
+</head> 
+<body contenteditable id="root">
+<div id="test" class="editing">There is a tide in the affairs of men,</div>
+<div class="editing">Which taken at the flood leads on to fortune.</div>
+<div class="editing">Omitted, all the voyage of their life,</div>
+<div class="editing">Is bound in shallows and in miseries.</div>
+<script>
+runEditingTest();
+</script>
+
+</body>
+</html>
diff --git a/LayoutTests/editing/pasteboard/paste-text-007-expected.txt b/LayoutTests/editing/pasteboard/paste-text-007-expected.txt
new file mode 100644 (file)
index 0000000..9f275af
--- /dev/null
@@ -0,0 +1,27 @@
+layer at (0,0) size 800x600
+  RenderCanvas at (0,0) size 800x600
+layer at (0,0) size 800x600
+  RenderBlock {HTML} at (0,0) size 800x600
+    RenderBody {BODY} at (8,8) size 784x584
+      RenderBlock {DIV} at (0,0) size 784x280 [border: (2px solid #FF0000)]
+        RenderBlock (anonymous) at (14,14) size 756x28
+          RenderText {TEXT} at (0,0) size 348x28
+            text run at (0,0) width 348: "There is a tide in the affairs of men,"
+        RenderBlock {DIV} at (14,42) size 756x56 [border: (2px solid #FF0000)]
+          RenderText {TEXT} at (14,14) size 348x28
+            text run at (14,14) width 348: "There is a tide in the affairs of men,"
+        RenderBlock {DIV} at (14,98) size 756x168 [border: (2px solid #FF0000)]
+          RenderBlock (anonymous) at (14,14) size 728x28
+            RenderText {TEXT} at (0,0) size 434x28
+              text run at (0,0) width 434: "Which taken at the flood leads on to fortune."
+          RenderBlock {DIV} at (14,42) size 728x112 [border: (2px solid #FF0000)]
+            RenderBlock (anonymous) at (14,14) size 700x28
+              RenderText {TEXT} at (0,0) size 351x28
+                text run at (0,0) width 351: "Omitted, all the voyage of their life,"
+            RenderBlock {DIV} at (14,42) size 700x56 [border: (2px solid #FF0000)]
+              RenderText {TEXT} at (14,14) size 357x28
+                text run at (14,14) width 357: "Is bound in shallows and in miseries."
+selection is CARET:
+start:      position 0 of child 1 {TEXT} of child 3 {DIV} of child 2 {DIV} of root {BODY}
+upstream:   position 0 of child 3 {DIV} of child 2 {DIV} of root {BODY}
+downstream: position 0 of child 1 {TEXT} of child 3 {DIV} of child 2 {DIV} of root {BODY}
diff --git a/LayoutTests/editing/pasteboard/paste-text-007.html b/LayoutTests/editing/pasteboard/paste-text-007.html
new file mode 100644 (file)
index 0000000..788d78b
--- /dev/null
@@ -0,0 +1,36 @@
+<html> 
+<head>
+
+<style>
+.editing { 
+    border: 2px solid red; 
+    padding: 12px; 
+    font-size: 24px; 
+}
+</style>
+<script src=../editing.js language="JavaScript" type="text/JavaScript" ></script>
+
+<script>
+
+function editingTest() {
+    for (i = 0; i < 39; i++)
+        extendSelectionForwardByCharacterCommand();
+    copyCommand();
+    pasteCommand();
+    pasteCommand();
+}
+
+</script>
+
+<title>Editing Test</title> 
+</head> 
+<body contenteditable id="root">
+
+<div id="test" class="editing">There is a tide in the affairs of men,<div class="editing">Which taken at the flood leads on to fortune.<div class="editing">Omitted, all the voyage of their life,<div class="editing">Is bound in shallows and in miseries.</div></div></div></div>
+<script>
+runEditingTest();
+</script>
+
+</body>
+</html>
diff --git a/LayoutTests/editing/pasteboard/paste-text-008-expected.txt b/LayoutTests/editing/pasteboard/paste-text-008-expected.txt
new file mode 100644 (file)
index 0000000..e423a5a
--- /dev/null
@@ -0,0 +1,28 @@
+layer at (0,0) size 800x600
+  RenderCanvas at (0,0) size 800x600
+layer at (0,0) size 800x600
+  RenderBlock {HTML} at (0,0) size 800x600
+    RenderBody {BODY} at (8,8) size 784x584
+      RenderBlock {DIV} at (0,0) size 784x140 [border: (2px solid #FF0000)]
+        RenderBlock (anonymous) at (14,14) size 756x28
+          RenderText {TEXT} at (0,0) size 351x28
+            text run at (0,0) width 351: "Omitted, all the voyage of their life,"
+        RenderBlock {DIV} at (14,42) size 756x56 [border: (2px solid #FF0000)]
+          RenderText {TEXT} at (14,14) size 114x28
+            text run at (14,14) width 114: "Is bound in "
+          RenderText {TEXT} at (128,14) size 243x28
+            text run at (128,14) width 243: "shallows and in miseries."
+        RenderBlock (anonymous) at (14,98) size 756x28
+          RenderText {TEXT} at (0,0) size 197x28
+            text run at (0,0) width 197: "Upon such a full sea"
+          RenderText {TEXT} at (197,0) size 243x28
+            text run at (197,0) width 243: "shallows and in miseries."
+      RenderBlock {DIV} at (0,140) size 784x56 [border: (2px solid #FF0000)]
+        RenderText {TEXT} at (14,14) size 197x28
+          text run at (14,14) width 197: "Upon such a full sea"
+        RenderText {TEXT} at (211,14) size 185x28
+          text run at (211,14) width 185: " are we now afloat,"
+selection is CARET:
+start:      position 20 of child 1 {TEXT} of child 3 {DIV} of root {BODY}
+upstream:   position 20 of child 1 {TEXT} of child 3 {DIV} of root {BODY}
+downstream: position 0 of child 2 {TEXT} of child 3 {DIV} of root {BODY}
diff --git a/LayoutTests/editing/pasteboard/paste-text-008.html b/LayoutTests/editing/pasteboard/paste-text-008.html
new file mode 100644 (file)
index 0000000..226f830
--- /dev/null
@@ -0,0 +1,38 @@
+<html> 
+<head>
+
+<style>
+.editing { 
+    border: 2px solid red; 
+    padding: 12px; 
+    font-size: 24px; 
+}
+</style>
+<script src=../editing.js language="JavaScript" type="text/JavaScript" ></script>
+
+<script>
+
+function editingTest() {
+    for (i = 0; i < 51; i++)
+        moveSelectionForwardByCharacterCommand();
+    for (i = 0; i < 46; i++)
+        extendSelectionForwardByCharacterCommand();
+    copyCommand();
+    pasteCommand();
+    pasteCommand();
+}
+
+</script>
+
+<title>Editing Test</title> 
+</head> 
+<body contenteditable id="root">
+
+<div class="editing" id="test">Omitted, all the voyage of their life,<div class="editing">Is bound in shallows and in miseries.</div>Upon such a full sea are we now afloat,</div>
+<script>
+runEditingTest();
+</script>
+
+</body>
+</html>
diff --git a/LayoutTests/editing/pasteboard/paste-text-009-expected.txt b/LayoutTests/editing/pasteboard/paste-text-009-expected.txt
new file mode 100644 (file)
index 0000000..5fd9eb7
--- /dev/null
@@ -0,0 +1,24 @@
+layer at (0,0) size 800x600
+  RenderCanvas at (0,0) size 800x600
+layer at (0,0) size 800x600
+  RenderBlock {HTML} at (0,0) size 800x600
+    RenderBody {BODY} at (8,8) size 784x584
+      RenderBlock {DIV} at (0,0) size 784x140 [border: (2px solid #FF0000)]
+        RenderBlock (anonymous) at (14,14) size 756x28
+          RenderText {TEXT} at (0,0) size 351x28
+            text run at (0,0) width 351: "Omitted, all the voyage of their life,"
+        RenderBlock {DIV} at (14,42) size 756x56 [border: (2px solid #FF0000)]
+          RenderText {TEXT} at (14,14) size 357x28
+            text run at (14,14) width 357: "Is bound in shallows and in miseries."
+        RenderBlock (anonymous) at (14,98) size 756x28
+          RenderText {TEXT} at (0,0) size 17x28
+            text run at (0,0) width 17: "U"
+      RenderBlock {DIV} at (0,140) size 784x56 [border: (2px solid #FF0000)]
+        RenderText {TEXT} at (14,14) size 17x28
+          text run at (14,14) width 17: "U"
+        RenderText {TEXT} at (31,14) size 365x28
+          text run at (31,14) width 365: "pon such a full sea are we now afloat,"
+selection is CARET:
+start:      position 1 of child 1 {TEXT} of child 3 {DIV} of root {BODY}
+upstream:   position 1 of child 1 {TEXT} of child 3 {DIV} of root {BODY}
+downstream: position 0 of child 2 {TEXT} of child 3 {DIV} of root {BODY}
diff --git a/LayoutTests/editing/pasteboard/paste-text-009.html b/LayoutTests/editing/pasteboard/paste-text-009.html
new file mode 100644 (file)
index 0000000..39787d1
--- /dev/null
@@ -0,0 +1,38 @@
+<html> 
+<head>
+
+<style>
+.editing { 
+    border: 2px solid red; 
+    padding: 12px; 
+    font-size: 24px; 
+}
+</style>
+<script src=../editing.js language="JavaScript" type="text/JavaScript" ></script>
+
+<script>
+
+function editingTest() {
+    for (i = 0; i < 76; i++)
+        moveSelectionForwardByCharacterCommand();
+    for (i = 0; i < 2; i++)
+        extendSelectionForwardByCharacterCommand();
+    copyCommand();
+    pasteCommand();
+    pasteCommand();
+}
+
+</script>
+
+<title>Editing Test</title> 
+</head> 
+<body contenteditable id="root">
+
+<div class="editing" id="test">Omitted, all the voyage of their life,<div class="editing">Is bound in shallows and in miseries.</div>Upon such a full sea are we now afloat,</div>
+<script>
+runEditingTest();
+</script>
+
+</body>
+</html>
index d37f3e0..a1d1ac7 100644 (file)
@@ -1,5 +1,86 @@
 2004-11-29  Ken Kocienda  <kocienda@apple.com>
 
+        Reviewed by Chris
+    
+        Rewrite of paste code (specifically the ReplaceSelectionCommand class). Many more cases
+        are handled correctly now, including selections that span multiple blocks, and cases
+        where content on the pasteboard ends in newlines (or what appear to be newlines to a
+        user, really block ends or BRs). I also made one small, but important change in the 
+        copy code to annotate the markup written to the pasteboard to support these selections 
+        ending in newlines.
+
+        New header that defines a couple of constants used in copying and pasting.
+
+        * ForwardingHeaders/editing/html_interchange.h: Added.
+        * khtml/editing/html_interchange.h: Added.
+        
+        Rewrite of the ReplaceSelectionCommand. There are several new helper functions, as well
+        as a new helper class, ReplacementFragment, which encapsulates information and functions
+        pertaining to a document fragment that is being inserted into a document.
+        
+        * khtml/editing/htmlediting.cpp:
+        (khtml::ReplacementFragment::ReplacementFragment):
+        (khtml::ReplacementFragment::~ReplacementFragment):
+        (khtml::ReplacementFragment::firstChild): Simple accessor.
+        (khtml::ReplacementFragment::lastChild): Ditto.
+        (khtml::ReplacementFragment::mergeStartNode): Looks at the nodes in a fragment and determines
+        the starting node to use for merging into the block containing the start of the selection.
+        (khtml::ReplacementFragment::mergeEndNode): Same as above, but for the end of the selection.
+        (khtml::ReplacementFragment::pruneEmptyNodes): Simple helper.
+        (khtml::ReplacementFragment::isInterchangeNewlineComment): Determines if a node is the
+        special annotation comment added in by the copy code.
+        (khtml::ReplacementFragment::removeNode): Simple helper.
+        (khtml::isComment): Simple helper.
+        (khtml::isProbablyBlock): Determines if a node is of a type that is usually rendered as a block.
+        I would like to do better than this some day, but this check will hold us until I can do better.
+        (khtml::ReplaceSelectionCommand::ReplaceSelectionCommand):
+        (khtml::ReplaceSelectionCommand::~ReplaceSelectionCommand):
+        (khtml::ReplaceSelectionCommand::doApply):
+        (khtml::ReplaceSelectionCommand::completeHTMLReplacement): Figures out the right ending selection.
+        * khtml/editing/htmlediting.h: Declarations for the new ReplacementFragment class.
+        (khtml::ReplacementFragment::root):
+        (khtml::ReplacementFragment::type):
+        (khtml::ReplacementFragment::isEmpty):
+        (khtml::ReplacementFragment::isSingleTextNode):
+        (khtml::ReplacementFragment::isTreeFragment):
+        (khtml::ReplacementFragment::hasMoreThanOneBlock):
+        (khtml::ReplacementFragment::hasLogicalNewlineAtEnd):
+        
+        This smaller set of changes markup generation to add the newline annotation described in the
+        comment at the start of this entry.
+        
+        * khtml/xml/dom2_rangeimpl.cpp:
+        (DOM::RangeImpl::addCommentToHTMLMarkup): Simple helper.
+        (DOM::RangeImpl::toHTML): Added new EAnnotateForInterchange default argument to control whether
+        comment annotations are added to the markup generated.
+        * khtml/xml/dom2_rangeimpl.h: Add some new declarations.
+        * kwq/WebCoreBridge.mm:
+        (-[WebCoreBridge markupStringFromRange:nodes:]): Request that markup resulting from call to 
+        DOM::RangeImpl::toHTML uses annotations when generating.
+        
+        New tests.        
+
+        * layout-tests/editing/pasteboard/paste-text-001-expected.txt: Added.
+        * layout-tests/editing/pasteboard/paste-text-001.html: Added.
+        * layout-tests/editing/pasteboard/paste-text-002-expected.txt: Added.
+        * layout-tests/editing/pasteboard/paste-text-002.html: Added.
+        * layout-tests/editing/pasteboard/paste-text-003-expected.txt: Added.
+        * layout-tests/editing/pasteboard/paste-text-003.html: Added.
+        * layout-tests/editing/pasteboard/paste-text-004-expected.txt: Added.
+        * layout-tests/editing/pasteboard/paste-text-004.html: Added.
+        * layout-tests/editing/pasteboard/paste-text-005-expected.txt: Added.
+        * layout-tests/editing/pasteboard/paste-text-005.html: Added.
+        * layout-tests/editing/pasteboard/paste-text-006-expected.txt: Added.
+        * layout-tests/editing/pasteboard/paste-text-006.html: Added.
+        * layout-tests/editing/pasteboard/paste-text-007-expected.txt: Added.
+        * layout-tests/editing/pasteboard/paste-text-007.html: Added.
+        * layout-tests/editing/pasteboard/paste-text-008-expected.txt: Added.
+        * layout-tests/editing/pasteboard/paste-text-008.html: Added.
+        * layout-tests/editing/pasteboard/paste-text-009-expected.txt: Added.
+        * layout-tests/editing/pasteboard/paste-text-009.html: Added.
+
+2004-11-29  Ken Kocienda  <kocienda@apple.com>
+
         Reviewed by Harrison
 
         Made two small changes that make it possible for comments to have DOM nodes made for them
diff --git a/WebCore/ForwardingHeaders/editing/html_interchange.h b/WebCore/ForwardingHeaders/editing/html_interchange.h
new file mode 100644 (file)
index 0000000..9c8f4eb
--- /dev/null
@@ -0,0 +1 @@
+#import <html_interchange.h>
diff --git a/WebCore/khtml/editing/html_interchange.h b/WebCore/khtml/editing/html_interchange.h
new file mode 100644 (file)
index 0000000..20d2eb9
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2004 Apple Computer, Inc.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+ */
+
+#ifndef KHTML_EDITING_HTML_INTERCHANGE_H
+#define KHTML_EDITING_HTML_INTERCHANGE_H
+
+#define KHTMLInterchangeNewline   "KHTMLInterchangeNewline"
+
+enum EAnnotateForInterchange { DoNotAnnotateForInterchange, AnnotateForInterchange };
+
+#endif
index 14d9c01..abe7879 100644 (file)
@@ -41,6 +41,7 @@
 #include "dom2_rangeimpl.h"
 #include "html_elementimpl.h"
 #include "html_imageimpl.h"
+#include "html_interchange.h"
 #include "htmlattrs.h"
 #include "htmltags.h"
 #include "khtml_part.h"
@@ -65,9 +66,11 @@ using DOM::DocumentFragmentImpl;
 using DOM::DocumentImpl;
 using DOM::DOMString;
 using DOM::DOMStringImpl;
+using DOM::DoNotStayInBlock;
 using DOM::DoNotUpdateLayout;
 using DOM::EditingTextImpl;
 using DOM::ElementImpl;
+using DOM::EStayInBlock;
 using DOM::HTMLElementImpl;
 using DOM::HTMLImageElementImpl;
 using DOM::NamedAttrMapImpl;
@@ -2670,139 +2673,419 @@ void JoinTextNodesCommand::doUnapply()
 //------------------------------------------------------------------------------------------
 // ReplaceSelectionCommand
 
-ReplaceSelectionCommand::ReplaceSelectionCommand(DocumentImpl *document, DocumentFragmentImpl *fragment, bool selectReplacement, bool smartReplace) 
-    : CompositeEditCommand(document), m_fragment(fragment), m_selectReplacement(selectReplacement), m_smartReplace(smartReplace)
+ReplacementFragment::ReplacementFragment(DocumentFragmentImpl *fragment)
+    : m_fragment(fragment), m_hasInterchangeNewlineComment(false), m_hasMoreThanOneBlock(false)
 {
-    ASSERT(m_fragment);
+    if (!m_fragment) {
+        m_type = EmptyFragment;
+        return;
+    }
+
     m_fragment->ref();
+
+    NodeImpl *firstChild = m_fragment->firstChild();
+    NodeImpl *lastChild = m_fragment->lastChild();
+
+    if (!firstChild) {
+        m_type = EmptyFragment;
+        return;
+    }
+
+    if (firstChild == lastChild && firstChild->isTextNode()) {
+        m_type = SingleTextNodeFragment;
+        return;
+    }
+    
+    m_type = TreeFragment;
+
+    NodeImpl *node = firstChild;
+    int blockCount = 0;
+    NodeImpl *commentToDelete = 0;
+    while (node) {
+        NodeImpl *next = node->traverseNextNode();
+        if (isInterchangeNewlineComment(node)) {
+            m_hasInterchangeNewlineComment = true;
+            commentToDelete = node;
+        }
+        else if (isProbablyBlock(node))
+            blockCount++;    
+        node = next;
+     }
+
+     if (commentToDelete)
+        removeNode(commentToDelete);
+
+    firstChild = m_fragment->firstChild();
+    lastChild = m_fragment->lastChild();
+    if (!isProbablyBlock(firstChild))
+        blockCount++;
+    if (!isProbablyBlock(lastChild) && firstChild != lastChild)
+        blockCount++;
+
+     if (blockCount > 1)
+        m_hasMoreThanOneBlock = true;
+}
+
+ReplacementFragment::~ReplacementFragment()
+{
+    if (m_fragment)
+        m_fragment->deref();
+}
+
+NodeImpl *ReplacementFragment::firstChild() const 
+{ 
+    return m_fragment->firstChild(); 
+}
+
+NodeImpl *ReplacementFragment::lastChild() const 
+{ 
+    return  m_fragment->lastChild(); 
+}
+
+NodeImpl *ReplacementFragment::mergeStartNode() const
+{
+    NodeImpl *node = m_fragment->firstChild();
+    while (node) {
+        NodeImpl *next = node->traverseNextNode();
+        if (!isProbablyBlock(node))
+            return node;
+        node = next;
+     }
+     return 0;
+}
+
+NodeImpl *ReplacementFragment::mergeEndNode() const
+{
+    NodeImpl *node = m_fragment->lastChild();
+    while (node && node->lastChild())
+        node = node->lastChild();
+    while (node) {
+        NodeImpl *prev = node->traversePreviousNode();
+        if (!isProbablyBlock(node)) {
+            NodeImpl *previousSibling = node->previousSibling();
+            while (1) {
+                if (!previousSibling || isProbablyBlock(previousSibling))
+                    return node;
+                node = previousSibling;
+            }
+        }
+        node = prev;
+    }
+    return 0;
+}
+
+void ReplacementFragment::pruneEmptyNodes()
+{
+    bool run = true;
+    while (run) {
+        run = false;
+        NodeImpl *node = m_fragment->firstChild();
+        while (node) {
+            if ((node->isTextNode() && static_cast<TextImpl *>(node)->length() == 0) ||
+                (isProbablyBlock(node) && node->childNodeCount() == 0)) {
+                NodeImpl *next = node->traverseNextSibling();
+                removeNode(node);
+                node = next;
+                run = true;
+            }
+            else {
+                node = node->traverseNextNode();
+            }
+         }
+    }
+}
+
+bool ReplacementFragment::isInterchangeNewlineComment(const NodeImpl *node)
+{
+    return isComment(node) && node->nodeValue() == KHTMLInterchangeNewline;
+}
+
+void ReplacementFragment::removeNode(NodeImpl *node)
+{
+    if (!node)
+        return;
+        
+    NodeImpl *parent = node->parentNode();
+    if (!parent)
+        return;
+        
+    int exceptionCode = 0;
+    parent->removeChild(node, exceptionCode);
+    ASSERT(exceptionCode == 0);
+ }
+
+bool isComment(const NodeImpl *node)
+{
+    return node && node->nodeType() == Node::COMMENT_NODE;
+}
+
+bool isProbablyBlock(const NodeImpl *node)
+{
+    if (!node)
+        return false;
+    
+    switch (node->id()) {
+        case ID_BLOCKQUOTE:
+        case ID_DD:
+        case ID_DIV:
+        case ID_DL:
+        case ID_DT:
+        case ID_H1:
+        case ID_H2:
+        case ID_H3:
+        case ID_H4:
+        case ID_H5:
+        case ID_H6:
+        case ID_HR:
+        case ID_LI:
+        case ID_OL:
+        case ID_P:
+        case ID_PRE:
+        case ID_TD:
+        case ID_TH:
+        case ID_TR:
+        case ID_UL:
+            return true;
+    }
+    
+    return false;
+}
+
+ReplaceSelectionCommand::ReplaceSelectionCommand(DocumentImpl *document, DocumentFragmentImpl *fragment, bool selectReplacement, bool smartReplace) 
+    : CompositeEditCommand(document), 
+      m_fragment(fragment),
+      m_selectReplacement(selectReplacement), 
+      m_smartReplace(smartReplace)
+{
 }
 
 ReplaceSelectionCommand::~ReplaceSelectionCommand()
 {
-    ASSERT(m_fragment);
-    m_fragment->deref();
 }
 
 void ReplaceSelectionCommand::doApply()
 {
-    NodeImpl *firstChild = m_fragment->firstChild();
-    NodeImpl *lastChild = m_fragment->lastChild();
-
     Selection selection = endingSelection();
+    VisiblePosition visibleStart(selection.start());
+    VisiblePosition visibleEnd(selection.end());
+    bool startAtStartOfLine = isFirstVisiblePositionOnLine(visibleStart);
+    bool startAtStartOfBlock = isFirstVisiblePositionInBlock(visibleStart);
+    bool startAtEndOfBlock = isLastVisiblePositionInBlock(visibleStart);
+    bool startAtBlockBoundary = startAtStartOfBlock || startAtEndOfBlock;
+    NodeImpl *startBlock = selection.start().node()->enclosingBlockFlowElement();
+    NodeImpl *endBlock = selection.end().node()->enclosingBlockFlowElement();
+
+    bool mergeStart = !(startAtStartOfLine && (m_fragment.hasInterchangeNewlineComment() || m_fragment.hasMoreThanOneBlock()));
+    bool mergeEnd = !m_fragment.hasInterchangeNewlineComment() && m_fragment.hasMoreThanOneBlock();
+    Position startPos = Position(selection.start().node()->enclosingBlockFlowElement(), 0);
+    Position endPos; 
+    EStayInBlock upstreamStayInBlock = StayInBlock;
 
     // Delete the current selection, or collapse whitespace, as needed
-    if (selection.isRange())
-        deleteSelection();
+    if (selection.isRange()) {
+        deleteSelection(false, !(m_fragment.hasInterchangeNewlineComment() || m_fragment.hasMoreThanOneBlock()));
+    }
+    else if (selection.isCaret() && mergeEnd && !startAtBlockBoundary) {
+        // The start and the end need to wind up in separate blocks.
+        // Insert a paragraph separator to make that happen.
+        insertParagraphSeparator();
+        upstreamStayInBlock = DoNotStayInBlock;
+    }
     
-    KHTMLPart *part = document()->part();
-    ASSERT(part);
+    selection = endingSelection();
+    if (!startAtBlockBoundary || !startPos.node()->inDocument())
+        startPos = selection.start().upstream(upstreamStayInBlock);
+    endPos = selection.end().downstream(); 
     
     // This command does not use any typing style that is set as a residual effect of
     // a delete.
     // FIXME: Improve typing style.
     // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
+    KHTMLPart *part = document()->part();
     part->clearTypingStyle();
     setTypingStyle(0);
-    
-    selection = endingSelection();
-    ASSERT(selection.isCaret());
 
+    if (!m_fragment.firstChild())
+        return;
+    
     // Now that we are about to add content, check to see if a placeholder element
     // can be removed.
-    Position pos = selection.start();
-    NodeImpl *block = pos.node()->enclosingBlockFlowElement();
+    NodeImpl *block = startPos.node()->enclosingBlockFlowElement();
     if (removeBlockPlaceholderIfNeeded(block)) {
-        pos = Position(block, 0);
+        startPos = Position(block, 0);
     }
     
     bool addLeadingSpace = false;
     bool addTrailingSpace = false;
     if (m_smartReplace) {
-        addLeadingSpace = pos.leadingWhitespacePosition().isNull();
-        addTrailingSpace = pos.trailingWhitespacePosition().isNull();
+        addLeadingSpace = startPos.leadingWhitespacePosition().isNull();
+        addTrailingSpace = endPos.trailingWhitespacePosition().isNull();
     }
 
 #if APPLE_CHANGES
     if (addLeadingSpace) {
-        QChar previousChar = VisiblePosition(pos).previous().character();
+        QChar previousChar = VisiblePosition(startPos).previous().character();
         if (!previousChar.isNull()) {
             addLeadingSpace = !KWQ(part)->isCharacterSmartReplaceExempt(previousChar, true);
         }
     }
     if (addTrailingSpace) {
-        QChar thisChar = VisiblePosition(pos).character();
+        QChar thisChar = VisiblePosition(endPos).character();
         if (!thisChar.isNull()) {
             addTrailingSpace = !KWQ(part)->isCharacterSmartReplaceExempt(thisChar, false);
         }
     }
 #endif
+
+    document()->updateLayout();
+
+    NodeImpl *refBlock = startPos.node()->enclosingBlockFlowElement();
+    Position insertionPos = startPos;
+    bool insertBlocksBefore = true;
+
+    NodeImpl *firstNodeInserted = 0;
+    NodeImpl *lastNodeInserted = 0;
+    bool lastNodeInsertedInMergeEnd = false;
+
+    // prune empty nodes from fragment
+    m_fragment.pruneEmptyNodes();
+
+    // Merge content into the end block, if necessary.
+    if (mergeEnd) {
+        NodeImpl *node = m_fragment.mergeEndNode();
+        if (node) {
+            NodeImpl *refNode = node;
+            NodeImpl *node = refNode ? refNode->nextSibling() : 0;
+            insertNodeAt(refNode, endPos.node(), endPos.offset());
+            firstNodeInserted = refNode;
+            lastNodeInserted = refNode;
+            while (node && !isProbablyBlock(node)) {
+                NodeImpl *next = node->nextSibling();
+                insertNodeAfter(node, refNode);
+                lastNodeInserted = node;
+                refNode = node;
+                node = next;
+            }
+            lastNodeInsertedInMergeEnd = true;
+        }
+    }
     
-    if (!firstChild) {
-        // Pasting something that didn't parse or was empty.
-        ASSERT(!lastChild);
-    } else if (firstChild == lastChild && firstChild->isTextNode()) {
-        // FIXME: HTML fragment case needs to be improved to the point
-        // where we can remove this separate case.
-        
-        // Simple text paste. Treat as if the text were typed.
-        Position upstreamStart(pos.upstream(StayInBlock));
-        DOMString text = static_cast<TextImpl *>(firstChild)->data();
-        if (addLeadingSpace) {
-            text = " " + text;
+    // prune empty nodes from fragment
+    m_fragment.pruneEmptyNodes();
+
+    // Merge content into the start block, if necessary.
+    if (mergeStart) {
+        NodeImpl *node = m_fragment.mergeStartNode();
+        NodeImpl *insertionNode = 0;
+        if (node) {
+            NodeImpl *refNode = node;
+            NodeImpl *node = refNode ? refNode->nextSibling() : 0;
+            insertNodeAt(refNode, startPos.node(), startPos.offset());
+            firstNodeInserted = refNode;
+            if (!lastNodeInsertedInMergeEnd)
+                lastNodeInserted = refNode;
+            insertionNode = refNode;
+            while (node && !isProbablyBlock(node)) {
+                NodeImpl *next = node->nextSibling();
+                insertNodeAfter(node, refNode);
+                if (!lastNodeInsertedInMergeEnd)
+                    lastNodeInserted = node;
+                insertionNode = node;
+                refNode = node;
+                node = next;
+            }
         }
-        if (addTrailingSpace) {
-            text += " ";
+        if (insertionNode)
+            insertionPos = Position(insertionNode, insertionNode->caretMaxOffset());
+        insertBlocksBefore = false;
+    }
+
+    // prune empty nodes from fragment
+    m_fragment.pruneEmptyNodes();
+    
+    // Merge everything remaining.
+    NodeImpl *node = m_fragment.firstChild();
+    if (node) {
+        NodeImpl *refNode = node;
+        NodeImpl *node = refNode ? refNode->nextSibling() : 0;
+        if (isProbablyBlock(refNode) && (insertBlocksBefore || startAtStartOfBlock)) {
+            insertNodeBefore(refNode, refBlock);
         }
-        inputText(text, m_selectReplacement);
-    } 
-    else {
-        // HTML fragment paste.
-        
-        // FIXME: Add leading and trailing spaces to the fragment?
-        // Or just insert them as we insert it?
-        
-        NodeImpl *beforeNode = firstChild;
-        NodeImpl *node = firstChild->nextSibling();
-        
-        insertNodeAt(firstChild, pos.node(), pos.offset());
-        
-        // Insert the nodes from the fragment
+        else if (isProbablyBlock(refNode) && startAtEndOfBlock) {
+            insertNodeAfter(refNode, refBlock);
+        }
+        else {
+            insertNodeAt(refNode, insertionPos.node(), insertionPos.offset());
+        }
+        if (!firstNodeInserted)
+            firstNodeInserted = refNode;
+        if (!lastNodeInsertedInMergeEnd)
+            lastNodeInserted = refNode;
         while (node) {
             NodeImpl *next = node->nextSibling();
-            insertNodeAfter(node, beforeNode);
-            beforeNode = node;
+            insertNodeAfter(node, refNode);
+            if (!lastNodeInsertedInMergeEnd)
+                lastNodeInserted = node;
+            refNode = node;
             node = next;
         }
-        ASSERT(beforeNode);
-       
-        // Find the last leaf.
-        NodeImpl *lastLeaf = lastChild;
-        while (1) {
-            NodeImpl *nextChild = lastLeaf->lastChild();
-            if (!nextChild)
-                break;
-            lastLeaf = nextChild;
-        }
+        insertionPos = Position(lastNodeInserted, lastNodeInserted->caretMaxOffset());
+    }
 
-        // Find the first leaf.
-        NodeImpl *firstLeaf = firstChild;
-        while (1) {
-            NodeImpl *nextChild = firstLeaf->firstChild();
-            if (!nextChild)
-                break;
-            firstLeaf = nextChild;
-        }
-        
-        Selection replacementSelection(Position(firstLeaf, firstLeaf->caretMinOffset()), Position(lastLeaf, lastLeaf->caretMaxOffset()));
-       if (m_selectReplacement) {
-            // Select what was inserted.
-            setEndingSelection(replacementSelection);
-        } 
-        else {
-            // Place the cursor after what was inserted, and mark misspellings in the inserted content.
-            selection = Selection(Position(lastLeaf, lastLeaf->caretMaxOffset()));
-            setEndingSelection(selection);
+    if (m_fragment.hasInterchangeNewlineComment()) {
+        if (startBlock == endBlock && !isProbablyBlock(lastNodeInserted)) {
+            setEndingSelection(insertionPos);
+            insertParagraphSeparator();
+            endPos = endingSelection().end().downstream();
         }
+        completeHTMLReplacement(startPos, endPos);
+    }
+    else {
+        completeHTMLReplacement(firstNodeInserted, lastNodeInserted);
+    }
+}
+
+void ReplaceSelectionCommand::completeHTMLReplacement(const Position &start, const Position &end)
+ {
+    if (start.isNull() || !start.node()->inDocument() || end.isNull() || !end.node()->inDocument())
+        return;
+    m_selectReplacement ? setEndingSelection(Selection(start, end)) : setEndingSelection(end);
+}
+
+void ReplaceSelectionCommand::completeHTMLReplacement(NodeImpl *firstNodeInserted, NodeImpl *lastNodeInserted)
+{
+    if (!firstNodeInserted || !firstNodeInserted->inDocument() ||
+        !lastNodeInserted || !lastNodeInserted->inDocument())
+        return;
+
+    // Find the last leaf.
+    NodeImpl *lastLeaf = lastNodeInserted;
+    while (1) {
+        NodeImpl *nextChild = lastLeaf->lastChild();
+        if (!nextChild)
+            break;
+        lastLeaf = nextChild;
+    }
+
+    // Find the first leaf.
+    NodeImpl *firstLeaf = firstNodeInserted;
+    while (1) {
+        NodeImpl *nextChild = firstLeaf->firstChild();
+        if (!nextChild)
+            break;
+        firstLeaf = nextChild;
+    }
+    
+    Position start(firstLeaf, firstLeaf->caretMinOffset());
+    Position end(lastLeaf, lastLeaf->caretMaxOffset());
+    Selection replacementSelection(start, end);
+    if (m_selectReplacement) {
+        // Select what was inserted.
+        setEndingSelection(replacementSelection);
+    } 
+    else {
+        // Place the cursor after what was inserted, and mark misspellings in the inserted content.
+        setEndingSelection(end);
     }
 }
 
index fdcf10e..ae1dc5f 100644 (file)
@@ -481,6 +481,51 @@ private:
 //------------------------------------------------------------------------------------------
 // ReplaceSelectionCommand
 
+// --- ReplacementFragment helper class
+
+class ReplacementFragment
+{
+public:
+    ReplacementFragment(DOM::DocumentFragmentImpl *fragment);
+    ~ReplacementFragment();
+
+    enum EFragmentType { EmptyFragment, SingleTextNodeFragment, TreeFragment };
+
+    DOM::DocumentFragmentImpl *root() const { return m_fragment; }
+    DOM::NodeImpl *firstChild() const;
+    DOM::NodeImpl *lastChild() const;
+
+    DOM::NodeImpl *mergeStartNode() const;
+    DOM::NodeImpl *mergeEndNode() const;
+    
+    void pruneEmptyNodes();
+
+    EFragmentType type() const { return m_type; }
+    bool isEmpty() const { return m_type == EmptyFragment; }
+    bool isSingleTextNode() const { return m_type == SingleTextNodeFragment; }
+    bool isTreeFragment() const { return m_type == TreeFragment; }
+
+    bool hasMoreThanOneBlock() const { return m_hasMoreThanOneBlock; }
+    bool hasInterchangeNewlineComment() const { return m_hasInterchangeNewlineComment; }
+
+private:
+    // no copy construction or assignment
+    ReplacementFragment(const ReplacementFragment &);
+    ReplacementFragment &operator=(const ReplacementFragment &);
+
+    static bool isInterchangeNewlineComment(const DOM::NodeImpl *);
+    void removeNode(DOM::NodeImpl *);
+
+    EFragmentType m_type;
+    DOM::DocumentFragmentImpl *m_fragment;
+    bool m_hasInterchangeNewlineComment;
+    bool m_hasMoreThanOneBlock;
+};
+
+// free-floating helper functions
+bool isProbablyBlock(const DOM::NodeImpl *);
+bool isComment(const DOM::NodeImpl *);
+
 class ReplaceSelectionCommand : public CompositeEditCommand
 {
 public:
@@ -490,7 +535,10 @@ public:
     virtual void doApply();
 
 private:
-    DOM::DocumentFragmentImpl *m_fragment;
+    void completeHTMLReplacement(const DOM::Position &, const DOM::Position &);
+    void completeHTMLReplacement(DOM::NodeImpl *, DOM::NodeImpl *);
+    
+    ReplacementFragment m_fragment;
     bool m_selectReplacement;
     bool m_smartReplace;
 };
index 3f62fc8..73389f7 100644 (file)
@@ -30,6 +30,7 @@
 #include "dom_xmlimpl.h"
 #include "html/html_elementimpl.h"
 #include "misc/htmltags.h"
+#include "editing/visible_position.h"
 #include "editing/visible_text.h"
 #include "xml/dom_position.h"
 
@@ -826,7 +827,25 @@ DOMString RangeImpl::toString( int &exceptioncode ) const
     return text;
 }
 
-DOMString RangeImpl::toHTML(QPtrList<NodeImpl> *nodes) const
+void RangeImpl::addCommentToHTMLMarkup(const DOMString &comment, QStringList &markups, EAddToMarkup appendOrPrepend) const
+ {
+    if (!m_ownerDocument)
+        return;
+        
+    CommentImpl *n = new CommentImpl(m_ownerDocument, comment);
+    n->ref();
+    switch (appendOrPrepend) {
+        case PrependToMarkup:
+            markups.prepend(n->startMarkup(this));
+            break;
+        case AppendToMarkup:
+            markups.append(n->startMarkup(this));
+            break;
+    }
+    n->deref();
+}
+
+DOMString RangeImpl::toHTML(QPtrList<NodeImpl> *nodes, EAnnotateForInterchange annotate) const
 {
     int exceptionCode;
     NodeImpl *commonAncestor = commonAncestorContainer(exceptionCode);
@@ -847,6 +866,13 @@ DOMString RangeImpl::toHTML(QPtrList<NodeImpl> *nodes) const
     NodeImpl *next;
     for (NodeImpl *n = startNode(); n != pastEnd; n = next) {
         next = n->traverseNextNode();
+        if (!n->renderer())
+            continue;
+
+        if (n->isBlockFlow() && next == pastEnd) {
+            // Don't write out an empty block.
+            continue;
+        }
         
         // Add the node to the markup.
         markups.append(n->startMarkup(this));
@@ -860,7 +886,7 @@ DOMString RangeImpl::toHTML(QPtrList<NodeImpl> *nodes) const
             lastClosed = n;
             
             // Check if the node is the last leaf of a tree.
-            if (n->nextSibling() == 0) {
+            if (n->nextSibling() == 0 || next == pastEnd) {
                 if (!ancestorsToClose.isEmpty()) {
                     // Close up the ancestors.
                     while (NodeImpl *ancestor = ancestorsToClose.last()) {
@@ -917,6 +943,15 @@ DOMString RangeImpl::toHTML(QPtrList<NodeImpl> *nodes) const
         }
     }
     
+    if (annotate) {
+        Position pos(m_endContainer, m_endOffset);
+        NodeImpl *block = pos.node()->enclosingBlockFlowElement();
+        NodeImpl *upstreamBlock = pos.upstream().node()->enclosingBlockFlowElement();
+        if (block != upstreamBlock) {
+            addCommentToHTMLMarkup(KHTMLInterchangeNewline, markups, AppendToMarkup);    
+        }
+    }
+        
     return markups.join("");
 }
 
index 11bfea8..ebc4e99 100644 (file)
 
 #include <qptrlist.h>
 #include "dom/dom2_range.h"
+#include "editing/html_interchange.h"
 #include "misc/shared.h"
 
+class QStringList;
+
 namespace DOM {
 
 class DocumentPtr;
@@ -69,7 +72,7 @@ public:
     DocumentFragmentImpl *cloneContents ( int &exceptioncode );
     void insertNode( NodeImpl *newNode, int &exceptioncode );
     DOMString toString ( int &exceptioncode ) const;
-    DOMString toHTML(QPtrList<NodeImpl> *nodes=NULL) const;
+    DOMString toHTML(QPtrList<NodeImpl> *nodes=NULL, EAnnotateForInterchange annotate=DoNotAnnotateForInterchange) const;
     DOMString text() const;
 
     DocumentFragmentImpl *createContextualFragment ( DOMString &html, int &exceptioncode ) const;
@@ -119,6 +122,9 @@ private:
     void setEndContainer(NodeImpl *_endContainer);
     void checkDeleteExtract(int &exceptioncode);
     bool containedByReadOnly() const;
+    
+    enum EAddToMarkup { PrependToMarkup, AppendToMarkup };
+    void addCommentToHTMLMarkup(const DOMString &, QStringList &, EAddToMarkup) const;
 };
 
 } // namespace
index 735d4de..70523c1 100644 (file)
@@ -551,7 +551,7 @@ static bool initializedKJS = FALSE;
     if (nodes) {
         nodeList = new QPtrList<NodeImpl>;
     }
-    NSString *markupString = [range _rangeImpl]->toHTML(nodeList).string().getNSString();
+    NSString *markupString = [range _rangeImpl]->toHTML(nodeList, AnnotateForInterchange).string().getNSString();
     if (nodes) {
         *nodes = [self nodesFromList:nodeList];
         delete nodeList;