escape ]]> in element content in OptimizedForSpeedSaver. Thanks to aizu-m. This closes #71

git-svn-id: https://svn.apache.org/repos/asf/xmlbeans/trunk@1935617 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/main/java/org/apache/xmlbeans/impl/store/Saver.java b/src/main/java/org/apache/xmlbeans/impl/store/Saver.java
index 2a16750..2c1c5c7 100755
--- a/src/main/java/org/apache/xmlbeans/impl/store/Saver.java
+++ b/src/main/java/org/apache/xmlbeans/impl/store/Saver.java
@@ -2074,10 +2074,11 @@
             int cch = c._cchSrc;
             int off = c._offSrc;
             int index = 0;
+            int trailingBrackets = 0;
             while (index < cch) {
                 int indexLimit = Math.min(index + 512, cch);
                 CharUtil.getChars(_buf, 0, src, off + index, indexLimit - index);
-                entitizeAndWriteText(indexLimit - index);
+                trailingBrackets = entitizeAndWriteText(indexLimit - index, trailingBrackets);
                 index = indexLimit;
             }
         }
@@ -2113,7 +2114,7 @@
             }
         }
 
-        private void entitizeAndWriteText(int bufLimit) {
+        private int entitizeAndWriteText(int bufLimit, int trailingBrackets) {
             int index = 0;
             for (int i = 0; i < bufLimit; i++) {
                 char c = _buf[i];
@@ -2122,15 +2123,33 @@
                         emit(_buf, index, i - index);
                         emit("&lt;");
                         index = i + 1;
+                        trailingBrackets = 0;
                         break;
                     case '&':
                         emit(_buf, index, i - index);
                         emit("&amp;");
                         index = i + 1;
+                        trailingBrackets = 0;
+                        break;
+                    case '>':
+                        // ']]>' is not allowed in content, so escape the '>' that closes it
+                        if (trailingBrackets >= 2) {
+                            emit(_buf, index, i - index);
+                            emit("&gt;");
+                            index = i + 1;
+                        }
+                        trailingBrackets = 0;
+                        break;
+                    case ']':
+                        trailingBrackets++;
+                        break;
+                    default:
+                        trailingBrackets = 0;
                         break;
                 }
             }
             emit(_buf, index, bufLimit - index);
+            return trailingBrackets;
         }
 
         private void entitizeAndWriteCommentText(int bufLimit) {
diff --git a/src/test/java/misc/checkin/SaveOptimizeForSpeedTest.java b/src/test/java/misc/checkin/SaveOptimizeForSpeedTest.java
index 7005ce7..2cb5d7f 100644
--- a/src/test/java/misc/checkin/SaveOptimizeForSpeedTest.java
+++ b/src/test/java/misc/checkin/SaveOptimizeForSpeedTest.java
@@ -67,6 +67,38 @@
     }
 
     @Test
+    void testCDataEndInText() throws Exception {
+        XmlObject o = XmlObject.Factory.parse("<root/>");
+        try (XmlCursor cur = o.newCursor()) {
+            cur.toFirstChild();
+            cur.toFirstContentToken();
+            cur.insertChars("a]]>b");
+        }
+        String out = saveForSpeed(o);
+        // ']]>' is forbidden in element content and must be escaped to ']]&gt;'
+        assertFalse(out.contains("]]>"));
+        // before the fix the literal ']]>' makes this a fatal parse error
+        XmlObject.Factory.parse(out);
+    }
+
+    @Test
+    void testCDataEndAcrossChunkBoundary() throws Exception {
+        // a ']]>' that straddles the 512-char chunk boundary: ']]' end the first
+        // chunk, '>' is the first char of the second. The trailing-bracket state
+        // has to carry across chunks or the '>' is emitted unescaped.
+        String data = repeat('x', 510) + "]]>" + repeat('y', 600);
+        XmlObject o = XmlObject.Factory.parse("<root/>");
+        try (XmlCursor cur = o.newCursor()) {
+            cur.toFirstChild();
+            cur.toFirstContentToken();
+            cur.insertChars(data);
+        }
+        String out = saveForSpeed(o);
+        assertFalse(out.contains("]]>"));
+        XmlObject.Factory.parse(out);
+    }
+
+    @Test
     void testProcInstTerminatorAcrossChunkBoundary() throws Exception {
         // a '?>' that straddles the 512-char chunk boundary: '?' is the last char
         // of the first chunk, '>' the first char of the second. The per-chunk