Bug 137450 - Problem copying and pasting a table from a web page to excel. r=bz
authorlwz <limweizhong@gmail.com>
Wed, 16 May 2012 12:39:54 -0400
changeset 95586 14e6675d74e4285f588c5152bdd15ff0525b562a
parent 95585 2e7bd3c9bbb5f328859f94f313d20913de4c9e22
child 95587 2cfe33680b088817302c48d63581efc5c4cb37d7
push idunknown
push userunknown
push dateunknown
reviewersbz
bugs137450
milestone15.0a1
Bug 137450 - Problem copying and pasting a table from a web page to excel. r=bz
content/base/src/nsDocumentEncoder.cpp
--- a/content/base/src/nsDocumentEncoder.cpp
+++ b/content/base/src/nsDocumentEncoder.cpp
@@ -125,16 +125,21 @@ protected:
   nsresult SerializeRangeNodes(nsRange* aRange, 
                                nsINode* aNode, 
                                nsAString& aString,
                                PRInt32 aDepth);
   nsresult SerializeRangeContextStart(const nsTArray<nsINode*>& aAncestorArray,
                                       nsAString& aString);
   nsresult SerializeRangeContextEnd(const nsTArray<nsINode*>& aAncestorArray,
                                     nsAString& aString);
+  virtual PRInt32
+  GetImmediateContextCount(const nsTArray<nsINode*>& aAncestorArray)
+  {
+    return -1;
+  }
 
   nsresult FlushText(nsAString& aString, bool aForce);
 
   bool IsVisibleNode(nsINode* aNode)
   {
     NS_PRECONDITION(aNode, "");
 
     if (mFlags & SkipInvisibleContent) {
@@ -180,16 +185,19 @@ protected:
   PRInt32           mStartRootIndex;
   PRInt32           mEndRootIndex;
   nsAutoTArray<nsINode*, 8>    mCommonAncestors;
   nsAutoTArray<nsIContent*, 8> mStartNodes;
   nsAutoTArray<PRInt32, 8>     mStartOffsets;
   nsAutoTArray<nsIContent*, 8> mEndNodes;
   nsAutoTArray<PRInt32, 8>     mEndOffsets;
   bool              mHaltRangeHint;  
+  // HTML context has already been serialized for
+  // table cell selections (where parent is <tr>)
+  bool              mContextAlreadySerialized;
   bool              mIsCopying;  // Set to true only while copying
   bool              mNodeIsContainer;
   nsStringBuffer*   mCachedBuffer;
 };
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsDocumentEncoder)
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDocumentEncoder)
@@ -227,16 +235,17 @@ void nsDocumentEncoder::Initialize(bool 
 {
   mFlags = 0;
   mWrapColumn = 72;
   mStartDepth = 0;
   mEndDepth = 0;
   mStartRootIndex = 0;
   mEndRootIndex = 0;
   mHaltRangeHint = false;
+  mContextAlreadySerialized = false;
   mNodeIsContainer = false;
   if (aClearCachedSerializer) {
     mSerializer = nsnull;
   }
 }
 
 nsDocumentEncoder::~nsDocumentEncoder()
 {
@@ -908,51 +917,65 @@ nsDocumentEncoder::SerializeRangeNodes(n
   }
   return NS_OK;
 }
 
 nsresult
 nsDocumentEncoder::SerializeRangeContextStart(const nsTArray<nsINode*>& aAncestorArray,
                                               nsAString& aString)
 {
-  PRInt32 i = aAncestorArray.Length();
+  if (mContextAlreadySerialized) {
+    return NS_OK;
+  }
+  PRInt32 i = aAncestorArray.Length(), j;
   nsresult rv = NS_OK;
 
+  // currently only for table-related elements; see Bug 137450
+  j = GetImmediateContextCount(aAncestorArray);
+
   while (i > 0) {
     nsINode *node = aAncestorArray.ElementAt(--i);
 
     if (!node)
       break;
 
-    if (IncludeInContext(node)) {
+    // Either a general inclusion or as immediate context
+    if (IncludeInContext(node) || i < j) {
       rv = SerializeNodeStart(node, 0, -1, aString);
 
       if (NS_FAILED(rv))
         break;
     }
   }
 
   return rv;
 }
 
 nsresult
 nsDocumentEncoder::SerializeRangeContextEnd(const nsTArray<nsINode*>& aAncestorArray,
                                             nsAString& aString)
 {
-  PRInt32 i = 0;
+  if (mContextAlreadySerialized) {
+    return NS_OK;
+  }
+  PRInt32 i = 0, j;
   PRInt32 count = aAncestorArray.Length();
   nsresult rv = NS_OK;
 
+  // currently only for table-related elements
+  j = GetImmediateContextCount(aAncestorArray);
+
   while (i < count) {
     nsINode *node = aAncestorArray.ElementAt(i++);
 
     if (!node)
       break;
 
-    if (IncludeInContext(node)) {
+    // Either a general inclusion or as immediate context
+    if (IncludeInContext(node) || i - 1 < j) {
       rv = SerializeNodeEnd(node, aString);
 
       if (NS_FAILED(rv))
         break;
     }
   }
 
   return rv;
@@ -1078,44 +1101,76 @@ nsDocumentEncoder::EncodeToString(nsAStr
 
     nsCOMPtr<nsIDOMNode> node, prevNode;
     for (i = 0; i < count; i++) {
       mSelection->GetRangeAt(i, getter_AddRefs(range));
 
       // Bug 236546: newlines not added when copying table cells into clipboard
       // Each selected cell shows up as a range containing a row with a single cell
       // get the row, compare it to previous row and emit </tr><tr> as needed
+      // Bug 137450: Problem copying/pasting a table from a web page to Excel.
+      // Each separate block of <tr></tr> produced above will be wrapped by the
+      // immediate context. This assumes that you can't select cells that are
+      // multiple selections from two tables simultaneously.
       range->GetStartContainer(getter_AddRefs(node));
       NS_ENSURE_TRUE(node, NS_ERROR_FAILURE);
       if (node != prevNode) {
+        nsCOMPtr<nsINode> p;
         if (prevNode) {
-          nsCOMPtr<nsINode> p = do_QueryInterface(prevNode);
+          p = do_QueryInterface(prevNode);
           rv = SerializeNodeEnd(p, output);
           NS_ENSURE_SUCCESS(rv, rv);
-          prevNode = nsnull;
         }
         nsCOMPtr<nsIContent> content = do_QueryInterface(node);
-        if (content && content->Tag() == nsGkAtoms::tr) {
-          nsCOMPtr<nsINode> n = do_QueryInterface(node);
+        if (content && content->IsHTML(nsGkAtoms::tr)) {
+          nsINode* n = content;
+          if (!prevNode) {
+            // Went from a non-<tr> to a <tr>
+            mCommonAncestors.Clear();
+            nsContentUtils::GetAncestors(n->GetNodeParent(), mCommonAncestors);
+            rv = SerializeRangeContextStart(mCommonAncestors, output);
+            NS_ENSURE_SUCCESS(rv, rv);
+            // Don't let SerializeRangeToString serialize the context again
+            mContextAlreadySerialized = true;
+          }
+
           rv = SerializeNodeStart(n, 0, -1, output);
           NS_ENSURE_SUCCESS(rv, rv);
           prevNode = node;
+        } else if (prevNode) {
+          // Went from a <tr> to a non-<tr>
+          mCommonAncestors.Clear();
+          nsContentUtils::GetAncestors(p->GetNodeParent(), mCommonAncestors);
+          rv = SerializeRangeContextEnd(mCommonAncestors, output);
+          NS_ENSURE_SUCCESS(rv, rv);
+          // Allow output of context again
+          mContextAlreadySerialized = false;
+          prevNode = nsnull;
         }
       }
 
       nsRange* r = static_cast<nsRange*>(range.get());
       rv = SerializeRangeToString(r, output);
       NS_ENSURE_SUCCESS(rv, rv);
     }
+
     if (prevNode) {
       nsCOMPtr<nsINode> p = do_QueryInterface(prevNode);
       rv = SerializeNodeEnd(p, output);
       NS_ENSURE_SUCCESS(rv, rv);
+      mCommonAncestors.Clear();
+      nsContentUtils::GetAncestors(p->GetNodeParent(), mCommonAncestors);
+      rv = SerializeRangeContextEnd(mCommonAncestors, output);
+      NS_ENSURE_SUCCESS(rv, rv);
     }
 
+    // could have been placed into the above if-loop
+    // but put outside to be safe
+    mContextAlreadySerialized = false; 
+
     mSelection = nsnull;
   } else if (mRange) {
       rv = SerializeRangeToString(mRange, output);
 
       mRange = nsnull;
   } else if (mNode) {
     if (!mNodeFixup && !(mFlags & SkipInvisibleContent) && !mStream &&
         mNodeIsContainer) {
@@ -1257,16 +1312,18 @@ protected:
   nsCOMPtr<nsIDOMNode> GetChildAt(nsIDOMNode *aParent, PRInt32 aOffset);
   bool IsMozBR(nsIDOMNode* aNode);
   nsresult GetNodeLocation(nsIDOMNode *inChild, nsCOMPtr<nsIDOMNode> *outParent, PRInt32 *outOffset);
   bool IsRoot(nsIDOMNode* aNode);
   bool IsFirstNode(nsIDOMNode *aNode);
   bool IsLastNode(nsIDOMNode *aNode);
   bool IsEmptyTextContent(nsIDOMNode* aNode);
   virtual bool IncludeInContext(nsINode *aNode);
+  virtual PRInt32
+  GetImmediateContextCount(const nsTArray<nsINode*>& aAncestorArray);
 
   bool mIsTextWidget;
 };
 
 nsHTMLCopyEncoder::nsHTMLCopyEncoder()
 {
   mIsTextWidget = false;
 }
@@ -1970,8 +2027,31 @@ nsresult
 NS_NewHTMLCopyTextEncoder(nsIDocumentEncoder** aResult)
 {
   *aResult = new nsHTMLCopyEncoder;
   if (!*aResult)
     return NS_ERROR_OUT_OF_MEMORY;
  NS_ADDREF(*aResult);
  return NS_OK;
 }
+
+PRInt32
+nsHTMLCopyEncoder::GetImmediateContextCount(const nsTArray<nsINode*>& aAncestorArray)
+{
+  PRInt32 i = aAncestorArray.Length(), j = 0;
+  while (j < i) {
+    nsINode *node = aAncestorArray.ElementAt(j);
+    if (!node) {
+      break;
+    }
+    nsCOMPtr<nsIContent> content(do_QueryInterface(node));
+    if (!content || !content->IsHTML() || content->Tag() != nsGkAtoms::tr    &&
+                                          content->Tag() != nsGkAtoms::thead &&
+                                          content->Tag() != nsGkAtoms::tbody &&
+                                          content->Tag() != nsGkAtoms::tfoot &&
+                                          content->Tag() != nsGkAtoms::table) {
+      break;
+    }
+    ++j;
+  }
+  return j;
+}
+