You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ay...@apache.org on 2014/09/11 10:53:35 UTC

git commit: CAMEL-7801 XMLTokenizer's wrapped mode to handle grouping without replicating the wrapper part

Repository: camel
Updated Branches:
  refs/heads/master a5dbf6688 -> 2c01b8e84


CAMEL-7801 XMLTokenizer's wrapped mode to handle grouping without replicating the wrapper part


Project: http://git-wip-us.apache.org/repos/asf/camel/repo
Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/2c01b8e8
Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/2c01b8e8
Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/2c01b8e8

Branch: refs/heads/master
Commit: 2c01b8e843e4a3d2eecce452470fba02a46ca790
Parents: a5dbf66
Author: Akitoshi Yoshida <ay...@apache.org>
Authored: Wed Sep 10 18:09:48 2014 +0200
Committer: Akitoshi Yoshida <ay...@apache.org>
Committed: Thu Sep 11 10:52:37 2014 +0200

----------------------------------------------------------------------
 .../apache/camel/builder/ExpressionBuilder.java |   6 +
 .../language/tokenizer/XMLTokenizeLanguage.java |   7 -
 .../model/language/XMLTokenizerExpression.java  |  14 +-
 .../support/XMLTokenExpressionIterator.java     |  95 ++++-
 .../XMLTokenizeLanguageGroupingTest.java        | 141 +++++++
 .../XMLTokenizeWrapLanguageGroupingTest.java    | 148 +++++++
 .../processor/SplitGroupMultiXmlTokenTest.java  |   6 +-
 .../SplitGroupWrappedMultiXmlTokenTest.java     |  81 ++++
 .../XMLTokenExpressionIteratorGroupingTest.java | 415 +++++++++++++++++++
 9 files changed, 887 insertions(+), 26 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/camel/blob/2c01b8e8/camel-core/src/main/java/org/apache/camel/builder/ExpressionBuilder.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/builder/ExpressionBuilder.java b/camel-core/src/main/java/org/apache/camel/builder/ExpressionBuilder.java
index dfb4b99..4473fe7 100644
--- a/camel-core/src/main/java/org/apache/camel/builder/ExpressionBuilder.java
+++ b/camel-core/src/main/java/org/apache/camel/builder/ExpressionBuilder.java
@@ -1259,6 +1259,12 @@ public final class ExpressionBuilder {
 
         return new XMLTokenExpressionIterator(path, mode);
     }
