Coverage Report - org.simpleframework.xml.core.PathParser
 
Classes in this File Line Coverage Branch Coverage Complexity
PathParser
97%
182/187
87%
89/102
2.491
PathParser$PathSection
51%
29/56
41%
15/36
2.491
 
 1  
 /*
 2  
  * PathParser.java November 2010
 3  
  *
 4  
  * Copyright (C) 2010, Niall Gallagher <niallg@users.sf.net>
 5  
  *
 6  
  * Licensed under the Apache License, Version 2.0 (the "License");
 7  
  * you may not use this file except in compliance with the License.
 8  
  * You may obtain a copy of the License at
 9  
  *
 10  
  *     http://www.apache.org/licenses/LICENSE-2.0
 11  
  *
 12  
  * Unless required by applicable law or agreed to in writing, software
 13  
  * distributed under the License is distributed on an "AS IS" BASIS,
 14  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 
 15  
  * implied. See the License for the specific language governing 
 16  
  * permissions and limitations under the License.
 17  
  */
 18  
 
 19  
 package org.simpleframework.xml.core;
 20  
 
 21  
 import java.util.ArrayList;
 22  
 import java.util.Iterator;
 23  
 import java.util.List;
 24  
 
 25  
 import org.simpleframework.xml.strategy.Type;
 26  
 import org.simpleframework.xml.stream.Format;
 27  
 import org.simpleframework.xml.stream.Style;
 28  
 import org.simpleframework.xml.util.Cache;
 29  
 import org.simpleframework.xml.util.ConcurrentCache;
 30  
 
 31  
 /**
 32  
  * The <code>PathParser</code> object is used to parse XPath paths.
 33  
  * This will parse a subset of the XPath expression syntax, such
 34  
  * that the path can be used to identify and navigate various XML
 35  
  * structures. Example paths that this can parse are as follows.
 36  
  * <pre>
 37  
  * 
 38  
  *    ./example/path
 39  
  *    ./example[2]/path/
 40  
  *    example/path
 41  
  *    example/path/@attribute
 42  
  *    ./path/@attribute 
 43  
  *    
 44  
  * </pre>
 45  
  * If the parsed path does not match an XPath expression similar to
 46  
  * the above then an exception is thrown. Once parsed the segments
 47  
  * of the path can be used to traverse data structures modelled on
 48  
  * an XML document or fragment.
 49  
  * 
 50  
  * @author Niall Gallagher
 51  
  * 
 52  
  * @see org.simpleframework.xml.core.ExpressionBuilder
 53  
  */
 54  
 class PathParser implements Expression {
 55  
    
 56  
    /**
 57  
     * This is used to cache the attributes created by this path.
 58  
     */
 59  
    protected Cache<String> attributes;
 60  
    
 61  
    /**
 62  
     * This is used to cache the elements created by this path.
 63  
     */
 64  
    protected Cache<String> elements;
 65  
    
 66  
    /**
 67  
     * This contains a list of the indexes for each path segment.
 68  
     */
 69  
    protected List<Integer> indexes;   
 70  
    
 71  
    /**
 72  
     * This is used to store the path prefixes for the parsed path.
 73  
     */
 74  
    protected List<String> prefixes;
 75  
    
 76  
    /**
 77  
     * This contains a list of the path segments that were parsed.
 78  
     */
 79  
    protected List<String> names;
 80  
    
 81  
    /**
 82  
     * This is used to build a fully qualified path expression.
 83  
     */
 84  
    protected StringBuilder builder;
 85  
    
 86  
    /**
 87  
     * This is the fully qualified path expression for this.
 88  
     */
 89  
    protected String location;
 90  
    
 91  
    /**
 92  
     * This is the the cached canonical representation of the path.
 93  
     */
 94  
    protected String cache;
 95  
    
 96  
    /**
 97  
     * This is a cache of the canonical path representation.
 98  
     */
 99  
    protected String path;
 100  
    
 101  
    
 102  
    /**
 103  
     * This is the format used to style the path segments.
 104  
     */
 105  
    protected Style style;
 106  
    
 107  
    /**
 108  
     * This is the type the expressions are to be parsed for.
 109  
     */
 110  
    protected Type type;
 111  
    
 112  
    /**
 113  
     * This is used to determine if the path is an attribute.
 114  
     */
 115  
    protected boolean attribute;
 116  
    
 117  
    /**
 118  
     * This is a copy of the source data that is to be parsed.
 119  
     */
 120  
    protected char[] data;
 121  
    
 122  
    /**
 123  
     * This represents the number of characters in the source path.
 124  
     */
 125  
    protected int count;
 126  
    
 127  
    /**
 128  
     * This is the start offset that skips any root references.
 129  
     */
 130  
    protected int start;
 131  
  
 132  
    /**
 133  
     * This is the current seek position for the parser.
 134  
     */
 135  
    protected int off;
 136  
 
 137  
    /**
 138  
     * Constructor for the <code>PathParser</code> object. This must
 139  
     * be given a valid XPath expression. Currently only a subset of
 140  
     * the XPath syntax is supported by this parser. Once finished
 141  
     * the parser will contain all the extracted path segments.
 142  
     * 
 143  
     * @param path this is the XPath expression to be parsed
 144  
     * @param type this is the type the expressions are parsed for
 145  
     * @param format this is the format used to style the path
 146  
     */
 147  1130
    public PathParser(String path, Type type, Format format) throws Exception {
 148  1130
       this.attributes = new ConcurrentCache<String>();
 149  1130
       this.elements = new ConcurrentCache<String>();
 150  1130
       this.indexes = new ArrayList<Integer>();
 151  1130
       this.prefixes = new ArrayList<String>();
 152  1130
       this.names = new ArrayList<String>();
 153  1130
       this.builder = new StringBuilder();
 154  1130
       this.style = format.getStyle();
 155  1130
       this.type = type;
 156  1130
       this.path = path;
 157  1130
       this.parse(path);
 158  1116
    }
 159  
    
 160  
    /**
 161  
     * This method is used to determine if this expression is an
 162  
     * empty path. An empty path can be represented by a single
 163  
     * period, '.'. It identifies the current path.
 164  
     * 
 165  
     * @return returns true if this represents an empty path
 166  
     */
 167  
    public boolean isEmpty() {
 168  423
       return isEmpty(location);
 169  
    }
 170  
    
 171  
    /**
 172  
     * This is used to determine if the expression is a path. An
 173  
     * expression represents a path if it contains more than one
 174  
     * segment. If only one segment exists it is an element name.
 175  
     * 
 176  
     * @return true if this contains more than one segment
 177  
     */
 178  
    public boolean isPath() {
 179  1025
       return names.size() > 1;
 180  
    }
 181  
    
 182  
    /**
 183  
     * This is used to determine if the expression points to an
 184  
     * attribute value. An attribute value contains an '@' character
 185  
     * before the last segment name. Such expressions distinguish
 186  
     * element references from attribute references.
 187  
     * 
 188  
     * @return this returns true if the path has an attribute
 189  
     */
 190  
    public boolean isAttribute() {
 191  164
       return attribute;
 192  
    }
 193  
    
 194  
    /**
 195  
     * If the first path segment contains an index it is provided
 196  
     * by this method. There may be several indexes within a 
 197  
     * path, however only the index at the first segment is issued
 198  
     * by this method. If there is no index this will return 1.
 199  
     * 
 200  
     * @return this returns the index of this path expression
 201  
     */
 202  
    public int getIndex() {
 203  875
       return indexes.get(0);
 204  
    }
 205  
    
 206  
    /**
 207  
     * This is used to extract a namespace prefix from the path
 208  
     * expression. A prefix is used to qualify the XML element name
 209  
     * and does not form part of the actual path structure. This
 210  
     * can be used to add the namespace in addition to the name.
 211  
     * 
 212  
     * @return this returns the prefix for the path expression
 213  
     */
 214  
    public String getPrefix(){
 215  366
       return prefixes.get(0);
 216  
    }
 217  
    
 218  
    /**
 219  
     * This can be used to acquire the first path segment within
 220  
     * the expression. The first segment represents the parent XML
 221  
     * element of the path. All segments returned do not contain
 222  
     * any slashes and so represents the real element name.
 223  
     * 
 224  
     * @return this returns the parent element for the path
 225  
     */
 226  
    public String getFirst() {
 227  797
       return names.get(0);
 228  
    }
 229  
    
 230  
    /**
 231  
     * This can be used to acquire the last path segment within
 232  
     * the expression. The last segment represents the leaf XML
 233  
     * element of the path. All segments returned do not contain
 234  
     * any slashes and so represents the real element name.
 235  
     * 
 236  
     * @return this returns the leaf element for the path
 237  
     */ 
 238  
    public String getLast() {
 239  168
       int count = names.size();
 240  168
       int index = count - 1;
 241  
       
 242  168
       return names.get(index);
 243  
    }
 244  
 
 245  
    /**
 246  
     * This location contains the full path expression with all
 247  
     * of the indexes explicitly shown for each path segment. This
 248  
     * is used to create a uniform representation that can be used
 249  
     * for comparisons of different path expressions. 
 250  
     * 
 251  
     * @return this returns an expanded version of the path
 252  
     */
 253  
    public String getPath() {
 254  518
       return location;
 255  
    }
 256  
    
 257  
    /**
 258  
     * This is used to acquire the element path using this XPath
 259  
     * expression. The element path is simply the fully qualified
 260  
     * path for this expression with the provided name appended.
 261  
     * If this is an empty path, the provided name is returned.
 262  
     * 
 263  
     * @param name this is the name of the element to be used
 264  
     * 
 265  
     * @return a fully qualified path for the specified name
 266  
     */
 267  
    public String getElement(String name) {
 268  2203
       if(!isEmpty(location)) {
 269  2201
          String path = elements.fetch(name); 
 270  
          
 271  2201
          if(path == null) {
 272  1011
             path = getElementPath(location, name);
 273  
             
 274  1011
             if(path != null) {
 275  1011
                elements.cache(name, path);
 276  
             }
 277  
          }
 278  2201
          return path;
 279  
       }
 280  2
       return style.getElement(name);
 281  
    }
 282  
    
 283  
    /**
 284  
     * This is used to acquire the element path using this XPath
 285  
     * expression. The element path is simply the fully qualified
 286  
     * path for this expression with the provided name appended.
 287  
     * If this is an empty path, the provided name is returned.
 288  
     * 
 289  
     * @param path this is the path expression to be used
 290  
     * @param name this is the name of the element to be used
 291  
     * 
 292  
     * @return a fully qualified path for the specified name
 293  
     */
 294  
    protected String getElementPath(String path, String name) {
 295  1011
       String element = style.getElement(name);
 296  
       
 297  1011
       if(isEmpty(element)) {
 298  0
          return path;
 299  
       }
 300  1011
       if(isEmpty(path)) {
 301  0
          return element;
 302  
       }
 303  1011
       return path + "/"+ element+"[1]";
 304  
    }
 305  
    
 306  
    /**
 307  
     * This is used to acquire the attribute path using this XPath
 308  
     * expression. The attribute path is simply the fully qualified
 309  
     * path for this expression with the provided name appended.
 310  
     * If this is an empty path, the provided name is returned.
 311  
     * 
 312  
     * @param name this is the name of the attribute to be used
 313  
     * 
 314  
     * @return a fully qualified path for the specified name
 315  
     */
 316  
    public String getAttribute(String name) {
 317  466
       if(!isEmpty(location)) {
 318  464
          String path = attributes.fetch(name); 
 319  
          
 320  464
          if(path == null) {
 321  134
             path = getAttributePath(location, name);
 322  
             
 323  134
             if(path != null) {
 324  134
                attributes.cache(name, path);
 325  
             }
 326  
          }
 327  464
          return path;
 328  
       }
 329  2
       return style.getAttribute(name);
 330  
    }
 331  
    
 332  
    /**
 333  
     * This is used to acquire the attribute path using this XPath
 334  
     * expression. The attribute path is simply the fully qualified
 335  
     * path for this expression with the provided name appended.
 336  
     * If this is an empty path, the provided name is returned.
 337  
     * 
 338  
     * @param path this is the path expression to be used
 339  
     * @param name this is the name of the attribute to be used
 340  
     * 
 341  
     * @return a fully qualified path for the specified name
 342  
     */
 343  
    protected String getAttributePath(String path, String name) {
 344  134
       String attribute = style.getAttribute(name);
 345  
             
 346  134
       if(isEmpty(path)) { 
 347  0
          return attribute;
 348  
       }
 349  134
       return path +"/@" +attribute;
 350  
    }
 351  
 
 352  
    /**
 353  
     * This is used to iterate over the path segments that have 
 354  
     * been extracted from the source XPath expression. Iteration
 355  
     * over the segments is done in the order they were parsed
 356  
     * from the source path.
 357  
     * 
 358  
     * @return this returns an iterator for the path segments
 359  
     */
 360  
    public Iterator<String> iterator() {
 361  0
       return names.iterator();
 362  
    }
 363  
    
 364  
    /**
 365  
     * This allows an expression to be extracted from the current
 366  
     * context. Extracting expressions in this manner makes it 
 367  
     * more convenient for navigating structures representing
 368  
     * the XML document. If an expression can not be extracted
 369  
     * with the given criteria an exception will be thrown.
 370  
     * 
 371  
     * @param from this is the number of segments to skip to
 372  
     * 
 373  
     * @return this returns an expression from this one
 374  
     */
 375  
    public Expression getPath(int from) {  
 376  226
       return getPath(from, 0);
 377  
    }
 378  
    
 379  
    /**
 380  
     * This allows an expression to be extracted from the current
 381  
     * context. Extracting expressions in this manner makes it 
 382  
     * more convenient for navigating structures representing
 383  
     * the XML document. If an expression can not be extracted
 384  
     * with the given criteria an exception will be thrown.
 385  
     * 
 386  
     * @param from this is the number of segments to skip to
 387  
     * @param trim the number of segments to trim from the end
 388  
     * 
 389  
     * @return this returns an expression from this one
 390  
     */
 391  
    public Expression getPath(int from, int trim) {
 392  604
       int last = names.size() - 1;
 393  
       
 394  604
       if(last- trim >= from) {       
 395  497
          return new PathSection(from, last -trim);
 396  
       }
 397  107
       return new PathSection(from, from);
 398  
    }
 399  
 
 400  
    /**
 401  
     * This method is used to parse the provided XPath expression.
 402  
     * When parsing the expression this will trim any references
 403  
     * to the root context, also any trailing slashes are removed.
 404  
     * An exception is thrown if the path is invalid.
 405  
     * 
 406  
     * @param path this is the XPath expression to be parsed
 407  
     */
 408  
    private void parse(String path) throws Exception {
 409  1130
       if(path != null) {
 410  1130
          count = path.length();
 411  1130
          data = new char[count];
 412  1130
          path.getChars(0, count, data, 0);
 413  
       }
 414  1130
       path();
 415  1116
    }
 416  
    
 417  
    /**
 418  
     * This method is used to parse the provided XPath expression.
 419  
     * When parsing the expression this will trim any references
 420  
     * to the root context, also any trailing slashes are removed.
 421  
     * An exception is thrown if the path is invalid.
 422  
     */ 
 423  
    private void path() throws Exception {
 424  1130
       if(data[off] == '/') {
 425  1
          throw new PathException("Path '%s' in %s references document root", path, type);
 426  
       }
 427  1129
       if(data[off] == '.') {
 428  20
          skip();
 429  
       }
 430  2962
       while (off < count) {
 431  1846
          if(attribute) {
 432  0
             throw new PathException("Path '%s' in %s references an invalid attribute", path, type);
 433  
          }
 434  1846
          segment();
 435  
       }  
 436  1116
       truncate();
 437  1116
       build();
 438  1116
    }
 439  
    
 440  
    /**
 441  
     * This method is used to build a fully qualified path that has
 442  
     * each segment index. Building a path in this manner ensures
 443  
     * that a parsed path can have a unique string that identifies 
 444  
     * the exact XML element the expression points to. 
 445  
     */
 446  
    private void build() {
 447  1116
       int count = names.size();
 448  1116
       int last = count - 1;
 449  
       
 450  2937
       for(int i = 0; i < count; i++) {
 451  1821
          String prefix = prefixes.get(i);
 452  1821
          String segment = names.get(i);
 453  1821
          int index = indexes.get(i);
 454  
          
 455  1821
          if(i > 0) {
 456  708
             builder.append('/');
 457  
          } 
 458  1821
          if(attribute && i == last) {
 459  43
             builder.append('@');
 460  43
             builder.append(segment);           
 461  
          } else {
 462  1778
             if(prefix != null) {
 463  401
                builder.append(prefix);
 464  401
                builder.append(':');
 465  
             }
 466  1778
             builder.append(segment);
 467  1778
             builder.append('[');
 468  1778
             builder.append(index);
 469  1778
             builder.append(']');
 470  
          }
 471  
       }
 472  1116
       location = builder.toString();
 473  1116
    }
 474  
 
 475  
    /**
 476  
     * This is used to skip any root prefix for the path. Skipping 
 477  
     * the root prefix ensures that it is not considered as a valid
 478  
     * path segment and so is not returned as part of the iterator
 479  
     * nor is it considered with building a string representation.
 480  
     */
 481  
    private void skip() throws Exception {
 482  20
       if (data.length > 1) {
 483  17
          if (data[off + 1] != '/') {
 484  2
             throw new PathException("Path '%s' in %s has an illegal syntax", path, type);
 485  
          }
 486  15
          off++;
 487  
       }      
 488  18
       start = ++off;
 489  18
    }
 490  
 
 491  
    /**
 492  
     * This method is used to extract a path segment from the source
 493  
     * expression. Before extracting the segment this validates the
 494  
     * input to ensure it represents a valid path. If the path is
 495  
     * not valid then this will thrown an exception.
 496  
     */
 497  
    private void segment() throws Exception {
 498  1846
       char first = data[off];
 499  
 
 500  1846
       if(first == '/') {
 501  3
          throw new PathException("Invalid path expression '%s' in %s", path, type);
 502  
       }
 503  1843
       if(first == '@') {
 504  48
          attribute();
 505  
       } else {
 506  1795
          element();
 507  
       }
 508  1835
       align();
 509  1835
    }
 510  
    
 511  
    /**
 512  
     * This is used to extract an element from the path expression. 
 513  
     * An element value is one that contains only alphanumeric values
 514  
     * or any special characters allowed within an XML element name.
 515  
     * If an illegal character is found an exception is thrown.
 516  
     */
 517  
    private void element() throws Exception { 
 518  1795
       int mark = off;
 519  1795
       int size = 0;
 520  
       
 521  10086
       while(off < count) {         
 522  9410
          char value = data[off++];
 523  
          
 524  9410
          if(!isValid(value)) {
 525  1119
             if(value == '@') {
 526  2
                off--;
 527  2
                break;
 528  1117
             } else if(value == '[') {
 529  521
                index();            
 530  519
                break;
 531  596
             } else if(value != '/') { 
 532  1
                throw new PathException("Illegal character '%s' in element for '%s' in %s", value, path, type);
 533  
             }         
 534  
             break;
 535  
          }
 536  8291
          size++;         
 537  8291
       }
 538  1792
       element(mark, size);
 539  1792
    }
 540  
    
 541  
    /**
 542  
     * This is used to extract an attribute from the path expression. 
 543  
     * An attribute value is one that contains only alphanumeric values
 544  
     * or any special characters allowed within an XML attribute name.
 545  
     * If an illegal character is found an exception is thrown.
 546  
     */
 547  
    private void attribute() throws Exception {
 548  48
       int mark = ++off;      
 549  
       
 550  152
       while(off < count) {
 551  107
          char value = data[off++];
 552  
          
 553  107
          if(!isValid(value)) {
 554  3
             throw new PathException("Illegal character '%s' in attribute for '%s' in %s", value, path, type);
 555  
          }         
 556  104
       }
 557  45
       if(off <= mark) {
 558  2
          throw new PathException("Attribute reference in '%s' for %s is empty", path, type);
 559  
       } else {
 560  43
          attribute = true;
 561  
       }
 562  43
       attribute(mark, off - mark);
 563  43
    }
 564  
    
 565  
    
 566  
    /**
 567  
     * This is used to extract an index from an element. An index is
 568  
     * a numerical value that identifies the position of the path
 569  
     * within the XML document. If the index can not be extracted
 570  
     * from the expression an exception is thrown.
 571  
     */
 572  
    private void index() throws Exception {
 573  521
       int value = 0;
 574  
 
 575  521
       if(data[off-1] == '[') {
 576  1050
          while(off < count) {
 577  1049
             char digit = data[off++];
 578  
             
 579  1049
             if(!isDigit(digit)){
 580  520
                break;
 581  
             }
 582  529
             value *= 10;
 583  529
             value += digit;
 584  529
             value -= '0';  
 585  529
          }
 586  
       }
 587  521
       if(data[off++ - 1] != ']') {
 588  2
          throw new PathException("Invalid index for path '%s' in %s", path, type);
 589  
       }
 590  519
       indexes.add(value);
 591  519
    }
 592  
    
 593  
    
 594  
    /**
 595  
     * This method is used to trim any trailing characters at the
 596  
     * end of the path. Trimming will remove any trailing legal
 597  
     * characters at the end of the path that we do not want in a
 598  
     * canonical string representation of the path expression. 
 599  
     */
 600  
    private void truncate() throws Exception {
 601  1116
       if(off - 1 >= data.length) {
 602  392
          off--;
 603  724
       } else if(data[off-1] == '/'){
 604  2
          off--;
 605  
       }
 606  1116
    }
 607  
    
 608  
    /**
 609  
     * This is used to add a default index to a segment or attribute
 610  
     * extracted from the source expression. In the event that a
 611  
     * segment does not contain an index, the default index of 1 is
 612  
     * assigned to the element for consistency.
 613  
     */
 614  
    private void align() throws Exception {
 615  1835
       int require = names.size();
 616  1835
       int size = indexes.size();
 617  
       
 618  1835
       if(require > size) {
 619  1316
          indexes.add(1);
 620  
       }
 621  1835
    }
 622  
    
 623  
    /**
 624  
     * This is used to determine if a string is empty. A string is
 625  
     * considered empty if it is null or of zero length. 
 626  
     * 
 627  
     * @param text this is the text to check if it is empty
 628  
     * 
 629  
     * @return this returns true if the string is empty or null
 630  
     */
 631  
    private boolean isEmpty(String text) {
 632  5248
       return text == null || text.length() == 0;
 633  
    }
 634  
    
 635  
    /** 
 636  
     * This is used to determine if the provided character is a digit.
 637  
     * Only digits can be used within a segment index, so this is used
 638  
     * when parsing the index to ensure all characters are valid.     
 639  
     * 
 640  
     * @param value this is the value of the character
 641  
     * 
 642  
     * @return this returns true if the provide character is a digit
 643  
     */
 644  
    private boolean isDigit(char value) {
 645  1049
       return Character.isDigit(value);
 646  
    }
 647  
    
 648  
    /**
 649  
     * This is used to determine if the provided character is a legal
 650  
     * XML element character. This is used to ensure all extracted
 651  
     * element names conform to legal element names.
 652  
     * 
 653  
     * @param value this is the value of the character
 654  
     * 
 655  
     * @return this returns true if the provided character is legal
 656  
     */
 657  
    private boolean isValid(char value) {
 658  9517
       return isLetter(value) || isSpecial(value);
 659  
    }
 660  
    
 661  
    /**
 662  
     * This is used to determine if the provided character is a legal
 663  
     * XML element character. This is used to ensure all extracted
 664  
     * element and attribute names conform to the XML specification.
 665  
     * 
 666  
     * @param value this is the value of the character
 667  
     * 
 668  
     * @return this returns true if the provided character is legal
 669  
     */
 670  
    private boolean isSpecial(char value) {
 671  1555
       return value == '_' || value == '-' || value == ':';
 672  
    }
 673  
    
 674  
    /**
 675  
     * This is used to determine if the provided character is an
 676  
     * alpha numeric character. This is used to ensure all extracted
 677  
     * element and attribute names conform to the XML specification.
 678  
     * 
 679  
     * @param value this is the value of the character
 680  
     * 
 681  
     * @return this returns true if the provided character is legal
 682  
     */
 683  
    private boolean isLetter(char value) {
 684  9517
       return Character.isLetterOrDigit(value);
 685  
    }
 686  
 
 687  
    /**
 688  
     * This will add a path segment to the list of segments. A path
 689  
     * segment is added only if it has at least one character. All
 690  
     * segments can be iterated over when parsing has completed.
 691  
     * 
 692  
     * @param start this is the start offset for the path segment
 693  
     * @param count this is the number of characters in the segment
 694  
     */
 695  
    private void element(int start, int count) {
 696  1792
       String segment = new String(data, start, count);
 697  
 
 698  1792
       if(count > 0) {
 699  1792
          element(segment);
 700  
       }
 701  1792
    }
 702  
    
 703  
    /**
 704  
     * This will add a path segment to the list of segments. A path
 705  
     * segment is added only if it has at least one character. All
 706  
     * segments can be iterated over when parsing has completed.
 707  
     * 
 708  
     * @param start this is the start offset for the path segment
 709  
     * @param count this is the number of characters in the segment
 710  
     */
 711  
    private void attribute(int start, int count) {
 712  43
       String segment = new String(data, start, count);
 713  
 
 714  43
       if(count > 0) {
 715  43
          attribute(segment);
 716  
       }
 717  43
    }
 718  
    
 719  
    /**
 720  
     * This will insert the path segment provided. A path segment is
 721  
     * represented by an optional namespace prefix and an XML element
 722  
     * name. If there is no prefix then a null is entered this will
 723  
     * ensure that the names and segments are kept aligned by index.
 724  
     * 
 725  
     * @param segment this is the path segment to be inserted
 726  
     */
 727  
    private void element(String segment) {
 728  1792
       int index = segment.indexOf(':');
 729  1792
       String prefix = null;
 730  
       
 731  1792
       if(index > 0) {
 732  401
          prefix = segment.substring(0, index);
 733  401
          segment = segment.substring(index+1);
 734  
       }
 735  1792
       String element = style.getElement(segment);
 736  
       
 737  1792
       prefixes.add(prefix);
 738  1792
       names.add(element);
 739  1792
    }
 740  
    
 741  
    /**
 742  
     * This will insert the path segment provided. A path segment is
 743  
     * represented by an optional namespace prefix and an XML element
 744  
     * name. If there is no prefix then a null is entered this will
 745  
     * ensure that the names and segments are kept aligned by index.
 746  
     * 
 747  
     * @param segment this is the path segment to be inserted
 748  
     */
 749  
    private void attribute(String segment) {
 750  43
       String attribute = style.getAttribute(segment);
 751  
       
 752  43
       prefixes.add(null);
 753  43
       names.add(attribute);
 754  43
    }
 755  
    
 756  
    /**
 757  
     * Provides a canonical XPath expression. This is used for both
 758  
     * debugging and reporting. The path returned represents the 
 759  
     * original path that has been parsed to form the expression.
 760  
     * 
 761  
     * @return this returns the string format for the XPath
 762  
     */
 763  
    public String toString() {
 764  15
       int size = off - start;
 765  
       
 766  15
       if(cache == null) {
 767  15
          cache = new String(data, start, size);
 768  
       }
 769  15
       return cache;
 770  
    } 
 771  
    
 772  
    /**
 773  
     * The <code>PathSection</code> represents a section of a path 
 774  
     * that is extracted. Providing a section allows the expression
 775  
     * to be broken up in to smaller parts without having to parse
 776  
     * the path again. This is used primarily for better performance.
 777  
     * 
 778  
     * @author Niall Gallagher
 779  
     */
 780  
    private class PathSection implements Expression {
 781  
       
 782  
       /**
 783  
        * This contains a cache of the path segments of the section.
 784  
        */
 785  
       private List<String> cache;
 786  
       
 787  
       /**
 788  
        * This is the fragment of the original path this section uses.
 789  
        */
 790  
       private String section;
 791  
       
 792  
       /**
 793  
        * This contains a cache of the canonical path representation.
 794  
        */
 795  
       private String path;
 796  
       
 797  
       /**
 798  
        * This is the first section index for this path section.
 799  
        */
 800  
       private int begin;
 801  
       
 802  
       /**
 803  
        * This is the last section index for this path section.
 804  
        */
 805  
       private int end;
 806  
       
 807  
       /**
 808  
        * Constructor for the <code>PathSection</code> object. A path
 809  
        * section represents a section of an original path expression.
 810  
        * To create a section the first and last segment index needs
 811  
        * to be provided.
 812  
        * 
 813  
        * @param index this is the first path segment index 
 814  
        * @param end this is the last path segment index
 815  
        */
 816  913
       public PathSection(int index, int end) {
 817  913
          this.cache = new ArrayList<String>();
 818  913
          this.begin = index;         
 819  913
          this.end = end;
 820  913
       }
 821  
       
 822  
       /**
 823  
        * This method is used to determine if this expression is an
 824  
        * empty path. An empty path can be represented by a single
 825  
        * period, '.'. It identifies the current path.
 826  
        * 
 827  
        * @return returns true if this represents an empty path
 828  
        */
 829  
       public boolean isEmpty() {
 830  1
          return begin == end;
 831  
       }
 832  
       
 833  
       /**
 834  
        * This is used to determine if the expression is a path. An
 835  
        * expression represents a path if it contains more than one
 836  
        * segment. If only one segment exists it is an element name.
 837  
        * 
 838  
        * @return true if this contains more than one segment
 839  
        */
 840  
       public boolean isPath() {
 841  698
          return end - begin >= 1;
 842  
       }
 843  
       
 844  
       /**
 845  
        * This is used to determine if the expression points to an
 846  
        * attribute value. An attribute value contains an '@' character
 847  
        * before the last segment name. Such expressions distinguish
 848  
        * element references from attribute references.
 849  
        * 
 850  
        * @return this returns true if the path has an attribute
 851  
        */
 852  
       public boolean isAttribute() {
 853  6
          if(attribute) {
 854  2
             return end >= names.size() - 1;
 855  
          }
 856  4
          return false;
 857  
       }
 858  
       
 859  
       /**
 860  
        * This location contains the full path expression with all
 861  
        * of the indexes explicitly shown for each path segment. This
 862  
        * is used to create a uniform representation that can be used
 863  
        * for comparisons of different path expressions. 
 864  
        * 
 865  
        * @return this returns an expanded version of the path
 866  
        */
 867  
       public String getPath() {
 868  0
          if(section == null) {
 869  0
             section = getCanonicalPath();
 870  
          }
 871  0
          return section;
 872  
       }
 873  
       
 874  
       /**
 875  
        * This is used to acquire the element path using this XPath
 876  
        * expression. The element path is simply the fully qualified
 877  
        * path for this expression with the provided name appended.
 878  
        * If this is an empty path, the provided name is returned.
 879  
        * 
 880  
        * @param name this is the name of the element to be used
 881  
        * 
 882  
        * @return a fully qualified path for the specified name
 883  
        */
 884  
       public String getElement(String name) {
 885  0
          String path = getPath();
 886  
          
 887  0
          if(path != null) {
 888  0
             return getElementPath(path, name);
 889  
          }
 890  0
          return name;
 891  
       }
 892  
       
 893  
       /**
 894  
        * This is used to acquire the attribute path using this XPath
 895  
        * expression. The attribute path is simply the fully qualified
 896  
        * path for this expression with the provided name appended.
 897  
        * If this is an empty path, the provided name is returned.
 898  
        * 
 899  
        * @param name this is the name of the attribute to be used
 900  
        * 
 901  
        * @return a fully qualified path for the specified name
 902  
        */
 903  
       public String getAttribute(String name) {
 904  0
          String path = getPath();
 905  
          
 906  0
          if(path != null) {
 907  0
             return getAttributePath(path, name);
 908  
          }
 909  0
          return name;
 910  
       }
 911  
       
 912  
       /**
 913  
        * If the first path segment contains an index it is provided
 914  
        * by this method. There may be several indexes within a 
 915  
        * path, however only the index at the first segment is issued
 916  
        * by this method. If there is no index this will return 1.
 917  
        * 
 918  
        * @return this returns the index of this path expression
 919  
        */
 920  
       public int getIndex() {
 921  785
          return indexes.get(begin);
 922  
       }
 923  
       
 924  
       /**
 925  
        * This is used to extract a namespace prefix from the path
 926  
        * expression. A prefix is used to qualify the XML element name
 927  
        * and does not form part of the actual path structure. This
 928  
        * can be used to add the namespace in addition to the name.
 929  
        * 
 930  
        * @return this returns the prefix for the path expression
 931  
        */
 932  
       public String getPrefix() {
 933  388
          return prefixes.get(begin);
 934  
       }
 935  
       
 936  
       /**
 937  
        * This can be used to acquire the first path segment within
 938  
        * the expression. The first segment represents the parent XML
 939  
        * element of the path. All segments returned do not contain
 940  
        * any slashes and so represents the real element name.
 941  
        * 
 942  
        * @return this returns the parent element for the path
 943  
        */
 944  
       public String getFirst() {
 945  833
          return names.get(begin);
 946  
       }
 947  
       
 948  
       /**
 949  
        * This can be used to acquire the last path segment within
 950  
        * the expression. The last segment represents the leaf XML
 951  
        * element of the path. All segments returned do not contain
 952  
        * any slashes and so represents the real element name.
 953  
        * 
 954  
        * @return this returns the leaf element for the path
 955  
        */ 
 956  
       public String getLast() {
 957  19
          return names.get(end);
 958  
       }
 959  
       
 960  
       /**
 961  
        * This allows an expression to be extracted from the current
 962  
        * context. Extracting expressions in this manner makes it 
 963  
        * more convenient for navigating structures representing
 964  
        * the XML document. If an expression can not be extracted
 965  
        * with the given criteria an exception will be thrown.
 966  
        * 
 967  
        * @param from this is the number of segments to skip to
 968  
        * 
 969  
        * @return this returns an expression from this one
 970  
        */
 971  
       public Expression getPath(int from) {     
 972  164
          return getPath(from, 0);
 973  
       }
 974  
       
 975  
       /**
 976  
        * This allows an expression to be extracted from the current
 977  
        * context. Extracting expressions in this manner makes it 
 978  
        * more convenient for navigating structures representing
 979  
        * the XML document. If an expression can not be extracted
 980  
        * with the given criteria an exception will be thrown.
 981  
        * 
 982  
        * @param from this is the number of segments to skip to
 983  
        * @param trim the number of segments to trim from the end
 984  
        * 
 985  
        * @return this returns an expression from this one
 986  
        */
 987  
       public Expression getPath(int from, int trim) {
 988  309
          return new PathSection(begin + from, end - trim);
 989  
       }
 990  
       
 991  
       /**
 992  
        * This is used to iterate over the path segments that have 
 993  
        * been extracted from the source XPath expression. Iteration
 994  
        * over the segments is done in the order they were parsed
 995  
        * from the source path.
 996  
        * 
 997  
        * @return this returns an iterator for the path segments
 998  
        */
 999  
       public Iterator<String> iterator() {
 1000  0
          if(cache.isEmpty()) {
 1001  0
             for(int i = begin; i <= end; i++) {
 1002  0
                String segment = names.get(i);
 1003  
                
 1004  0
                if(segment != null) {
 1005  0
                   cache.add(segment);
 1006  
                }
 1007  
             }
 1008  
          }
 1009  0
          return cache.iterator();         
 1010  
       }      
 1011  
       
 1012  
       /**
 1013  
        * This is used to acquire the path section that contains all
 1014  
        * the segments in the section as well as the indexes for the
 1015  
        * segments. This method basically gets a substring of the
 1016  
        * primary path location from the first to the last segment.
 1017  
        * 
 1018  
        * @return this returns the section as a fully qualified path
 1019  
        */
 1020  
       private String getCanonicalPath() {
 1021  0
          int start = 0;
 1022  0
          int last = 0;
 1023  0
          int pos = 0;
 1024  
          
 1025  0
          for(pos = 0; pos < begin; pos++) {
 1026  0
             start = location.indexOf('/', start + 1);
 1027  
          }
 1028  0
          for(last = start; pos <= end; pos++) {
 1029  0
             last = location.indexOf('/', last + 1);
 1030  0
             if(last == -1) {
 1031  0
                last = location.length();
 1032  
             }
 1033  
          }
 1034  0
          return location.substring(start + 1, last);
 1035  
       }
 1036  
       
 1037  
       /**
 1038  
        * Provides a canonical XPath expression. This is used for both
 1039  
        * debugging and reporting. The path returned represents the 
 1040  
        * original path that has been parsed to form the expression.
 1041  
        * 
 1042  
        * @return this returns the string format for the XPath
 1043  
        */
 1044  
       private String getFragment() {        
 1045  19
          int last = start;
 1046  19
          int pos = 0; 
 1047  
          
 1048  19
          for(int i = 0; i <= end;) {
 1049  404
             if(last >= count) {
 1050  11
                last++;
 1051  11
                break;
 1052  
             }
 1053  393
             if(data[last++] == '/'){
 1054  69
                if(++i == begin) {                  
 1055  19
                   pos = last;
 1056  
                }         
 1057  
             }            
 1058  
          }
 1059  19
          return new String(data, pos, --last -pos);         
 1060  
       }
 1061  
       
 1062  
       /**
 1063  
        * Provides a canonical XPath expression. This is used for both
 1064  
        * debugging and reporting. The path returned represents the 
 1065  
        * original path that has been parsed to form the expression.
 1066  
        * 
 1067  
        * @return this returns the string format for the XPath
 1068  
        */
 1069  
       public String toString() {
 1070  19
          if(path == null) {
 1071  19
             path = getFragment();
 1072  
          }
 1073  19
          return path;
 1074  
       }   
 1075  
    }
 1076  
 }