Author: noble
Date: Tue Oct  6 07:42:28 2009
New Revision: 822154

URL: http://svn.apache.org/viewvc?rev=822154&view=rev
Log:
SOLR-1437 DIH: Enhance XPathRecordReader to deal with //tagname and other 
improvments.

Modified:
    lucene/solr/trunk/contrib/dataimporthandler/CHANGES.txt
    
lucene/solr/trunk/contrib/dataimporthandler/src/main/java/org/apache/solr/handler/dataimport/XPathRecordReader.java
    
lucene/solr/trunk/contrib/dataimporthandler/src/test/java/org/apache/solr/handler/dataimport/TestXPathRecordReader.java

Modified: lucene/solr/trunk/contrib/dataimporthandler/CHANGES.txt
URL: 
http://svn.apache.org/viewvc/lucene/solr/trunk/contrib/dataimporthandler/CHANGES.txt?rev=822154&r1=822153&r2=822154&view=diff
==============================================================================
--- lucene/solr/trunk/contrib/dataimporthandler/CHANGES.txt (original)
+++ lucene/solr/trunk/contrib/dataimporthandler/CHANGES.txt Tue Oct  6 07:42:28 
2009
@@ -178,6 +178,8 @@
 5. SOLR-1465: Replaced string concatenations with StringBuilder append calls 
in XPathRecordReader.
               (Mark Miller, shalin)
 
+6.SOLR-1437 : XPathEntityProcessor can deal with xpath syntaxes such as 
//tagname , /root//tagname (Fergus McMenemie via noble)
+
 Bug Fixes
 ----------------------
 1. SOLR-800:  Deep copy collections to avoid ConcurrentModificationException 
in XPathEntityprocessor while streaming

Modified: 
lucene/solr/trunk/contrib/dataimporthandler/src/main/java/org/apache/solr/handler/dataimport/XPathRecordReader.java
URL: 
http://svn.apache.org/viewvc/lucene/solr/trunk/contrib/dataimporthandler/src/main/java/org/apache/solr/handler/dataimport/XPathRecordReader.java?rev=822154&r1=822153&r2=822154&view=diff
==============================================================================
--- 
lucene/solr/trunk/contrib/dataimporthandler/src/main/java/org/apache/solr/handler/dataimport/XPathRecordReader.java
 (original)
+++ 
lucene/solr/trunk/contrib/dataimporthandler/src/main/java/org/apache/solr/handler/dataimport/XPathRecordReader.java
 Tue Oct  6 07:42:28 2009
@@ -34,9 +34,12 @@
  * /a/b/subje...@qualifier='fullTitle']
  * /a/b/subje...@qualifier=]/subtag
  * /a/b/subject/@qualifier
+ * //a
+ * //a/b...
+ * /a//b
+ * /a//b...
  * /a/b/c
  * </pre>
- * Keep in mind that the wild-card syntax  '//' is not supported
  * A record is a Map<String,Object> . The key is the provided name
  * and the value is a String or a List<String>
  *