+    
+    public static Expression tokenizeXMLAwareExpression(String path, char mode, int group) {
+        ObjectHelper.notEmpty(path, "path");
+
+        return new XMLTokenExpressionIterator(path, mode, group);
+    }
 
     /**
      * Returns a tokenize expression which will tokenize the string with the

http://git-wip-us.apache.org/repos/asf/camel/blob/2c01b8e8/camel-core/src/main/java/org/apache/camel/language/tokenizer/XMLTokenizeLanguage.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/language/tokenizer/XMLTokenizeLanguage.java b/camel-core/src/main/java/org/apache/camel/language/tokenizer/XMLTokenizeLanguage.java
index 2b38128..b42357f 100644
--- a/camel-core/src/main/java/org/apache/camel/language/tokenizer/XMLTokenizeLanguage.java
+++ b/camel-core/src/main/java/org/apache/camel/language/tokenizer/XMLTokenizeLanguage.java
@@ -74,13 +74,6 @@ public class XMLTokenizeLanguage extends LanguageSupport {
         ObjectHelper.notNull(path, "token");
 
         Expression answer = ExpressionBuilder.tokenizeXMLAwareExpression(path, mode);
-
-        // if group then wrap answer in group expression
-        if (group > 0) {
-            //REVISIT wrap the xml tokens with a group element to turn the result into xml?
-            answer = ExpressionBuilder.groupIteratorExpression(answer, null, group);
-        }
-
         return answer;
     }
 

http://git-wip-us.apache.org/repos/asf/camel/blob/2c01b8e8/camel-core/src/main/java/org/apache/camel/model/language/XMLTokenizerExpression.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/model/language/XMLTokenizerExpression.java b/camel-core/src/main/java/org/apache/camel/model/language/XMLTokenizerExpression.java
index 8e96044..323024a 100644
--- a/camel-core/src/main/java/org/apache/camel/model/language/XMLTokenizerExpression.java
+++ b/camel-core/src/main/java/org/apache/camel/model/language/XMLTokenizerExpression.java
@@ -87,6 +87,9 @@ public class XMLTokenizerExpression extends NamespaceAwareExpression {
         if (mode != null) {
             setProperty(expression, "mode", mode);
         }
+        if (group != null) {
+            setProperty(expression, "group", group);
+        }
     }
 
     @Override
@@ -98,17 +101,14 @@ public class XMLTokenizerExpression extends NamespaceAwareExpression {
         if (mode != null) {
             setProperty(predicate, "mode", mode);
         }
+        if (group != null) {
+            setProperty(predicate, "group", group);
+        }
     }
 
     @Override
     public Expression createExpression(CamelContext camelContext) {
-        Expression answer = super.createExpression(camelContext); 
-        if (group != null) {
-            if (group > 0) {
-                //REVISIT wrap the xml tokens with a group element to turn the result into xml?
-                answer = ExpressionBuilder.groupIteratorExpression(answer, null, group);
-            }
-        }
+        Expression answer = super.createExpression(camelContext);
         return answer;
     }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/camel/blob/2c01b8e8/camel-core/src/main/java/org/apache/camel/support/XMLTokenExpressionIterator.java
----------------------------------------------------------------------
diff --git a/camel-core/src/main/java/org/apache/camel/support/XMLTokenExpressionIterator.java b/camel-core/src/main/java/org/apache/camel/support/XMLTokenExpressionIterator.java
index d48a2d4..f233281 100644
--- a/camel-core/src/main/java/org/apache/camel/support/XMLTokenExpressionIterator.java
+++ b/camel-core/src/main/java/org/apache/camel/support/XMLTokenExpressionIterator.java
@@ -53,12 +53,18 @@ import org.slf4j.LoggerFactory;
 public class XMLTokenExpressionIterator extends ExpressionAdapter implements NamespaceAware {
     protected final String path;
     protected char mode;
+    protected int group;
     protected Map<String, String> nsmap;
 
     public XMLTokenExpressionIterator(String path, char mode) {
+        this(path, mode, 1);
+    }
+
+    public XMLTokenExpressionIterator(String path, char mode, int group) {
         ObjectHelper.notEmpty(path, "path");
         this.path = path;
         this.mode = mode;
+        this.group = group > 1 ? group : 1;
     }
 
     @Override
@@ -74,6 +80,14 @@ public class XMLTokenExpressionIterator extends ExpressionAdapter implements Nam
         this.mode = mode != null ? mode.charAt(0) : 0;
     }
     
+    public int getGroup() {
+        return group;
+    }
+
+    public void setGroup(int group) {
+        this.group = group;
+    }
+
     protected Iterator<?> createIterator(InputStream in, String charset) throws XMLStreamException, UnsupportedEncodingException {
         Reader reader;
         if (charset == null) {
@@ -81,12 +95,12 @@ public class XMLTokenExpressionIterator extends ExpressionAdapter implements Nam
         } else {
             reader = new InputStreamReader(in, charset);
         }
-        XMLTokenIterator iterator = new XMLTokenIterator(path, nsmap, mode, reader);
+        XMLTokenIterator iterator = new XMLTokenIterator(path, nsmap, mode, group, reader);
         return iterator;
     }
 
     protected Iterator<?> createIterator(Reader in) throws XMLStreamException {
-        XMLTokenIterator iterator = new XMLTokenIterator(path, nsmap, mode, in);
+        XMLTokenIterator iterator = new XMLTokenIterator(path, nsmap, mode, group, in);
         return iterator;
     }
 
@@ -147,12 +161,14 @@ public class XMLTokenExpressionIterator extends ExpressionAdapter implements Nam
         private AttributedQName[] splitpath;
         private int index;
         private char mode;
+        private int group;
         private RecordableReader in;
         private XMLStreamReader reader;
         private List<QName> path;
         private List<Map<String, String>> namespaces;
         private List<String> segments;
         private List<QName> segmentlog;
+        private List<String> tokens;
         private int code;
         private int consumed;
         private boolean backtrack;
@@ -164,10 +180,20 @@ public class XMLTokenExpressionIterator extends ExpressionAdapter implements Nam
         public XMLTokenIterator(String path, Map<String, String> nsmap, char mode, InputStream in, String charset) 
             throws XMLStreamException, UnsupportedEncodingException {
             // woodstox's getLocation().etCharOffset() does not return the offset correctly for InputStream, so use Reader instead.
+            this(path, nsmap, mode, 1, new InputStreamReader(in, charset));
+        }
+
+        public XMLTokenIterator(String path, Map<String, String> nsmap, char mode, int group, InputStream in, String charset) 
+            throws XMLStreamException, UnsupportedEncodingException {
+            // woodstox's getLocation().etCharOffset() does not return the offset correctly for InputStream, so use Reader instead.
             this(path, nsmap, mode, new InputStreamReader(in, charset));
         }
 
         public XMLTokenIterator(String path, Map<String, String> nsmap, char mode, Reader in) throws XMLStreamException {
+            this(path, nsmap, mode, 1, in);
+        }
+
+        public XMLTokenIterator(String path, Map<String, String> nsmap, char mode, int group, Reader in) throws XMLStreamException {
             final String[] sl = path.substring(1).split("/");
             this.splitpath = new AttributedQName[sl.length];
             for (int i = 0; i < sl.length; i++) {
@@ -182,6 +208,7 @@ public class XMLTokenExpressionIterator extends ExpressionAdapter implements Nam
             }
             
             this.mode = mode != 0 ? mode : 'i';
+            this.group = group > 0 ? group : 1;
             this.in = new RecordableReader(in);
             this.reader = new StaxConverter().createXMLStreamReader(this.in);
 
@@ -201,7 +228,10 @@ public class XMLTokenExpressionIterator extends ExpressionAdapter implements Nam
             } else if (this.mode == 'i') {
                 this.namespaces = new ArrayList<Map<String, String>>();
             }
-                        
+            // when grouping the tokens, allocate the storage to temporarily store tokens. 
+            if (this.group > 1) {
+                this.tokens = new ArrayList<String>();
+            }       
             this.nextToken = getNextToken();
 
         }
@@ -336,7 +366,7 @@ public class XMLTokenExpressionIterator extends ExpressionAdapter implements Nam
 
         private String createContextualToken(String token) {
             StringBuilder sb = new StringBuilder();
-            if (mode == 'w') {
+            if (mode == 'w' && group == 1) {
                 for (int i = 0; i < segments.size(); i++) {
                     sb.append(segments.get(i));
                 }
@@ -389,17 +419,45 @@ public class XMLTokenExpressionIterator extends ExpressionAdapter implements Nam
                     }
                     sb.append(token.substring(ep + 1, bp));
                 }
+            } else {
+                return token;
             }
 
             return sb.toString();
         }
 
+        private String getGroupedToken() {
+            StringBuilder sb = new StringBuilder();
+            if (mode == 'w') {
+                 // for wrapped
+                for (int i = 0; i < segments.size(); i++) {
+                    sb.append(segments.get(i));
+                }
+                for (String s : tokens) {
+                    sb.append(s);
+                }
+                for (int i = path.size() - 1; i >= 0; i--) {
+                    QName q = path.get(i);
+                    sb.append("</").append(makeName(q)).append(">");
+                }
+            } else {
+                // for injected, unwrapped, text
+                sb.append("<group>");
+                for (String s : tokens) {
+                    sb.append(s);
+                }
+                sb.append("</group>");
+            }
+            tokens.clear();
+            return sb.toString();
+        }
+        
         private String getNextToken() throws XMLStreamException {
-            int code = 0;
-            while (code != XMLStreamConstants.END_DOCUMENT) {
-                code = readNext();
+            int xcode = 0;
+            while (xcode != XMLStreamConstants.END_DOCUMENT) {
+                xcode = readNext();
 
-                switch (code) {
+                switch (xcode) {
                 case XMLStreamConstants.START_ELEMENT:
                     depth++;
                     QName name = reader.getName();
@@ -424,7 +482,14 @@ public class XMLTokenExpressionIterator extends ExpressionAdapter implements Nam
                             token = getCurrentToken();
                             backtrack = true;
                             trackdepth = depth;
-                            return token;
+                            if (group > 1) {
+                                tokens.add(token);
+                                if (group == tokens.size()) {
+                                    return getGroupedToken();
+                                }
+                            } else {
+                                return token;    
+                            }
                         } else {
                             // intermediary match
                             down();
@@ -437,6 +502,13 @@ public class XMLTokenExpressionIterator extends ExpressionAdapter implements Nam
                     }
                     break;
                 case XMLStreamConstants.END_ELEMENT:
+                    if ((backtrack || (trackdepth > 0 && depth == trackdepth))
+                        && (mode == 'w' && group > 1 && tokens.size() > 0)) {
+                        // flush the left over using the current context
+                        code = XMLStreamConstants.END_ELEMENT;
+                        return getGroupedToken();
+                    }
+
                     depth--;
                     QName endname = reader.getName();
                     LOG.trace("ee={}", endname);
@@ -474,6 +546,11 @@ public class XMLTokenExpressionIterator extends ExpressionAdapter implements Nam
                     break;
                 case XMLStreamConstants.END_DOCUMENT:
                     LOG.trace("depth={}", depth);
+                    if (group > 1 && tokens.size() > 0) {
+                        // flush the left over before really going EoD
+                        code = XMLStreamConstants.END_DOCUMENT;
+                        return getGroupedToken();
+                    }
                     break;
                 default:
                     break;

http://git-wip-us.apache.org/repos/asf/camel/blob/2c01b8e8/camel-core/src/test/java/org/apache/camel/language/tokenizer/XMLTokenizeLanguageGroupingTest.java
----------------------------------------------------------------------
diff --git a/camel-core/src/test/java/org/apache/camel/language/tokenizer/XMLTokenizeLanguageGroupingTest.java b/camel-core/src/test/java/org/apache/camel/language/tokenizer/XMLTokenizeLanguageGroupingTest.java
new file mode 100644
index 0000000..79fd0df
--- /dev/null
+++ b/camel-core/src/test/java/org/apache/camel/language/tokenizer/XMLTokenizeLanguageGroupingTest.java
@@ -0,0 +1,141 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.language.tokenizer;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.builder.xml.Namespaces;
+import org.apache.camel.component.mock.MockEndpoint;
+
+public class XMLTokenizeLanguageGroupingTest extends ContextTestSupport {
+
+    public void testSendClosedTagMessageToTokenize() throws Exception {
+        getMockEndpoint("mock:result").expectedBodiesReceived("<group><c:child some_attr='a' anotherAttr='a' xmlns:c=\"urn:c\"></c:child>"
+                                                              + "<c:child some_attr='b' anotherAttr='b' xmlns:c=\"urn:c\"></c:child></group>");
+
+        template.sendBody("direct:start",
+            "<?xml version='1.0' encoding='UTF-8'?><c:parent xmlns:c='urn:c'><c:child some_attr='a' anotherAttr='a'></c:child><c:child some_attr='b' anotherAttr='b'></c:child></c:parent>");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    public void testSendClosedTagWithLineBreaksMessageToTokenize() throws Exception {
+        getMockEndpoint("mock:result").expectedBodiesReceived("<group><c:child some_attr='a' anotherAttr='a' xmlns:c=\"urn:c\">\n</c:child>"
+                                                              + "<c:child some_attr='b' anotherAttr='b' xmlns:c=\"urn:c\">\n</c:child></group>");
+
+        template.sendBody("direct:start",
+            "<?xml version='1.0' encoding='UTF-8'?>\n"
+                + "<c:parent xmlns:c='urn:c'>\n"
+                + "<c:child some_attr='a' anotherAttr='a'>\n"
+                + "</c:child>\n"
+                + "<c:child some_attr='b' anotherAttr='b'>\n"
+                + "</c:child>\n"
+                + "</c:parent>");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    public void testSendSelfClosingTagMessageToTokenize() throws Exception {
+        getMockEndpoint("mock:result").expectedBodiesReceived("<group><c:child some_attr='a' anotherAttr='a'  xmlns:c=\"urn:c\"/>"
+                                                              + "<c:child some_attr='b' anotherAttr='b'  xmlns:c=\"urn:c\"/></group>");
+
+        template.sendBody("direct:start",
+            "<?xml version='1.0' encoding='UTF-8'?><c:parent xmlns:c='urn:c'><c:child some_attr='a' anotherAttr='a' /><c:child some_attr='b' anotherAttr='b' /></c:parent>");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    public void testSendMixedClosingTagMessageToTokenize() throws Exception {
+        getMockEndpoint("mock:result").expectedBodiesReceived(
+            "<group><c:child some_attr='a' anotherAttr='a' xmlns:c=\"urn:c\">ha</c:child>"
+            + "<c:child some_attr='b' anotherAttr='b'  xmlns:c=\"urn:c\"/></group>", 
+            "<group><c:child some_attr='c' xmlns:c=\"urn:c\"></c:child></group>");
+
+        template.sendBody("direct:start",
+            "<?xml version='1.0' encoding='UTF-8'?><c:parent xmlns:c='urn:c'><c:child some_attr='a' anotherAttr='a'>ha</c:child>"
+            + "<c:child some_attr='b' anotherAttr='b' /><c:child some_attr='c'></c:child></c:parent>");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    public void testSendMixedClosingTagInsideMessageToTokenize() throws Exception {
+        getMockEndpoint("mock:result").expectedBodiesReceived(
+            "<group><c:child name='child1' xmlns:c=\"urn:c\"><grandchild name='grandchild1'/> <grandchild name='grandchild2'/></c:child>"
+            + "<c:child name='child2' xmlns:c=\"urn:c\"><grandchild name='grandchild1'></grandchild><grandchild name='grandchild2'></grandchild></c:child></group>");
+
+        template.sendBody("direct:start",
+            "<c:parent xmlns:c='urn:c'><c:child name='child1'><grandchild name='grandchild1'/> <grandchild name='grandchild2'/></c:child>"
+            + "<c:child name='child2'><grandchild name='grandchild1'></grandchild><grandchild name='grandchild2'></grandchild></c:child></c:parent>");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    public void testSendNamespacedChildMessageToTokenize() throws Exception {
+        getMockEndpoint("mock:result").expectedBodiesReceived(
+            "<group><c:child xmlns:c='urn:c' some_attr='a' anotherAttr='a'></c:child><c:child xmlns:c='urn:c' some_attr='b' anotherAttr='b' /></group>");
+
+        template.sendBody("direct:start",
+            "<?xml version='1.0' encoding='UTF-8'?><c:parent xmlns:c='urn:c'><c:child xmlns:c='urn:c' some_attr='a' anotherAttr='a'></c:child>"
+            + "<c:child xmlns:c='urn:c' some_attr='b' anotherAttr='b' /></c:parent>");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    public void testSendNamespacedParentMessageToTokenize() throws Exception {
+        getMockEndpoint("mock:result").expectedBodiesReceived(
+            "<group><c:child some_attr='a' anotherAttr='a' xmlns:d=\"urn:d\" xmlns:c=\"urn:c\"></c:child>"
+            + "<c:child some_attr='b' anotherAttr='b' xmlns:d=\"urn:d\" xmlns:c=\"urn:c\"/></group>");
+        
+        template.sendBody("direct:start",
+            "<?xml version='1.0' encoding='UTF-8'?><c:parent xmlns:c='urn:c' xmlns:d=\"urn:d\"><c:child some_attr='a' anotherAttr='a'></c:child>"
+            + "<c:child some_attr='b' anotherAttr='b'/></c:parent>");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    public void testSendMoreParentsMessageToTokenize() throws Exception {
+        MockEndpoint result = getMockEndpoint("mock:result");
+        if (isJavaVersion("1.8"))  {
+            result.expectedBodiesReceived(
+                "<group><c:child some_attr='a' anotherAttr='a' xmlns:c=\"urn:c\" xmlns:d=\"urn:d\" xmlns:g=\"urn:g\"></c:child>"
+                + "<c:child some_attr='b' anotherAttr='b' xmlns:c=\"urn:c\" xmlns:d=\"urn:d\" xmlns:g=\"urn:g\"/></group>");
+        } else {
+            result.expectedBodiesReceived(
+                "<group><c:child some_attr='a' anotherAttr='a' xmlns:g=\"urn:g\" xmlns:d=\"urn:d\" xmlns:c=\"urn:c\"></c:child>"
+                + "<c:child some_attr='b' anotherAttr='b' xmlns:g=\"urn:g\" xmlns:d=\"urn:d\" xmlns:c=\"urn:c\"/></group>");
+        }
+
+        template.sendBody("direct:start",
+            "<?xml version='1.0' encoding='UTF-8'?><g:greatgrandparent xmlns:g='urn:g'><grandparent><uncle/><aunt>emma</aunt><c:parent xmlns:c='urn:c' xmlns:d=\"urn:d\">"
+            + "<c:child some_attr='a' anotherAttr='a'></c:child><c:child some_attr='b' anotherAttr='b'/></c:parent></grandparent></g:greatgrandparent>");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            Namespaces ns = new Namespaces("C", "urn:c");
+            public void configure() {
+                from("direct:start")
+                    .split().xtokenize("//C:child", 'i', ns, 2)
+                        .to("mock:result")
+                    .end();
+            }
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/2c01b8e8/camel-core/src/test/java/org/apache/camel/language/tokenizer/XMLTokenizeWrapLanguageGroupingTest.java
----------------------------------------------------------------------
diff --git a/camel-core/src/test/java/org/apache/camel/language/tokenizer/XMLTokenizeWrapLanguageGroupingTest.java b/camel-core/src/test/java/org/apache/camel/language/tokenizer/XMLTokenizeWrapLanguageGroupingTest.java
new file mode 100644
index 0000000..728a70f
--- /dev/null
+++ b/camel-core/src/test/java/org/apache/camel/language/tokenizer/XMLTokenizeWrapLanguageGroupingTest.java
@@ -0,0 +1,148 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.language.tokenizer;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.builder.xml.Namespaces;
+
+public class XMLTokenizeWrapLanguageGroupingTest extends ContextTestSupport {
+
+    public void testSendClosedTagMessageToTokenize() throws Exception {
+        getMockEndpoint("mock:result").expectedBodiesReceived("<?xml version='1.0' encoding='UTF-8'?><c:parent xmlns:c='urn:c'><c:child some_attr='a' anotherAttr='a'></c:child>"
+                                                              + "<c:child some_attr='b' anotherAttr='b'></c:child></c:parent>");
+
+        template.sendBody("direct:start",
+            "<?xml version='1.0' encoding='UTF-8'?><c:parent xmlns:c='urn:c'><c:child some_attr='a' anotherAttr='a'></c:child><c:child some_attr='b' anotherAttr='b'></c:child></c:parent>");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    public void testSendClosedTagWithLineBreaksMessageToTokenize() throws Exception {
+        getMockEndpoint("mock:result").expectedBodiesReceived("<?xml version='1.0' encoding='UTF-8'?>\n<c:parent xmlns:c='urn:c'>\n<c:child some_attr='a' anotherAttr='a'>\n</c:child>" 
+                                                              + "<c:child some_attr='b' anotherAttr='b'>\n</c:child></c:parent>");
+
+        template.sendBody("direct:start",
+            "<?xml version='1.0' encoding='UTF-8'?>\n"
+                + "<c:parent xmlns:c='urn:c'>\n"
+                + "<c:child some_attr='a' anotherAttr='a'>\n"
+                + "</c:child>\n"
+                + "<c:child some_attr='b' anotherAttr='b'>\n"
+                + "</c:child>\n"
+                + "</c:parent>");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    public void testSendSelfClosingTagMessageToTokenize() throws Exception {
+        getMockEndpoint("mock:result").expectedBodiesReceived("<?xml version='1.0' encoding='UTF-8'?><c:parent xmlns:c='urn:c'><c:child some_attr='a' anotherAttr='a' />" 
+                                                              + "<c:child some_attr='b' anotherAttr='b' /></c:parent>");
+
+        template.sendBody("direct:start",
+            "<?xml version='1.0' encoding='UTF-8'?><c:parent xmlns:c='urn:c'><c:child some_attr='a' anotherAttr='a' /><c:child some_attr='b' anotherAttr='b' /></c:parent>");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    public void testSendMixedClosingTagMessageToTokenize() throws Exception {
+        getMockEndpoint("mock:result").expectedBodiesReceived(
+            "<?xml version='1.0' encoding='UTF-8'?><c:parent xmlns:c='urn:c'><c:child some_attr='a' anotherAttr='a'>ha</c:child>"
+            + "<c:child some_attr='b' anotherAttr='b' /></c:parent>", 
+            "<?xml version='1.0' encoding='UTF-8'?><c:parent xmlns:c='urn:c'><c:child some_attr='c'></c:child></c:parent>");
+
+        template.sendBody("direct:start",
+            "<?xml version='1.0' encoding='UTF-8'?><c:parent xmlns:c='urn:c'><c:child some_attr='a' anotherAttr='a'>ha</c:child>"
+            + "<c:child some_attr='b' anotherAttr='b' /><c:child some_attr='c'></c:child></c:parent>");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    public void testSendMixedClosingTagInsideMessageToTokenize() throws Exception {
+        getMockEndpoint("mock:result").expectedBodiesReceived(
+            "<c:parent xmlns:c='urn:c'><c:child name='child1'><grandchild name='grandchild1'/> <grandchild name='grandchild2'/></c:child>"
+            + "<c:child name='child2'><grandchild name='grandchild1'></grandchild><grandchild name='grandchild2'></grandchild></c:child></c:parent>");
+
+        template.sendBody("direct:start",
+            "<c:parent xmlns:c='urn:c'><c:child name='child1'><grandchild name='grandchild1'/> <grandchild name='grandchild2'/></c:child>"
+            + "<c:child name='child2'><grandchild name='grandchild1'></grandchild><grandchild name='grandchild2'></grandchild></c:child></c:parent>");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    public void testSendNamespacedChildMessageToTokenize() throws Exception {
+        getMockEndpoint("mock:result").expectedBodiesReceived(
+            "<?xml version='1.0' encoding='UTF-8'?><c:parent xmlns:c='urn:c'><c:child xmlns:c='urn:c' some_attr='a' anotherAttr='a'></c:child>" 
+            + "<c:child xmlns:c='urn:c' some_attr='b' anotherAttr='b' /></c:parent>");
+
+        template.sendBody("direct:start",
+            "<?xml version='1.0' encoding='UTF-8'?><c:parent xmlns:c='urn:c'><c:child xmlns:c='urn:c' some_attr='a' anotherAttr='a'></c:child>"
+            + "<c:child xmlns:c='urn:c' some_attr='b' anotherAttr='b' /></c:parent>");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    public void testSendNamespacedParentMessageToTokenize() throws Exception {
+        getMockEndpoint("mock:result").expectedBodiesReceived(
+            "<?xml version='1.0' encoding='UTF-8'?><c:parent xmlns:c='urn:c' xmlns:d=\"urn:d\"><c:child some_attr='a' anotherAttr='a'></c:child>" 
+            + "<c:child some_attr='b' anotherAttr='b'/></c:parent>");
+        
+        template.sendBody("direct:start",
+            "<?xml version='1.0' encoding='UTF-8'?><c:parent xmlns:c='urn:c' xmlns:d=\"urn:d\"><c:child some_attr='a' anotherAttr='a'></c:child><c:child some_attr='b' anotherAttr='b'/></c:parent>");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    public void testSendMoreParentsMessageToTokenize() throws Exception {
+        getMockEndpoint("mock:result").expectedBodiesReceived(
+            "<?xml version='1.0' encoding='UTF-8'?><g:greatgrandparent xmlns:g='urn:g'><grandparent><uncle/><aunt>emma</aunt><c:parent xmlns:c='urn:c' xmlns:d=\"urn:d\">"
+            + "<c:child some_attr='a' anotherAttr='a'></c:child>"
+            + "<c:child some_attr='b' anotherAttr='b'/></c:parent></grandparent></g:greatgrandparent>");
+
+        template.sendBody("direct:start",
+            "<?xml version='1.0' encoding='UTF-8'?><g:greatgrandparent xmlns:g='urn:g'><grandparent><uncle/><aunt>emma</aunt><c:parent xmlns:c='urn:c' xmlns:d=\"urn:d\">"
+            + "<c:child some_attr='a' anotherAttr='a'></c:child><c:child some_attr='b' anotherAttr='b'/></c:parent></grandparent></g:greatgrandparent>");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    public void testSendParentMessagesWithDifferentAttributesToTokenize() throws Exception {
+        getMockEndpoint("mock:result").expectedBodiesReceived(
+            "<?xml version='1.0' encoding='UTF-8'?><g:grandparent xmlns:g='urn:g'><c:parent name='e' xmlns:c='urn:c' xmlns:d=\"urn:d\">"
+            + "<c:child some_attr='a' anotherAttr='a'></c:child></c:parent></g:grandparent>",
+            "<?xml version='1.0' encoding='UTF-8'?><g:grandparent xmlns:g='urn:g'><c:parent name='f' xmlns:c='urn:c' xmlns:d=\"urn:d\">"
+            + "<c:child some_attr='b' anotherAttr='b'/></c:parent></g:grandparent>");
+
+        template.sendBody("direct:start",
+            "<?xml version='1.0' encoding='UTF-8'?><g:grandparent xmlns:g='urn:g'><c:parent name='e' xmlns:c='urn:c' xmlns:d=\"urn:d\">"
+            + "<c:child some_attr='a' anotherAttr='a'></c:child></c:parent><c:parent name='f' xmlns:c='urn:c' xmlns:d=\"urn:d\"><c:child some_attr='b' anotherAttr='b'/>"
+            + "</c:parent></g:grandparent>");
+        assertMockEndpointsSatisfied();
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            Namespaces ns = new Namespaces("C", "urn:c");
+            public void configure() {
+                from("direct:start")
+                    .split().xtokenize("//C:child", 'w', ns, 2)
+                        .to("mock:result")
+                    .end();
+            }
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/2c01b8e8/camel-core/src/test/java/org/apache/camel/processor/SplitGroupMultiXmlTokenTest.java
----------------------------------------------------------------------
diff --git a/camel-core/src/test/java/org/apache/camel/processor/SplitGroupMultiXmlTokenTest.java b/camel-core/src/test/java/org/apache/camel/processor/SplitGroupMultiXmlTokenTest.java
index 86f4201..67a1f90 100644
--- a/camel-core/src/test/java/org/apache/camel/processor/SplitGroupMultiXmlTokenTest.java
+++ b/camel-core/src/test/java/org/apache/camel/processor/SplitGroupMultiXmlTokenTest.java
@@ -36,9 +36,9 @@ public class SplitGroupMultiXmlTokenTest extends ContextTestSupport {
     public void testTokenXMLPairGroup() throws Exception {
         MockEndpoint mock = getMockEndpoint("mock:split");
         mock.expectedMessageCount(3);
-        mock.message(0).body().isEqualTo("<order id=\"1\" xmlns=\"http:acme.com\">Camel in Action</order><order id=\"2\" xmlns=\"http:acme.com\">ActiveMQ in Action</order>");
-        mock.message(1).body().isEqualTo("<order id=\"3\" xmlns=\"http:acme.com\">Spring in Action</order><order id=\"4\" xmlns=\"http:acme.com\">Scala in Action</order>");
-        mock.message(2).body().isEqualTo("<order id=\"5\" xmlns=\"http:acme.com\">Groovy in Action</order>");
+        mock.message(0).body().isEqualTo("<group><order id=\"1\" xmlns=\"http:acme.com\">Camel in Action</order><order id=\"2\" xmlns=\"http:acme.com\">ActiveMQ in Action</order></group>");
+        mock.message(1).body().isEqualTo("<group><order id=\"3\" xmlns=\"http:acme.com\">Spring in Action</order><order id=\"4\" xmlns=\"http:acme.com\">Scala in Action</order></group>");
+        mock.message(2).body().isEqualTo("<group><order id=\"5\" xmlns=\"http:acme.com\">Groovy in Action</order></group>");
 
         String body = createBody();
         template.sendBodyAndHeader("file:target/pair", body, Exchange.FILE_NAME, "orders.xml");

http://git-wip-us.apache.org/repos/asf/camel/blob/2c01b8e8/camel-core/src/test/java/org/apache/camel/processor/SplitGroupWrappedMultiXmlTokenTest.java
----------------------------------------------------------------------
diff --git a/camel-core/src/test/java/org/apache/camel/processor/SplitGroupWrappedMultiXmlTokenTest.java b/camel-core/src/test/java/org/apache/camel/processor/SplitGroupWrappedMultiXmlTokenTest.java
new file mode 100644
index 0000000..4c1fa94
--- /dev/null
+++ b/camel-core/src/test/java/org/apache/camel/processor/SplitGroupWrappedMultiXmlTokenTest.java
@@ -0,0 +1,81 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.processor;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.Exchange;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.builder.xml.Namespaces;
+import org.apache.camel.component.mock.MockEndpoint;
+
+/**
+ *
+ */
+public class SplitGroupWrappedMultiXmlTokenTest extends ContextTestSupport {
+
+    @Override
+    protected void setUp() throws Exception {
+        deleteDirectory("target/pair");
+        super.setUp();
+    }
+
+    public void testTokenXMLPairGroup() throws Exception {
+        MockEndpoint mock = getMockEndpoint("mock:split");
+        mock.expectedMessageCount(3);
+        mock.message(0).body().isEqualTo(
+            "<?xml version=\"1.0\"?>\n<orders xmlns=\"http:acme.com\">\n  <order id=\"1\">Camel in Action</order><order id=\"2\">ActiveMQ in Action</order></orders>");
+        mock.message(1).body().isEqualTo(
+            "<?xml version=\"1.0\"?>\n<orders xmlns=\"http:acme.com\">\n  <order id=\"3\">Spring in Action</order><order id=\"4\">Scala in Action</order></orders>");
+        mock.message(2).body().isEqualTo(
+            "<?xml version=\"1.0\"?>\n<orders xmlns=\"http:acme.com\">\n  <order id=\"5\">Groovy in Action</order></orders>");
+
+        String body = createBody();
+        template.sendBodyAndHeader("file:target/pair", body, Exchange.FILE_NAME, "orders.xml");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    protected String createBody() {
+        StringBuilder sb = new StringBuilder("<?xml version=\"1.0\"?>\n");
+        sb.append("<orders xmlns=\"http:acme.com\">\n");
+        sb.append("  <order id=\"1\">Camel in Action</order>\n");
+        sb.append("  <order id=\"2\">ActiveMQ in Action</order>\n");
+        sb.append("  <order id=\"3\">Spring in Action</order>\n");
+        sb.append("  <order id=\"4\">Scala in Action</order>\n");
+        sb.append("  <order id=\"5\">Groovy in Action</order>\n");
+        sb.append("</orders>");
+        return sb.toString();
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+            Namespaces ns = new Namespaces("", "http:acme.com");
+            @Override
+            public void configure() throws Exception {
+                // START SNIPPET: e1
+                from("file:target/pair")
+                        // split the order child tags, and inherit namespaces from the orders root tag
+                        .split().xtokenize("//order", 'w', ns, 2)
+                        .to("log:split")
+                        .to("mock:split");
+                // END SNIPPET: e1
+            }
+        };
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/2c01b8e8/camel-core/src/test/java/org/apache/camel/support/XMLTokenExpressionIteratorGroupingTest.java
----------------------------------------------------------------------
diff --git a/camel-core/src/test/java/org/apache/camel/support/XMLTokenExpressionIteratorGroupingTest.java b/camel-core/src/test/java/org/apache/camel/support/XMLTokenExpressionIteratorGroupingTest.java
new file mode 100644
index 0000000..20f5d45
--- /dev/null
+++ b/camel-core/src/test/java/org/apache/camel/support/XMLTokenExpressionIteratorGroupingTest.java
@@ -0,0 +1,415 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.camel.support;
+
+import java.io.ByteArrayInputStream;
+import java.io.Closeable;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+/**
+ * 
+ */
+public class XMLTokenExpressionIteratorGroupingTest extends TestCase {
+
+    private static final byte[] TEST_BODY = (
+        "<?xml version='1.0' encoding='UTF-8'?>"
+        + "<g:A xmlns:g='urn:g'>"
+        + "<c:B attr='1' xmlns:c='urn:c'>"
+        + "<c:C attr='1'>peach</c:C>"
+        + "<c:C attr='2'/>"
+        + "<c:C attr='3'>orange</c:C>"
+        + "<c:C attr='4'/>"
+        + "</c:B>"
+        + "<c:B attr='2' xmlns:c='urn:c'>"
+        + "<c:C attr='5'>mango</c:C>"
+        + "<c:C attr='6'/>"
+        + "<c:C attr='7'>pear</c:C>"
+        + "<c:C attr='8'/>"
+        + "</c:B>"
+        + "</g:A>").getBytes();
+
+    private static final String[] RESULTS_WRAPPED_SIZE1 = {
+        "<?xml version='1.0' encoding='UTF-8'?>"
+            + "<g:A xmlns:g='urn:g'>"
+            + "<c:B attr='1' xmlns:c='urn:c'>"
+            + "<c:C attr='1'>peach</c:C>"
+            + "</c:B>"
+            + "</g:A>",
+        "<?xml version='1.0' encoding='UTF-8'?>"
+            + "<g:A xmlns:g='urn:g'>"
+            + "<c:B attr='1' xmlns:c='urn:c'>"
+            + "<c:C attr='2'/>"
+            + "</c:B>"
+            + "</g:A>",
+        "<?xml version='1.0' encoding='UTF-8'?>"
+            + "<g:A xmlns:g='urn:g'>"
+            + "<c:B attr='1' xmlns:c='urn:c'>"
+            + "<c:C attr='3'>orange</c:C>"
+            + "</c:B>"
+            + "</g:A>",
+        "<?xml version='1.0' encoding='UTF-8'?>"
+            + "<g:A xmlns:g='urn:g'>"
+            + "<c:B attr='1' xmlns:c='urn:c'>"
+            + "<c:C attr='4'/>"
+            + "</c:B>"
+            + "</g:A>",
+        "<?xml version='1.0' encoding='UTF-8'?>"
+            + "<g:A xmlns:g='urn:g'>"
+            + "<c:B attr='2' xmlns:c='urn:c'>"
+            + "<c:C attr='5'>mango</c:C>"
+            + "</c:B>"
+            + "</g:A>",
+        "<?xml version='1.0' encoding='UTF-8'?>"
+            + "<g:A xmlns:g='urn:g'>"
+            + "<c:B attr='2' xmlns:c='urn:c'>"
+            + "<c:C attr='6'/>"
+            + "</c:B>"
+            + "</g:A>",
+        "<?xml version='1.0' encoding='UTF-8'?>"
+            + "<g:A xmlns:g='urn:g'>"
+            + "<c:B attr='2' xmlns:c='urn:c'>"
+            + "<c:C attr='7'>pear</c:C>"
+            + "</c:B>"
+            + "</g:A>",
+        "<?xml version='1.0' encoding='UTF-8'?>"
+            + "<g:A xmlns:g='urn:g'>"
+            + "<c:B attr='2' xmlns:c='urn:c'>"
+            + "<c:C attr='8'/>"
+            + "</c:B>"
+            + "</g:A>"};
+    
+    private static final String[] RESULTS_WRAPPED_SIZE2 = {
+        "<?xml version='1.0' encoding='UTF-8'?>"
+            + "<g:A xmlns:g='urn:g'>"
+            + "<c:B attr='1' xmlns:c='urn:c'>"
+            + "<c:C attr='1'>peach</c:C>"
+            + "<c:C attr='2'/>"
+            + "</c:B>"
+            + "</g:A>",
+        "<?xml version='1.0' encoding='UTF-8'?>"
+            + "<g:A xmlns:g='urn:g'>"
+            + "<c:B attr='1' xmlns:c='urn:c'>"
+            + "<c:C attr='3'>orange</c:C>"
+            + "<c:C attr='4'/>"
+            + "</c:B>"
+            + "</g:A>",
+        "<?xml version='1.0' encoding='UTF-8'?>"
+            + "<g:A xmlns:g='urn:g'>"
+            + "<c:B attr='2' xmlns:c='urn:c'>"
+            + "<c:C attr='5'>mango</c:C>"
+            + "<c:C attr='6'/>"
+            + "</c:B>"
+            + "</g:A>",
+        "<?xml version='1.0' encoding='UTF-8'?>"
+            + "<g:A xmlns:g='urn:g'>"
+            + "<c:B attr='2' xmlns:c='urn:c'>"
+            + "<c:C attr='7'>pear</c:C>"
+            + "<c:C attr='8'/>"
+            + "</c:B>"
+            + "</g:A>"};
+    
+    private static final String[] RESULTS_WRAPPED_SIZE3L = {
+        "<?xml version='1.0' encoding='UTF-8'?>"
+            + "<g:A xmlns:g='urn:g'>"
+            + "<c:B attr='1' xmlns:c='urn:c'>"
+            + "<c:C attr='1'>peach</c:C>"
+            + "<c:C attr='2'/>"
+            + "<c:C attr='3'>orange</c:C>"
+            + "</c:B>"
+            + "</g:A>",
+        "<?xml version='1.0' encoding='UTF-8'?>"
+            + "<g:A xmlns:g='urn:g'>"
+            + "<c:B attr='1' xmlns:c='urn:c'>"
+            + "<c:C attr='4'/>"
+            + "</c:B>"
+            + "</g:A>",
+        "<?xml version='1.0' encoding='UTF-8'?>"
+            + "<g:A xmlns:g='urn:g'>"
+            + "<c:B attr='2' xmlns:c='urn:c'>"
+            + "<c:C attr='5'>mango</c:C>"
+            + "<c:C attr='6'/>"
+            + "<c:C attr='7'>pear</c:C>"
+            + "</c:B>"
+            + "</g:A>",
+        "<?xml version='1.0' encoding='UTF-8'?>"
+            + "<g:A xmlns:g='urn:g'>"
+            + "<c:B attr='2' xmlns:c='urn:c'>"
+            + "<c:C attr='8'/>"
+            + "</c:B>"
+            + "</g:A>"};
+
+    private static final String[] RESULTS_WRAPPED_SIZE3U = {
+        "<?xml version='1.0' encoding='UTF-8'?>"
+            + "<g:A xmlns:g='urn:g'>"
+            + "<c:B attr='1' xmlns:c='urn:c'>"
+            + "<c:C attr='1'>peach</c:C>"
+            + "<c:C attr='2'/>"
+            + "<c:C attr='3'>orange</c:C>"
+            + "</c:B>"
+            + "</g:A>",
+        "<?xml version='1.0' encoding='UTF-8'?>"
+            + "<g:A xmlns:g='urn:g'>"
+            + "<c:B attr='1' xmlns:c='urn:c'>"
+            + "<c:C attr='4'/>"
+            + "</c:B>"
+            + "<c:B attr='2' xmlns:c='urn:c'>"
+            + "<c:C attr='5'>mango</c:C>"
+            + "<c:C attr='6'/>"
+            + "</c:B>"
+            + "</g:A>",
+        "<?xml version='1.0' encoding='UTF-8'?>"
+            + "<g:A xmlns:g='urn:g'>"
+            + "<c:B attr='2' xmlns:c='urn:c'>"
+            + "<c:C attr='7'>pear</c:C>"
+            + "<c:C attr='8'/>"
+            + "</c:B>"
+            + "</g:A>"};
+
+    private static final String[] RESULTS_WRAPPED_SIZE4 = {
+        "<?xml version='1.0' encoding='UTF-8'?>"
+            + "<g:A xmlns:g='urn:g'>"
+            + "<c:B attr='1' xmlns:c='urn:c'>"
+            + "<c:C attr='1'>peach</c:C>"
+            + "<c:C attr='2'/>"
+            + "<c:C attr='3'>orange</c:C>"
+            + "<c:C attr='4'/>"
+            + "</c:B>"
+            + "</g:A>",
+        "<?xml version='1.0' encoding='UTF-8'?>"
+            + "<g:A xmlns:g='urn:g'>"
+            + "<c:B attr='2' xmlns:c='urn:c'>"
+            + "<c:C attr='5'>mango</c:C>"
+            + "<c:C attr='6'/>"
+            + "<c:C attr='7'>pear</c:C>"
+            + "<c:C attr='8'/>"
+            + "</c:B>"
+            + "</g:A>"};
+    
+    private static final String[] RESULTS_WRAPPED_SIZE5L = RESULTS_WRAPPED_SIZE4;
+
+    private static final String[] RESULTS_WRAPPED_SIZE5U = {
+        "<?xml version='1.0' encoding='UTF-8'?>"
+            + "<g:A xmlns:g='urn:g'>"
+            + "<c:B attr='1' xmlns:c='urn:c'>"
+            + "<c:C attr='1'>peach</c:C>"
+            + "<c:C attr='2'/>"
+            + "<c:C attr='3'>orange</c:C>"
+            + "<c:C attr='4'/>"
+            + "</c:B>"
+            + "<c:B attr='2' xmlns:c='urn:c'>"
+            + "<c:C attr='5'>mango</c:C>"
+            + "</c:B>"
+            + "</g:A>",
+        "<?xml version='1.0' encoding='UTF-8'?>"
+            + "<g:A xmlns:g='urn:g'>"
+            + "<c:B attr='2' xmlns:c='urn:c'>"
+            + "<c:C attr='6'/>"
+            + "<c:C attr='7'>pear</c:C>"
+            + "<c:C attr='8'/>"
+            + "</c:B>"
+            + "</g:A>"};
+
+    private static final String[] RESULTS_INJECTED_SIZE1 = {
+        "<c:C attr='1' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\">peach</c:C>",
+        "<c:C attr='2' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\"/>",
+        "<c:C attr='3' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\">orange</c:C>",
+        "<c:C attr='4' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\"/>",
+        "<c:C attr='5' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\">mango</c:C>",
+        "<c:C attr='6' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\"/>",
+        "<c:C attr='7' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\">pear</c:C>",
+        "<c:C attr='8' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\"/>"};
+
+    private static final String[] RESULTS_INJECTED_SIZE2 = {
+        "<group>"
+            + "<c:C attr='1' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\">peach</c:C>"
+            + "<c:C attr='2' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\"/>"
+            + "</group>",
+        "<group>"
+            + "<c:C attr='3' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\">orange</c:C>"
+            + "<c:C attr='4' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\"/>"
+            + "</group>",
+        "<group>"
+            + "<c:C attr='5' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\">mango</c:C>"
+            + "<c:C attr='6' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\"/>"
+            + "</group>",
+        "<group>"
+            + "<c:C attr='7' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\">pear</c:C>"
+            + "<c:C attr='8' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\"/>"
+            + "</group>"};
+
+    private static final String[] RESULTS_INJECTED_SIZE3 = {
+        "<group>"
+            + "<c:C attr='1' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\">peach</c:C>"
+            + "<c:C attr='2' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\"/>"
+            + "<c:C attr='3' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\">orange</c:C>"
+            + "</group>",
+        "<group>"
+            + "<c:C attr='4' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\"/>"
+            + "<c:C attr='5' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\">mango</c:C>"
+            + "<c:C attr='6' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\"/>"
+            + "</group>",
+        "<group>"
+            + "<c:C attr='7' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\">pear</c:C>"
+            + "<c:C attr='8' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\"/>"
+            + "</group>"};
+
+    private static final String[] RESULTS_INJECTED_SIZE4 = {
+        "<group>"
+            + "<c:C attr='1' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\">peach</c:C>"
+            + "<c:C attr='2' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\"/>"
+            + "<c:C attr='3' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\">orange</c:C>"
+            + "<c:C attr='4' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\"/>"
+            + "</group>",
+        "<group>"
+            + "<c:C attr='5' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\">mango</c:C>"
+            + "<c:C attr='6' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\"/>"
+            + "<c:C attr='7' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\">pear</c:C>"
+            + "<c:C attr='8' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\"/>"
+            + "</group>"};
+
+    private static final String[] RESULTS_INJECTED_SIZE5 = {
+        "<group>"
+            + "<c:C attr='1' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\">peach</c:C>"
+            + "<c:C attr='2' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\"/>"
+            + "<c:C attr='3' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\">orange</c:C>"
+            + "<c:C attr='4' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\"/>"
+            + "<c:C attr='5' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\">mango</c:C>"
+            + "</group>",
+        "<group>"
+            + "<c:C attr='6' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\"/>"
+            + "<c:C attr='7' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\">pear</c:C>"
+            + "<c:C attr='8' xmlns:g=\"urn:g\" xmlns:c=\"urn:c\"/>"
+            + "</group>"};
+
+    private Map<String, String> nsmap;
+        
+    
+    @Override
+    public void setUp() {
+        nsmap = new HashMap<String, String>();
+        nsmap.put("g", "urn:g");
+        nsmap.put("c", "urn:c");
+    }
+
+    // wrapped mode
+    public void testExtractWrappedSize1() throws Exception {
+        invokeAndVerify("//c:C", 
+            'w', 1, new ByteArrayInputStream(TEST_BODY), "utf-8", RESULTS_WRAPPED_SIZE1);
+    }
+
+    public void testExtractWrappedSize2() throws Exception {
+        invokeAndVerify("//c:C", 
+            'w', 2, new ByteArrayInputStream(TEST_BODY), "utf-8", RESULTS_WRAPPED_SIZE2);
+    }
+
+    public void disabledtestExtractWrappedSize3L() throws Exception {
+        invokeAndVerify("//c:C", 
+            'w', 3, new ByteArrayInputStream(TEST_BODY), "utf-8", RESULTS_WRAPPED_SIZE3L);
+    }
+
+    // disabled: not working for now as the context extraction across two ancestor paths is not working
+    public void disabledtestExtractWrappedSize3U() throws Exception {
+        invokeAndVerify("//c:C", 
+            'W', 3, new ByteArrayInputStream(TEST_BODY), "utf-8", RESULTS_WRAPPED_SIZE3U);
+    }
+
+    public void testExtractWrappedSize4() throws Exception {
+        invokeAndVerify("//c:C", 
+            'w', 4, new ByteArrayInputStream(TEST_BODY), "utf-8", RESULTS_WRAPPED_SIZE4);
+    }
+
+    public void disabledtestExtractWrappedSize5L() throws Exception {
+        invokeAndVerify("//c:C", 
+            'w', 5, new ByteArrayInputStream(TEST_BODY), "utf-8", RESULTS_WRAPPED_SIZE5L);
+    }
+
+    // disabled: not working for now as the context extraction across two ancestor paths is not working
+    public void disabledtestExtractWrappedSize5U() throws Exception {
+        invokeAndVerify("//c:C", 
+            'W', 5, new ByteArrayInputStream(TEST_BODY), "utf-8", RESULTS_WRAPPED_SIZE5U);
+    }
+
+    // injected mode
+    public void testExtractInjectedSize1() throws Exception {
+        invokeAndVerify("//c:C", 
+            'i', 1, new ByteArrayInputStream(TEST_BODY), "utf-8", RESULTS_INJECTED_SIZE1);
+    }
+
+    public void testExtractInjectedSize2() throws Exception {
+        invokeAndVerify("//c:C", 
+            'i', 2, new ByteArrayInputStream(TEST_BODY), "utf-8", RESULTS_INJECTED_SIZE2);
+    }
+
+    public void testExtractInjectedSize3() throws Exception {
+        invokeAndVerify("//c:C", 
+            'i', 3, new ByteArrayInputStream(TEST_BODY), "utf-8", RESULTS_INJECTED_SIZE3);
+    }
+
+    public void testExtractInjectedSize4() throws Exception {
+        invokeAndVerify("//c:C", 
+            'i', 4, new ByteArrayInputStream(TEST_BODY), "utf-8", RESULTS_INJECTED_SIZE4);
+    }
+
+    public void testExtractInjectedSize5() throws Exception {
+        invokeAndVerify("//c:C", 
+            'i', 5, new ByteArrayInputStream(TEST_BODY), "utf-8", RESULTS_INJECTED_SIZE5);
+    }
+
+    public void testExtractWrappedLeftOver() throws Exception {
+        final byte[] data = ("<?xml version='1.0' encoding='UTF-8'?><g:A xmlns:g='urn:g'><c:B attr='1' xmlns:c='urn:c'>"
+            + "<c:C attr='1'>peach</c:C>"
+            + "<c:C attr='2'/>"
+            + "<c:C attr='3'>orange</c:C>"
+            + "</c:B></g:A>").getBytes();
+        final String[] results = {"<?xml version='1.0' encoding='UTF-8'?><g:A xmlns:g='urn:g'><c:B attr='1' xmlns:c='urn:c'>"
+                                      + "<c:C attr='1'>peach</c:C><c:C attr='2'/>"
+                                      + "</c:B></g:A>",
+                                  "<?xml version='1.0' encoding='UTF-8'?><g:A xmlns:g='urn:g'><c:B attr='1' xmlns:c='urn:c'>"
+                                      + "<c:C attr='3'>orange</c:C>"
+                                      + "</c:B></g:A>"};
+        invokeAndVerify("//c:C", 
+            'w', 2, new ByteArrayInputStream(data), "utf-8", results);
+    }
+
+    private void invokeAndVerify(String path, char mode, int group,
+        InputStream in, String charset, String[] expected) 
+        throws Exception {
+        XMLTokenExpressionIterator xtei = new XMLTokenExpressionIterator(path, mode);
+        xtei.setNamespaces(nsmap);
+        xtei.setGroup(group);
+
+        Iterator<?> it = xtei.createIterator(in, "utf-8");
+        List<String> results = new ArrayList<String>();
+        while (it.hasNext()) {
+            results.add((String)it.next());
+        }
+        ((Closeable)it).close();
+
+        assertEquals("token count", expected.length, results.size());
+        for (int i = 0; i < expected.length; i++) {
+            assertEquals("mismatch [" + i + "]", expected[i], results.get(i));
+        }
+    }
+}