@@ -52,6 +55,7 @@
  */
 public class XPathRecordReader {
   private Node rootNode = new Node("/", null);
+
   /** 
    * The FLATTEN flag indicates that all text and cdata under a specific
    * tag should be recursivly fetched and appended to the current Node's
@@ -61,23 +65,25 @@
 
   /**
    * A constructor called with a '|' seperated list of Xpath expressions
-   * which define sub sections of the XML stream that are to be emitted
+   * which define sub sections of the XML stream that are to be emitted as
    * seperate records.
    * 
    * @param forEachXpath  The XPATH for which a record is emitted. Once the
    * xpath tag is encountered, the Node.parse method starts collecting wanted 
    * fields and at the close of the tag, a record is emitted containing all 
    * fields collected since the tag start. Once 
-   * emitted the collected fields are cleared. Any fields collected in the 
parent tag or above
-   * will also be included in the record, but these are not
-   * cleared after emitting the record.
-
+   * emitted the collected fields are cleared. Any fields collected in the 
+   * parent tag or above will also be included in the record, but these are
+   * not cleared after emitting the record.
+   *
    * It uses the ' | ' syntax of XPATH to pass in multiple xpaths.
    */
   public XPathRecordReader(String forEachXpath) {
     String[] splits = forEachXpath.split("\\|");
     for (String split : splits) {
       split = split.trim();
+      if (split.startsWith("//"))
+         throw new RuntimeException("forEach cannot start with '//': " + 
split);
       if (split.length() == 0)
         continue;
       // The created Node has a name set to the full forEach attribute xpath
@@ -86,9 +92,9 @@
   }
 
   /**
-   * A wrapper around {...@link #addField0 addField0()} to create a series of 
Nodes 
-   * based on the supplied Xpath for the given fieldName. The created nodes 
-   * are inserted into a Node tree.
+   * A wrapper around {...@link #addField0 addField0()} to create a series of  
+   * Nodes based on the supplied Xpath and a given fieldName. The created  
+   * nodes are inserted into a Node tree.
    *
    * @param name The name for this field in the emitted record
    * @param xpath The xpath expression for this field
@@ -96,16 +102,14 @@
    *                    a List<String>
    */
   public synchronized XPathRecordReader addField(String name, String xpath, 
boolean multiValued) {
-    if (!xpath.startsWith("/"))
-      throw new RuntimeException("xpath must start with '/' : " + xpath);
     addField0(xpath, name, multiValued, false, 0);
     return this;
   }
 
   /**
-   * A wrapper around {...@link #addField0 addField0()} to create a series of 
Nodes 
-   * based on the supplied Xpath for the given fieldName. The created nodes 
-   * are inserted into a Node tree.
+   * A wrapper around {...@link #addField0 addField0()} to create a series of  
+   * Nodes based on the supplied Xpath and a given fieldName. The created  
+   * nodes are inserted into a Node tree.
    *
    * @param name The name for this field in the emitted record
    * @param xpath The xpath expression for this field
@@ -114,8 +118,6 @@
    * @param flags FLATTEN: Recursivly combine text from all child XML elements
    */
   public synchronized XPathRecordReader addField(String name, String xpath, 
boolean multiValued, int flags) {
-    if (!xpath.startsWith("/"))
-      throw new RuntimeException("xpath must start with '/' : " + xpath);
     addField0(xpath, name, multiValued, false, flags);
     return this;
   }
@@ -129,24 +131,28 @@
    * @param name The name for this field in the emitted record
    * @param multiValued If 'true' then the emitted record will have values in 
    *                    a List<String>
-   * @param isRecord When 'true' flags that this XPATH is from a forEach 
statement
+   * @param isRecord Flags that this XPATH is from a forEach statement
    * @param flags The only supported flag is 'FLATTEN'
    */
   private void addField0(String xpath, String name, boolean multiValued,
                          boolean isRecord, int flags) {
+    if (!xpath.startsWith("/"))
+      throw new RuntimeException("xpath must start with '/' : " + xpath);
     List<String> paths = splitEscapeQuote(xpath);
+    // deal with how split behaves when seperator starts a string!
     if ("".equals(paths.get(0).trim()))
       paths.remove(0);
     rootNode.build(paths, name, multiValued, isRecord, flags);
+    rootNode.buildOptimise(null);
   }
 
   /** 
-   * Uses {...@link #streamRecords streamRecords} to parse the XML source but 
-   * collects the emitted records into a List which is returned upon 
completion.
+   * Uses {...@link #streamRecords streamRecords} to parse the XML source but 
with
+   * a handler that collects all the emitted records into a single List which 
+   * is returned upon completion.
    *
    * @param r the stream reader
    * @return results a List of emitted records
-   *
    */
   public List<Map<String, Object>> getAllRecords(Reader r) {
     final List<Map<String, Object>> results = new ArrayList<Map<String, 
Object>>();
@@ -181,21 +187,22 @@
    * For each node/leaf in the Node tree there is one object of this class.
    * This tree of objects represents all the XPaths we are interested in.
    * For each Xpath segment of interest we create a node. In most cases the
-   * node (branch) is rather basic , but for the final portion (leaf) of any 
Xpath  we add
-   * more information to the Node. When parsing the XML document we
-   * step though this tree as we stream records from the reader. If the XML
+   * node (branch) is rather basic , but for the final portion (leaf) of any
+   * Xpath we add more information to the Node. When parsing the XML document 
+   * we step though this tree as we stream records from the reader. If the XML
    * document departs from this tree we skip start tags till we are back on 
    * the tree.
-   *
    */
-  private class Node {
+  private static class Node {
     String name;      // genrally: segment of the Xpath represented by this 
Node
     String fieldName; // the fieldname in the emitted record (key of the map)
     String xpathName; // the segment of the Xpath represented by this Node
     String forEachPath; // the full Xpath from the forEach entity attribute
-    List<Node> attributes; // a List of attribute Nodes associated with this 
Node
-    List<Node> childNodes; // a List of child Nodes of this node
+    List<Node> attributes; // List of attribute Nodes associated with this Node
+    List<Node> childNodes; // List of immediate child Nodes of this node
+    List<Node> wildCardNodes; // List of '//' style decendants of this Node
     List<Map.Entry<String, String>> attribAndValues;
+    Node wildAncestor; // ancestor Node containing '//' style decendants 
     Node parent; // parent Node in the tree
     boolean hasText=false; // flag: store/emit streamed text for this node
     boolean multiValued=false; //flag: this fields values are returned as a 
List
@@ -205,7 +212,7 @@
 
     public Node(String name, Node p) {
       // Create a basic Node, suitable for the mid portions of any Xpath.
-      // Node.xpathName and Node.name are set to same value
+      // Node.xpathName and Node.name are set to same value.
       xpathName = this.name = name;
       parent = p;
     }
@@ -222,13 +229,17 @@
      * tag/subtag read from the source, this method is called recursively.
      *
      */
-    private void parse(XMLStreamReader parser, Handler handler,
-                       Map<String, Object> values, Stack<Set<String>> stack,
-                       boolean recordStarted) throws IOException, 
XMLStreamException {
+    private void parse(XMLStreamReader parser, 
+                       Handler handler,
+                       Map<String, Object> values, 
+                       Stack<Set<String>> stack, // lists of values to purge
+                       boolean recordStarted
+                       ) throws IOException, XMLStreamException {
       Set<String> valuesAddedinThisFrame = null;
       if (isRecord) {
         // This Node is a match for an XPATH from a forEach attribute, 
-        // prepare to emit a new record when its END_ELEMENT is matched 
+        // prepare for the clean up that will occurr when the record
+        // is emitted after its END_ELEMENT is matched 
         recordStarted = true;
         valuesAddedinThisFrame = new HashSet<String>();
         stack.push(valuesAddedinThisFrame);
@@ -236,15 +247,16 @@
         // This node is a child of some parent which matched against forEach 
         // attribute. Continue to add values to an existing record.
         valuesAddedinThisFrame = stack.peek();
-      } else {
-        //if this tag has an attribute or text which is a brank/leaf just push 
an item up the stack
-        if (attributes != null || hasText)
-          valuesAddedinThisFrame = new HashSet<String>();
-        stack.push(valuesAddedinThisFrame);
       }
 
       try {
+        /* The input stream has deposited us at this Node in our tree of 
+         * intresting nodes. Depending on how this node is of interest,
+         * process further tokens from the input stream and decide what
+         * we do next
+         */
         if (attributes != null) {
+          // we interested in storing attributes from the input stream
           for (Node node : attributes) {
             String value = parser.getAttributeValue(null, node.name);
             if (value != null || (recordStarted && !isRecord)) {
@@ -255,122 +267,133 @@
         }
 
         Set<Node> childrenFound = new HashSet<Node>();
-        // Internally we have to gobble CDATA | CHARACTERS | SPACE events as we
-        // store text, the gobbling continues till we have fetched some other 
-        // event. We use "isNextEventFetched" to indcate that the gobbling has
-        // already fetched the next event.
-        boolean isNextEventFetched = false;
         int event = -1;
+        int flattenedStarts=0; // our tag depth when flattening elements
+        StringBuilder text = new StringBuilder();
 
-        while (true) {
-          if (!isNextEventFetched) {
-            event = parser.next();
-            isNextEventFetched = false;
-          }
-          if (event == END_DOCUMENT) {
-            return;
-          }
+        while (true) {  
+          event = parser.next();
+   
           if (event == END_ELEMENT) {
-            if (isRecord)
-              handler.handle(getDeepCopy(values), forEachPath);
-            if (recordStarted && !isRecord
-                    && !childrenFound.containsAll(childNodes)) {
-              for (Node n : childNodes) {
-                if (!childrenFound.contains(n))
-                  n.putNulls(values);
+            if (flattenedStarts > 0) flattenedStarts--;
+            else {
+              if (text.length() > 0 && valuesAddedinThisFrame != null) {
+                valuesAddedinThisFrame.add(fieldName);
+                putText(values, text.toString(), fieldName, multiValued);
               }
-            }
-            return;
-          }
-          if ((event == CDATA || event == CHARACTERS || event == SPACE)
-                  && hasText) {
-            valuesAddedinThisFrame.add(fieldName);
-            // becuase we are fetching events here we need to ensure the outer
-            // loop does not end up doing an extra parser.next()
-            isNextEventFetched = true;
-            StringBuilder text = new StringBuilder(parser.getText());
-            event = parser.next();
-
-            while (true) {
-              if(event == CDATA || event == CHARACTERS || event == SPACE) {
-                text.append(parser.getText());
-              } else if(event == START_ELEMENT) {
-                if (flatten) {
-                  int starts = 1;
-                  while (true) {
-                    event = parser.next();
-                    if (event == CDATA || event == CHARACTERS || event == 
SPACE) {
-                      text.append(parser.getText());
-                    } else if (event == START_ELEMENT) {
-                      starts++;
-                    } else if (event == END_ELEMENT) {
-                      starts--;
-                      if (starts == 0) break;
-                    }
-                  }
-                } else {
-                  // We are not flatten-ing, so look to see if any of the child
-                  // elements are wanted, and recurse if any are found.
-                  handleStartElement(parser, childrenFound, handler, values, 
stack, recordStarted);
+              if (isRecord) handler.handle(getDeepCopy(values), forEachPath);
+              if (childNodes != null && recordStarted && !isRecord && 
!childrenFound.containsAll(childNodes)) {
+                // nonReccord nodes where we have not collected text for ALL
+                // the child nodes.
+                for (Node n : childNodes) {
+                  // For the multivalue child nodes where we could have, but
+                  // didnt, collect text. Push a null string into values.
+                  if (!childrenFound.contains(n)) n.putNulls(values);
                 }
-              } else {
-                break;
               }
-              event = parser.next();
+              return;
             }
-            // save the text we have read against the fieldName in the Map 
values
-            putText(values, text.toString(), fieldName, multiValued);
-          } else if (event == START_ELEMENT) {
-            handleStartElement(parser, childrenFound, handler, values, stack, 
recordStarted);
           }
-        }
-      } finally {
-        /*If a record has ended  (tag closed) then clearup all the fields found
-        in this record after this tag started */
-        Set<String> cleanThis = null;
-        if (isRecord || !recordStarted) {
-          cleanThis = stack.pop();
-        } else {
-          return;
-        }
-        if (cleanThis != null) {
-          for (String fld : cleanThis) {
-            values.remove(fld);
+          else if (hasText && (event==CDATA || event==CHARACTERS || 
event==SPACE)) {
+            text.append(parser.getText());
+          } 
+          else if (event == START_ELEMENT) {
+            if ( flatten ) 
+               flattenedStarts++;
+            else 
+               handleStartElement(parser, childrenFound, handler, values, 
stack, recordStarted);
+          }
+          // END_DOCUMENT is least likely to appear and should be 
+          // last in if-then-else skip chain
+          else if (event == END_DOCUMENT) return;
+          }
+        }finally {
+        if ((isRecord || !recordStarted) && !stack.empty()) {
+          Set<String> cleanThis = stack.pop();
+          if (cleanThis != null) {
+            for (String fld : cleanThis) values.remove(fld);
           }
         }
       }
     }
 
-    /**if a new tag is encountered, check if it is of interest of not (if 
there is a matching child Node).
-     * if yes continue parsing else skip
+    /**
+     * If a new tag is encountered, check if it is of interest or not by seeing
+     * if it matches against our node tree. If we have deperted from the node 
+     * tree then walk back though the tree's ancestor nodes checking to see if
+     * any // expressions exist for the node and compare them against the new
+     * tag. If matched then "jump" to that node, otherwise ignore the tag.
+     *
+     * Note, the list of // expressions found while walking back up the tree
+     * is chached in the HashMap decends. Then if the new tag is to be skipped,
+     * any inner chil tags are compared against the cache and jumped to if
+     * matched.
      */
     private void handleStartElement(XMLStreamReader parser, Set<Node> 
childrenFound,
                                     Handler handler, Map<String, Object> 
values,
                                     Stack<Set<String>> stack, boolean 
recordStarted)
             throws IOException, XMLStreamException {
-      Node n = getMatchingChild(parser);
+      Node n = getMatchingNode(parser,childNodes);
+      Map<String, Object> decends=new HashMap<String, Object>();
       if (n != null) {
         childrenFound.add(n);
         n.parse(parser, handler, values, stack, recordStarted);
+        return;
         }
-      else {
-        // skip ELEMENTS till source document is back within the tree
-        int count=1; // we have had our first START_ELEMENT
-        while ( count != 0 ) {
-          int token = parser.next();
-          if (token == START_ELEMENT) count++;
-          else if (token == END_ELEMENT)  count--;
+      // The stream has diverged from the tree of interesting elements, but
+      // are there any wildCardNodes ... anywhere in our path from the root?
+      Node dn = this; // checking our Node first!
+            
+      do {
+        if (dn.wildCardNodes != null) {
+          // Check to see if the streams tag matches one of the "//" all
+          // decendents type expressions for this node.
+          n = getMatchingNode(parser, dn.wildCardNodes);
+          if (n != null) {
+            childrenFound.add(n);
+            n.parse(parser, handler, values, stack, recordStarted);
+            break;
           }
+          // add the list of this nodes wild decendents to the cache
+          for (Node nn : dn.wildCardNodes) decends.put(nn.name, nn);
+        }
+        dn = dn.wildAncestor; // leap back along the tree toward root
+      } while (dn != null) ;
+ 
+      if (n == null) {
+        // we have a START_ELEMENT which is not within the tree of
+        // interesting nodes. Skip over the contents of this element
+        // but recursivly repeat the above for any START_ELEMENTs
+        // found within this element.
+        int count = 1; // we have had our first START_ELEMENT
+        while (count != 0) {
+          int token = parser.next();
+          if (token == START_ELEMENT) {
+            Node nn = (Node) decends.get(parser.getLocalName());
+            if (nn != null) {
+              // We have a //Node which matches the stream's parser.localName
+              childrenFound.add(nn);
+              // Parse the contents of this stream element
+              nn.parse(parser, handler, values, stack, recordStarted);
+            } 
+            else count++;
+          } 
+          else if (token == END_ELEMENT) count--;
+        }
       }
     }
 
-    /**check if the current tag is to be parsed or not. if yes return the Node 
object
+
+    /**
+     * Check if the current tag is to be parsed or not. We step through the
+     * supplied List "searchList" looking for a match. If matched, return the
+     * Node object.
      */
-    private Node getMatchingChild(XMLStreamReader parser) {
-      if (childNodes == null)
+    private Node getMatchingNode(XMLStreamReader parser,List<Node> searchL){
+      if (searchL == null)
         return null;
       String localName = parser.getLocalName();
-      for (Node n : childNodes) {
+      for (Node n : searchL) {
         if (n.name.equals(localName)) {
           if (n.attribAndValues == null)
             return n;
@@ -389,14 +412,14 @@
           return false;
         if (e.getValue() != null && !e.getValue().equals(val))
           return false;
-
       }
       return true;
     }
 
     /**
      * A recursive routine that walks the Node tree from a supplied start
-     * pushing a null string onto every multiValued fieldName's List of values.
+     * pushing a null string onto every multiValued fieldName's List of values
+     * where a value has not been provided from the stream.
      */
     private void putNulls(Map<String, Object> values) {
       if (attributes != null) {
@@ -414,10 +437,10 @@
     }
 
     /**
-     * Add the field name and text into the values Map. If it is a non 
multivalued field, then the text
-     * is simply placed in the object portion of the Map. If it is a
-     * multivalued field then the text is pushed onto a List which is
-     * the object portion of the Map.
+     * Add the field name and text into the values Map. If it is a non
+     * multivalued field, then the text is simply placed in the object
+     * portion of the Map. If it is a multivalued field then the text is
+     * pushed onto a List which is the object portion of the Map.
      */
     @SuppressWarnings("unchecked")
     private void putText(Map<String, Object> values, String value,
@@ -436,13 +459,24 @@
 
 
     /**
+     * Walk the Node tree propagating any wildDescentant information to
+     * child nodes. This allows us to optimise the performance of the
+     * main parse method.
+     */
+    private void buildOptimise(Node wa) {
+     wildAncestor=wa;
+     if ( wildCardNodes != null ) wa = this;
+     if ( childNodes != null )
+       for ( Node n : childNodes ) n.buildOptimise(wa);
+     }
+
+    /**
      * Build a Node tree structure representing all Xpaths of intrest to us.
      * This must be done before parsing of the XML stream starts. Each node 
      * holds one portion of an Xpath. Taking each Xpath segment in turn this
      * method walks the Node tree  and finds where the new segment should be
      * inserted. It creates a Node representing a field's name, XPATH and 
      * some flags and inserts the Node into the Node tree.
-     *
      */
     private void build(
         List<String> paths,   // a List of segments from the split xpaths
@@ -452,27 +486,46 @@
         int flags             // are we to flatten matching xpaths
         ) {
       // recursivly walk the paths Lists adding new Nodes as required
-      String name = paths.remove(0); // shift out next Xpath segment
-      if (paths.isEmpty() && name.startsWith("@")) {
+      String xpseg = paths.remove(0); // shift out next Xpath segment
+
+      if (paths.isEmpty() && xpseg.startsWith("@")) {
         // we have reached end of element portion of Xpath and can now only
         // have an element attribute. Add it to this nodes list of attributes
         if (attributes == null) {
           attributes = new ArrayList<Node>();
         }
-        name = name.substring(1); // strip the '@'
-        attributes.add(new Node(name, fieldName, multiValued));
-
-      } else {
+        xpseg = xpseg.substring(1); // strip the '@'
+        attributes.add(new Node(xpseg, fieldName, multiValued));
+      }
+      else if ( xpseg.length() == 0) {
+        // we have a '//' selector for all decendents of the current nodes
+        xpseg = paths.remove(0); // shift out next Xpath segment
+        if (wildCardNodes == null) wildCardNodes = new ArrayList<Node>();
+        Node n = getOrAddNode(xpseg, wildCardNodes);
+        if (paths.isEmpty()) {
+          // We are current a leaf node.
+          // xpath with content we want to store and return
+          n.hasText = true;        // we have to store text found here
+          n.fieldName = fieldName; // name to store collected text against
+          n.multiValued = multiValued; // true: text be stored in a List
+          n.flatten = flags == FLATTEN; // true: store text from child tags
+        }
+        else {
+          // recurse to handle next paths segment
+          n.build(paths, fieldName, multiValued, record, flags);
+        }
+      }
+      else {
         if (childNodes == null)
           childNodes = new ArrayList<Node>();
         // does this "name" already exist as a child node.
-        Node n = getOrAddChildNode(name);
+        Node n = getOrAddNode(xpseg,childNodes);
         if (paths.isEmpty()) {
-          // We have reached the end of paths. When parsing the actual
-          // input we have traversed to a position where we actutally have to
-          // do something. getOrAddChildNode() will have created and returned
-          // a new minimal Node with name and xpathName already populated. We
-          // need to add more information
+          // We have emptied paths, we are for the moment a leaf of the tree.
+          // When parsing the actual input we have traversed to a position 
+          // where we actutally have to do something. getOrAddNode() will
+          // have created and returned a new minimal Node with name and
+          // xpathName already populated. We need to add more information.
           if (record) {
             // forEach attribute
             n.isRecord = true; // flag: forEach attribute, prepare to emit rec
@@ -491,10 +544,9 @@
       }
     }
 
-    private Node getOrAddChildNode(String xpathName) {
-      for (Node n : childNodes)
-        if (n.xpathName.equals(xpathName))
-          return n;
+    private Node getOrAddNode(String xpathName, List<Node> searchList ) {
+      for (Node n : searchList)
+        if (n.xpathName.equals(xpathName)) return n;
       // new territory! add a new node for this Xpath bitty
       Node n = new Node(xpathName, this); // a minimal Node initalization
       Matcher m = ATTRIB_PRESENT_WITHVAL.matcher(xpathName);
@@ -510,49 +562,54 @@
           if (n.attribAndValues == null)
             n.attribAndValues = new ArrayList<Map.Entry<String, String>>();
           n.attribAndValues.addAll(attribs.entrySet());
-
         }
       }
-      childNodes.add(n);
+      searchList.add(n);
       return n;
     }
-  } // end of class Node
-
 
-  /**
-   * Copies a supplied Map to a new Map which is returned. Used to copy a 
-   * records values. If a fields value is a List then they have to be 
-   * deep-copied for thread safety
-   */
-  private Map<String, Object> getDeepCopy(Map<String, Object> values) {
-    Map<String, Object> result = new HashMap<String, Object>();
-    for (Map.Entry<String, Object> entry : values.entrySet()) {
-      if (entry.getValue() instanceof List) {
-        result.put(entry.getKey(),new ArrayList((List) entry.getValue()));
-      } else {
-        result.put(entry.getKey(),entry.getValue());
+    /**
+     * Copies a supplied Map to a new Map which is returned. Used to copy a
+     * records values. If a fields value is a List then they have to be
+     * deep-copied for thread safety
+     */
+    private static Map<String, Object> getDeepCopy(Map<String, Object> values) 
{
+      Map<String, Object> result = new HashMap<String, Object>();
+      for (Map.Entry<String, Object> entry : values.entrySet()) {
+        if (entry.getValue() instanceof List) {
+          result.put(entry.getKey(), new ArrayList((List) entry.getValue()));
+        } else {
+          result.put(entry.getKey(), entry.getValue());
+        }
       }
+      return result;
     }
-    return result;
-  }
+  } // end of class Node
+
 
   /**
-   * The Xpath is split into segments using the '/' s a seperator. However
+   * The Xpath is split into segments using the '/' as a seperator. However
    * this method deals with special cases where there is a slash '/' character
-   * inside the attribute value e.g. x/@html='text/html'. We need to split
-   * by '/' excluding the '/' which is a part of the attribute's value.
+   * inside the attribute value e.g. x/@html='text/html'. We split by '/' but 
+   * then reassemble things were the '/' appears within a quoted sub-string.
+   *
+   * We have already enforced that the string must begin with a seperator. This
+   * method depends heavily on how split behaves if the string starts with the
+   * seperator or if a sequence of multiple seperator's appear. 
    */
   private static List<String> splitEscapeQuote(String str) {
     List<String> result = new LinkedList<String>();
     String[] ss = str.split("/");
-    for (int i = 0; i < ss.length; i++) {
-      if (ss[i].length() == 0 && result.size() == 0) continue;
+    for (int i=0; i<ss.length; i++) { // i=1: skip seperator at start of string
       StringBuilder sb = new StringBuilder();
       int quoteCount = 0;
       while (true) {
         sb.append(ss[i]);
-        for (int j = 0; j < ss[i].length(); j++) if (ss[i].charAt(j) == '\'') 
quoteCount++;
+        for (int j=0; j<ss[i].length(); j++)
+            if (ss[i].charAt(j) == '\'') quoteCount++;
+        // have we got a split inside quoted sub-string?
         if ((quoteCount % 2) == 0) break;
+        // yes!; replace the '/' and loop to concat next token
         i++;
         sb.append("/");
       }
@@ -576,7 +633,8 @@
      * the addField() methods. The value can be a single String (for single 
      * valued fields) or a List<String> (for multiValued).
      * @param xpath The forEach XPATH for which this record is being emitted
-     * If there is any change all parsing will be aborted and the Exception is 
propogated up
+     * If there is any change all parsing will be aborted and the Exception
+     * is propogated up
      */
     public void handle(Map<String, Object> record, String xpath);
   }

Modified: 
lucene/solr/trunk/contrib/dataimporthandler/src/test/java/org/apache/solr/handler/dataimport/TestXPathRecordReader.java
URL: 
http://svn.apache.org/viewvc/lucene/solr/trunk/contrib/dataimporthandler/src/test/java/org/apache/solr/handler/dataimport/TestXPathRecordReader.java?rev=822154&r1=822153&r2=822154&view=diff
==============================================================================
--- 
lucene/solr/trunk/contrib/dataimporthandler/src/test/java/org/apache/solr/handler/dataimport/TestXPathRecordReader.java
 (original)
+++ 
lucene/solr/trunk/contrib/dataimporthandler/src/test/java/org/apache/solr/handler/dataimport/TestXPathRecordReader.java
 Tue Oct  6 07:42:28 2009
@@ -33,9 +33,13 @@
 public class TestXPathRecordReader {
   @Test
   public void basic() {
-    String xml = "<root>\n" + "   <b>\n" + "     <c>Hello C1</c>\n"
-            + "     <c>Hello C1</c>\n" + "   </b>\n" + "   <b>\n"
-            + "     <c>Hello C2</c>\n" + "   </b>\n" + "</root>";
+    String xml="<root>\n"
+             + "   <b><c>Hello C1</c>\n"
+             + "      <c>Hello C1</c>\n"
+             + "      </b>\n"
+             + "   <b><c>Hello C2</c>\n"
+             + "     </b>\n"
+             + "</root>";
     XPathRecordReader rr = new XPathRecordReader("/root/b");
     rr.addField("c", "/root/b/c", true);
     List<Map<String, Object>> l = rr.getAllRecords(new StringReader(xml));
@@ -46,8 +50,10 @@
 
   @Test
   public void attributes() {
-    String xml = "<root>\n" + "   <b a=\"x0\" b=\"y0\" />\n"
-            + "   <b a=\"x1\" b=\"y1\" />\n" + "   <b a=\"x2\" b=\"y2\" />\n"
+    String xml="<root>\n"
+             + "   <b a=\"x0\" b=\"y0\" />\n"
+             + "   <b a=\"x1\" b=\"y1\" />\n"
+             + "   <b a=\"x2\" b=\"y2\" />\n"
             + "</root>";
     XPathRecordReader rr = new XPathRecordReader("/root/b");
     rr.addField("a", "/root/b/@a", false);
@@ -55,22 +61,26 @@
     List<Map<String, Object>> l = rr.getAllRecords(new StringReader(xml));
     Assert.assertEquals(3, l.size());
     Assert.assertEquals("x0", l.get(0).get("a"));
+    Assert.assertEquals("x1", l.get(1).get("a"));
+    Assert.assertEquals("x2", l.get(2).get("a"));
+    Assert.assertEquals("y0", l.get(0).get("b"));
     Assert.assertEquals("y1", l.get(1).get("b"));
+    Assert.assertEquals("y2", l.get(2).get("b"));
   }
   
   @Test
   public void attrInRoot(){
-    String xml = "<r>\n" +
+    String xml="<r>\n" +
             "<merchantProduct id=\"814636051\" mid=\"189973\">\n" +
             "                   <in_stock type=\"stock-4\" />\n" +
             "                   <condition type=\"cond-0\" />\n" +
             "                   <price>301.46</price>\n" +
-            "</merchantProduct>\n" +
+               "   </merchantProduct>\n" +
             "<merchantProduct id=\"814636052\" mid=\"189974\">\n" +
             "                   <in_stock type=\"stock-5\" />\n" +
             "                   <condition type=\"cond-1\" />\n" +
             "                   <price>302.46</price>\n" +
-            "</merchantProduct>\n" +
+               "   </merchantProduct>\n" +
             "\n" +
             "</r>";
      XPathRecordReader rr = new XPathRecordReader("/r/merchantProduct");
@@ -94,9 +104,12 @@
 
   @Test
   public void attributes2Level() {
-    String xml = "<root>\n" + "<a>\n" + "   <b a=\"x0\" b=\"y0\" />\n"
-            + "   <b a=\"x1\" b=\"y1\" />\n" + "   <b a=\"x2\" b=\"y2\" />\n"
-            + "</a>" + "</root>";
+    String xml="<root>\n"
+             + "<a>\n  <b a=\"x0\" b=\"y0\" />\n"
+             + "       <b a=\"x1\" b=\"y1\" />\n"
+             + "       <b a=\"x2\" b=\"y2\" />\n"
+             + "       </a>"
+             + "</root>";
     XPathRecordReader rr = new XPathRecordReader("/root/a/b");
     rr.addField("a", "/root/a/b/@a", false);
     rr.addField("b", "/root/a/b/@b", false);
@@ -108,11 +121,16 @@
 
   @Test
   public void attributes2LevelHetero() {
-    String xml = "<root>\n" + "<a>\n" + "   <b a=\"x0\" b=\"y0\" />\n"
-            + "   <b a=\"x1\" b=\"y1\" />\n" + "   <b a=\"x2\" b=\"y2\" />\n"
-            + "</a>" + "<x>\n" + "   <b a=\"x4\" b=\"y4\" />\n"
-            + "   <b a=\"x5\" b=\"y5\" />\n" + "   <b a=\"x6\" b=\"y6\" />\n"
-            + "</x>" + "</root>";
+    String xml="<root>\n"
+             + "<a>\n   <b a=\"x0\" b=\"y0\" />\n"
+             + "        <b a=\"x1\" b=\"y1\" />\n"
+             + "        <b a=\"x2\" b=\"y2\" />\n"
+             + "        </a>"
+             + "<x>\n   <b a=\"x4\" b=\"y4\" />\n"
+             + "        <b a=\"x5\" b=\"y5\" />\n"
+             + "        <b a=\"x6\" b=\"y6\" />\n"
+             + "        </x>"
+             + "</root>";
     XPathRecordReader rr = new XPathRecordReader("/root/a | /root/x");
     rr.addField("a", "/root/a/b/@a", false);
     rr.addField("b", "/root/a/b/@b", false);
@@ -123,12 +141,9 @@
     final List<Map<String, Object>> x = new ArrayList<Map<String, Object>>();
     rr.streamRecords(new StringReader(xml), new XPathRecordReader.Handler() {
       public void handle(Map<String, Object> record, String xpath) {
-        if (record == null)
-          return;
-        if (xpath.equals("/root/a"))
-          a.add(record);
-        if (xpath.equals("/root/x"))
-          x.add(record);
+        if (record == null) return;
+        if (xpath.equals("/root/a")) a.add(record);
+        if (xpath.equals("/root/x")) x.add(record);
       }
     });
 
@@ -138,9 +153,14 @@
 
   @Test
   public void attributes2LevelMissingAttrVal() {
-    String xml = "<root>\n" + "<a>\n" + "   <b a=\"x0\" b=\"y0\" />\n"
-            + "   <b a=\"x1\" b=\"y1\" />\n" + "</a>" + "<a>\n"
-            + "   <b a=\"x3\"  />\n" + "   <b b=\"y4\" />\n" + "</a>" + 
"</root>";
+    String xml="<root>\n"
+             + "<a>\n  <b a=\"x0\" b=\"y0\" />\n"
+             + "       <b a=\"x1\" b=\"y1\" />\n"
+             + "       </a>"
+             + "<a>\n  <b a=\"x3\"  />\n"
+             + "       <b b=\"y4\" />\n"
+             + "       </a>"
+             + "</root>";
     XPathRecordReader rr = new XPathRecordReader("/root/a");
     rr.addField("a", "/root/a/b/@a", true);
     rr.addField("b", "/root/a/b/@b", true);
@@ -152,12 +172,20 @@
 
   @Test
   public void elems2LevelMissing() {
-    String xml = "<root>\n" + "\t<a>\n" + "\t   <b>\n" + "\t      <x>x0</x>\n"
-            + "\t      <y>y0</y>\n" + "\t   </b>\n" + "\t   <b>\n"
-            + "\t   \t<x>x1</x>\n" + "\t   \t<y>y1</y>\n" + "\t   </b>\n"
-            + "\t</a>\n" + "\t<a>\n" + "\t   <b>\n" + "\t      <x>x3</x>\n"
-            + "\t   </b>\n" + "\t   <b>\n" + "\t   \t<y>y4</y>\n" + "\t   
</b>\n"
-            + "\t</a>\n" + "</root>";
+    String xml="<root>\n"
+             + "\t<a>\n"
+             + "\t   <b>\n\t  <x>x0</x>\n"
+             + "\t            <y>y0</y>\n"
+             + "\t            </b>\n"
+             + "\t   <b>\n\t  <x>x1</x>\n"
+             + "\t            <y>y1</y>\n"
+             + "\t            </b>\n"
+             + "\t   </a>\n"
+             + "\t<a>\n"
+             + "\t   <b>\n\t  <x>x3</x>\n\t   </b>\n"
+             + "\t   <b>\n\t  <y>y4</y>\n\t   </b>\n"
+             + "\t   </a>\n"
+             + "</root>";
     XPathRecordReader rr = new XPathRecordReader("/root/a");
     rr.addField("a", "/root/a/b/x", true);
     rr.addField("b", "/root/a/b/y", true);
@@ -207,12 +235,23 @@
 
   @Test
   public void elems2LevelWithAttrib() {
-    String xml = "<root>\n" + "\t<a>\n" + "\t   <b k=\"x\">\n"
-            + "\t      <x>x0</x>\n" + "\t      <y>y0</y>\n" + "\t   </b>\n"
-            + "\t   <b k=\"y\">\n" + "\t   \t<x>x1</x>\n" + "\t   
\t<y>y1</y>\n"
-            + "\t   </b>\n" + "\t</a>\n" + "\t<a>\n" + "\t   <b>\n"
-            + "\t      <x>x3</x>\n" + "\t   </b>\n" + "\t   <b>\n"
-            + "\t   \t<y>y4</y>\n" + "\t   </b>\n" + "\t</a>\n" + "</root>";
+    String xml = "<root>\n\t<a>\n\t   <b k=\"x\">\n"
+            + "\t                        <x>x0</x>\n"
+            + "\t                        <y>y0</y>\n"
+            + "\t                        </b>\n"
+            + "\t                     <b k=\"y\">\n"
+            + "\t                        <x>x1</x>\n"
+            + "\t                        <y>y1</y>\n"
+            + "\t                        </b>\n"
+            + "\t                </a>\n"
+            + "\t           <a>\n\t   <b>\n"
+            + "\t                        <x>x3</x>\n"
+            + "\t                        </b>\n"
+            + "\t                     <b>\n"
+            + "\t                     <y>y4</y>\n"
+            + "\t                        </b>\n"
+            + "\t               </a>\n"
+            + "</root>";
     XPathRecordReader rr = new XPathRecordReader("/root/a");
     rr.addField("x", "/root/a/b...@k]/x", true);
     rr.addField("y", "/root/a/b...@k]/y", true);
@@ -225,13 +264,24 @@
 
   @Test
   public void elems2LevelWithAttribMultiple() {
-    String xml = "<root>\n" + "\t<a>\n" + "\t   <b k=\"x\" m=\"n\" >\n"
-            + "\t      <x>x0</x>\n" + "\t      <y>y0</y>\n" + "\t   </b>\n"
-            + "\t   <b k=\"y\" m=\"p\">\n" + "\t   \t<x>x1</x>\n"
-            + "\t   \t<y>y1</y>\n" + "\t   </b>\n" + "\t</a>\n" + "\t<a>\n"
-            + "\t   <b k=\"x\">\n" + "\t      <x>x3</x>\n" + "\t   </b>\n"
-            + "\t   <b m=\"n\">\n" + "\t   \t<y>y4</y>\n" + "\t   </b>\n"
-            + "\t</a>\n" + "</root>";
+    String xml="<root>\n"
+             + "\t<a>\n\t   <b k=\"x\" m=\"n\" >\n"
+             + "\t             <x>x0</x>\n"
+             + "\t             <y>y0</y>\n"
+             + "\t             </b>\n"
+             + "\t          <b k=\"y\" m=\"p\">\n"
+             + "\t             <x>x1</x>\n"
+             + "\t             <y>y1</y>\n"
+             + "\t             </b>\n"
+             + "\t   </a>\n"
+             + "\t<a>\n\t   <b k=\"x\">\n"
+             + "\t             <x>x3</x>\n"
+             + "\t             </b>\n"
+             + "\t          <b m=\"n\">\n"
+             + "\t             <y>y4</y>\n"
+             + "\t             </b>\n"
+             + "\t   </a>\n"
+             + "</root>";
     XPathRecordReader rr = new XPathRecordReader("/root/a");
     rr.addField("x", "/root/a/b...@k][@m='n']/x", true);
     rr.addField("y", "/root/a/b...@k][@m='n']/y", true);
@@ -244,12 +294,18 @@
 
   @Test
   public void elems2LevelWithAttribVal() {
-    String xml = "<root>\n" + "\t<a>\n" + "\t   <b k=\"x\">\n"
-            + "\t      <x>x0</x>\n" + "\t      <y>y0</y>\n" + "\t   </b>\n"
-            + "\t   <b k=\"y\">\n" + "\t   \t<x>x1</x>\n" + "\t   
\t<y>y1</y>\n"
-            + "\t   </b>\n" + "\t</a>\n" + "\t<a>\n" + "\t   <b>\n"
-            + "\t      <x>x3</x>\n" + "\t   </b>\n" + "\t   <b>\n"
-            + "\t   \t<y>y4</y>\n" + "\t   </b>\n" + "\t</a>\n" + "</root>";
+    String xml="<root>\n\t<a>\n   <b k=\"x\">\n"
+             + "\t                  <x>x0</x>\n"
+             + "\t                  <y>y0</y>\n"
+             + "\t                  </b>\n"
+             + "\t                <b k=\"y\">\n"
+             + "\t                  <x>x1</x>\n"
+             + "\t                  <y>y1</y>\n"
+             + "\t                  </b>\n"
+             + "\t                </a>\n"
+             + "\t        <a>\n   <b><x>x3</x></b>\n"
+             + "\t                <b><y>y4</y></b>\n"
+             + "\t</a>\n" + "</root>";
     XPathRecordReader rr = new XPathRecordReader("/root/a");
     rr.addField("x", "/root/a/b...@k='x']/x", true);
     rr.addField("y", "/root/a/b...@k='x']/y", true);
@@ -274,31 +330,148 @@
   }
 
   @Test
+  public void  unsupported_Xpaths() {
+    String xml = "<root><b><a x=\"a/b\" h=\"hello-A\"/>  </b></root>";
+    XPathRecordReader rr=null;
+    try {
+      rr = new XPathRecordReader("//b");
+      Assert.fail("A RuntimeException was expected: //b forEach cannot begin 
with '//'.");
+      }
+    catch (RuntimeException ex) {  }
+     try {
+      rr.addField("bold"  ,"b",        false);
+      Assert.fail("A RuntimeException was expected: 'b' xpaths must begin with 
'/'.");
+      }
+    catch (RuntimeException ex) {  }
+
+  }
+
+  @Test
+  public void any_decendent_from_root() {
+    XPathRecordReader rr = new XPathRecordReader("/anyd/contenido");
+    rr.addField("descdend", "//boo",                   true);
+    rr.addField("inr_descd","//boo/i",                false);
+    rr.addField("cont",     "/anyd/contenido",        false);
+    rr.addField("id",       "/anyd/contenido/@id",    false);
+    rr.addField("status",   "/anyd/status",           false);
+    rr.addField("title",    "/anyd/contenido/titulo", 
false,XPathRecordReader.FLATTEN);
+    rr.addField("resume",   "/anyd/contenido/resumen",false);
+    rr.addField("text",     "/anyd/contenido/texto",  false);
+
+    String xml="<anyd>\n"
+             + "  this <boo>top level</boo> is ignored because it is external 
to the forEach\n"
+             + "  <status>as is <boo>this element</boo></status>\n"
+             + "  <contenido id=\"10097\" idioma=\"cat\">\n"
+             + "    This one is <boo>not ignored as its</boo> inside a 
forEach\n"
+             + "    <antetitulo><i> big <boo>antler</boo></i></antetitulo>\n"
+             + "    <titulo>  My <i>flattened <boo>title</boo></i> </titulo>\n"
+             + "    <resumen> My summary <i>skip this!</i>  </resumen>\n"
+             + "    <texto>   <boo>Within the body of</boo>My text</texto>\n"
+             + "    <p>Access <boo>inner <i>sub clauses</i> as 
well</boo></p>\n"
+             + "    </contenido>\n"
+             + "</anyd>";
+
+    List<Map<String, Object>> l = rr.getAllRecords(new StringReader(xml));
+    Assert.assertEquals(1, l.size());
+    Map<String, Object> m = l.get(0);
+    Assert.assertEquals("This one is  inside a forEach", 
m.get("cont").toString().trim());
+    Assert.assertEquals("10097"             ,m.get("id"));
+    Assert.assertEquals("My flattened title",m.get("title").toString().trim());
+    Assert.assertEquals("My summary"        
,m.get("resume").toString().trim());
+    Assert.assertEquals("My text"           ,m.get("text").toString().trim());
+    Assert.assertEquals("not ignored as its",(String) ((List) 
m.get("descdend")).get(0) );
+    Assert.assertEquals("antler"            ,(String) ((List) 
m.get("descdend")).get(1) );
+    Assert.assertEquals("Within the body of",(String) ((List) 
m.get("descdend")).get(2) );
+    Assert.assertEquals("inner  as well"    ,(String) ((List) 
m.get("descdend")).get(3) );
+    Assert.assertEquals("sub clauses"       
,m.get("inr_descd").toString().trim());
+  }
+
+  @Test
+  public void any_decendent_of_a_child1() {
+    XPathRecordReader rr = new XPathRecordReader("/anycd");
+    rr.addField("descdend", "/anycd//boo",         true);
+
+    // same test string as above but checking to see if *all* //boo's are 
collected
+    String xml="<anycd>\n"
+             + "  this <boo>top level</boo> is ignored because it is external 
to the forEach\n"
+             + "  <status>as is <boo>this element</boo></status>\n"
+             + "  <contenido id=\"10097\" idioma=\"cat\">\n"
+             + "    This one is <boo>not ignored as its</boo> inside a 
forEach\n"
+             + "    <antetitulo><i> big <boo>antler</boo></i></antetitulo>\n"
+             + "    <titulo>  My <i>flattened <boo>title</boo></i> </titulo>\n"
+             + "    <resumen> My summary <i>skip this!</i>  </resumen>\n"
+             + "    <texto>   <boo>Within the body of</boo>My text</texto>\n"
+             + "    <p>Access <boo>inner <i>sub clauses</i> as 
well</boo></p>\n"
+             + "    </contenido>\n"
+             + "</anycd>";
+
+    List<Map<String, Object>> l = rr.getAllRecords(new StringReader(xml));
+    Assert.assertEquals(1, l.size());
+    Map<String, Object> m = l.get(0);
+    Assert.assertEquals("top level"         ,(String) ((List) 
m.get("descdend")).get(0) );
+    Assert.assertEquals("this element"      ,(String) ((List) 
m.get("descdend")).get(1) );
+    Assert.assertEquals("not ignored as its",(String) ((List) 
m.get("descdend")).get(2) );
+    Assert.assertEquals("antler"            ,(String) ((List) 
m.get("descdend")).get(3) );
+    Assert.assertEquals("title"             ,(String) ((List) 
m.get("descdend")).get(4) );
+    Assert.assertEquals("Within the body of",(String) ((List) 
m.get("descdend")).get(5) );
+    Assert.assertEquals("inner  as well"    ,(String) ((List) 
m.get("descdend")).get(6) );
+  }
+
+  @Test
+  public void any_decendent_of_a_child2() {
+    XPathRecordReader rr = new XPathRecordReader("/anycd");
+    rr.addField("descdend", "/anycd/contenido//boo",         true);
+
+    // same test string as above but checking to see if *some* //boo's are 
collected
+    String xml="<anycd>\n"
+             + "  this <boo>top level</boo> is ignored because it is external 
to the forEach\n"
+             + "  <status>as is <boo>this element</boo></status>\n"
+             + "  <contenido id=\"10097\" idioma=\"cat\">\n"
+             + "    This one is <boo>not ignored as its</boo> inside a 
forEach\n"
+             + "    <antetitulo><i> big <boo>antler</boo></i></antetitulo>\n"
+             + "    <titulo>  My <i>flattened <boo>title</boo></i> </titulo>\n"
+             + "    <resumen> My summary <i>skip this!</i>  </resumen>\n"
+             + "    <texto>   <boo>Within the body of</boo>My text</texto>\n"
+             + "    <p>Access <boo>inner <i>sub clauses</i> as 
well</boo></p>\n"
+             + "    </contenido>\n"
+             + "</anycd>";
+
+    List<Map<String, Object>> l = rr.getAllRecords(new StringReader(xml));
+    Assert.assertEquals(1, l.size());
+    Map<String, Object> m = l.get(0);
+    Assert.assertEquals("not ignored as its",((List) m.get("descdend")).get(0) 
);
+    Assert.assertEquals("antler"            ,((List) m.get("descdend")).get(1) 
);
+    Assert.assertEquals("title"             ,((List) m.get("descdend")).get(2) 
);
+    Assert.assertEquals("Within the body of",((List) m.get("descdend")).get(3) 
);
+    Assert.assertEquals("inner  as well"    ,((List) m.get("descdend")).get(4) 
);
+  }
+  
+  @Test
   public void another() {
-    String xml = "<root>\n"
+    String xml="<root>\n"
             + "       <contenido id=\"10097\" idioma=\"cat\">\n"
-            + "       <antetitulo></antetitulo>\n" + "       <titulo>\n"
-            + "               This is my title\n" + "       </titulo>\n"
-            + "       <resumen>\n" + "               This is my summary\n"
-            + "       </resumen>\n" + "       <texto>\n"
-            + "               This is the body of my text\n" + "       
</texto>\n"
-            + "       </contenido>\n" + "</root>";
+             + "    <antetitulo></antetitulo>\n"
+             + "    <titulo>    This is my title             </titulo>\n"
+             + "    <resumen>   This is my summary           </resumen>\n"
+             + "    <texto>     This is the body of my text  </texto>\n"
+             + "    </contenido>\n"
+             + "</root>";
     XPathRecordReader rr = new XPathRecordReader("/root/contenido");
     rr.addField("id", "/root/contenido/@id", false);
     rr.addField("title", "/root/contenido/titulo", false);
-    rr.addField("resume", "/root/contenido/resumen", false);
+    rr.addField("resume","/root/contenido/resumen",false);
     rr.addField("text", "/root/contenido/texto", false);
 
     List<Map<String, Object>> l = rr.getAllRecords(new StringReader(xml));
     Assert.assertEquals(1, l.size());
     Map<String, Object> m = l.get(0);
-    Assert.assertEquals("10097", m.get("id").toString().trim());
+    Assert.assertEquals("10097", m.get("id"));
     Assert.assertEquals("This is my title", m.get("title").toString().trim());
-    Assert
-            .assertEquals("This is my summary", 
m.get("resume").toString().trim());
+    Assert.assertEquals("This is my summary", 
m.get("resume").toString().trim());
     Assert.assertEquals("This is the body of my text", m.get("text").toString()
             .trim());
   }
+
   @Test
   public void sameForEachAndXpath(){
     String xml="<root>\n" +
@@ -367,4 +540,31 @@
     Assert.assertEquals("B.2.2",b.get(1));
     Assert.assertEquals("C.2.2",c.get(1));
   }
+
+
+ @Test
+  public void testError(){
+    String malformedXml = "<root>\n" +
+          "    <node>\n" +
+          "        <id>1</id>\n" +
+          "        <desc>test1</desc>\n" +
+          "    </node>\n" +
+          "    <node>\n" +
+          "        <id>2</id>\n" +
+          "        <desc>test2</desc>\n" +
+          "    </node>\n" +
+          "    <node>\n" +
+          "        <id/>3</id>\n" +   // invalid XML
+          "        <desc>test3</desc>\n" +
+          "    </node>\n" +
+          "</root>";
+    XPathRecordReader rr = new XPathRecordReader("/root/node");
+    rr.addField("id", "/root/node/id", true);
+    rr.addField("desc", "/root/node/desc", true);
+    try {
+      rr.getAllRecords(new StringReader(malformedXml));
+      Assert.fail("A RuntimeException was expected: the input XML is 
invalid.");
+      }
+    catch (Exception e) { }
+ }
 }


Reply via email to