001    package com.oxygenxml.validate.nvdl;
002    
003    import java.util.Vector;
004    
005    import com.thaiopensource.xml.util.Naming;
006    
007    /**
008     * Stores a NVDL/NRL path information.
009     * Parses a path string and returns a list of Path objects.
010     * This stores a single path that can optionally start with a / and 
011     * contains a list of local names separated by /, like
012     * /path1/path2 or
013     * path1/path2.
014     * 
015     */
016    class Path {
017      /**
018       * Flag indicating wether the path starts with / or not.
019       */
020      private final boolean root;
021    
022      /**
023       * The list of local names that form the path.
024       */
025      private final Vector names;
026    
027      /**
028       * Constructor, creates a Path.
029       * @param root Flag specifying wether the path starts with / or not.
030       * @param names The list of local names.
031       */
032      Path(boolean root, Vector names) {
033        this.root = root;
034        this.names = names;
035      }
036    
037      /**
038       * Determines if the path starts with / or not.
039       * @return true if the path starts with /.
040       */
041      boolean isRoot() {
042        return root;
043      }
044    
045      /**
046       * Get the local names list.
047       * @return A vector with the local names.
048       */
049      Vector getNames() {
050        return names;
051      }
052    
053      /**
054       * Get a string representation of this path.
055       * It can be either /name1/name2 or name1/name2.
056       */
057      public String toString() {
058        StringBuffer buf = new StringBuffer();
059        if (root)
060          buf.append('/');
061        for (int i = 0, len = names.size(); i < len; i++) {
062          if (i != 0)
063            buf.append('/');
064          buf.append((String) names.elementAt(i));
065        }
066        return buf.toString();
067      }
068    
069      /**
070       * Exception thrown in case we get errors parsing a path.
071       */
072      static class ParseException extends Exception {
073        /**
074         * The message key.
075         */
076        private final String messageKey;
077    
078        /**
079         * Creates an exception with a given message key.
080         * @param messageKey The message key.
081         */
082        ParseException(String messageKey) {
083          super(messageKey);
084          this.messageKey = messageKey;
085        }
086    
087        /**
088         * Get the message key.
089         * @return The message key.
090         */
091        public String getMessageKey() {
092          return messageKey;
093        }
094      }
095    
096      // states for parsing the path.
097      
098      /**
099       * Initial state.
100       */
101      private static final int START = 0;
102    
103      /**
104       * In a local name.
105       */
106      private static final int IN_NAME = 1;
107    
108      /**
109       * After a local name.
110       */
111      private static final int AFTER_NAME = 2;
112    
113      /**
114       * After a slash.
115       */
116      private static final int AFTER_SLASH = 3;
117    
118      /**
119       * Gets the list of Path from the path string.
120       * The path string can represent more paths separated by |.
121       * 
122       * @param str The path string.
123       * @return A Vector with the determined Path objects.
124       * @throws ParseException In case of invalid path expression.
125       */
126      static Vector parse(String str) throws ParseException {
127        int state = START;
128        int nameStartIndex = -1;
129        Vector paths = new Vector();
130        Vector names = new Vector();
131        boolean root = false;
132        for (int i = 0, len = str.length(); i < len; i++) {
133          char c = str.charAt(i);
134          switch (c) {
135          case ' ':
136          case '\r':
137          case '\n':
138          case '\t':
139            if (state == IN_NAME) {
140              names.addElement(makeName(str, nameStartIndex, i));
141              state = AFTER_NAME;
142            }
143            break;
144          case '/':
145            switch (state) {
146            case IN_NAME:
147              names.addElement(makeName(str, nameStartIndex, i));
148              break;
149            case START:
150              root = true;
151              break;
152            case AFTER_SLASH:
153              throw new ParseException("unexpected_slash");
154            }
155            state = AFTER_SLASH;
156            break;
157          case '|':
158            switch (state) {
159            case START:
160              throw new ParseException("empty_path");
161            case AFTER_NAME:
162              break;
163            case AFTER_SLASH:
164              throw new ParseException("expected_name");
165            case IN_NAME:
166              names.addElement(makeName(str, nameStartIndex, i));
167              break;
168            }
169            paths.addElement(new Path(root, names));
170            root = false;
171            names = new Vector();
172            state = START;
173            break;
174          default:
175            switch (state) {
176            case AFTER_NAME:
177              throw new ParseException("expected_slash");
178            case AFTER_SLASH:
179            case START:
180              nameStartIndex = i;
181              state = IN_NAME;
182              break;
183            case IN_NAME:
184              break;
185            }
186            break;
187          }
188        }
189        switch (state) {
190        case START:
191          throw new ParseException("empty_path");
192        case AFTER_NAME:
193          break;
194        case AFTER_SLASH:
195          throw new ParseException("expected_name");
196        case IN_NAME:
197          names.addElement(makeName(str, nameStartIndex, str.length()));
198          break;
199        }
200        paths.addElement(new Path(root, names));
201        return paths;
202      }
203    
204      /**
205       * Extracts a name from a given string (path) from the specified
206       * start position to the specified end position.
207       * It also checks that the extracted name is a valid non qualified name (local name).
208       * 
209       * @param str The path string.
210       * @param start The start position.
211       * @param end The end position.
212       * @return A string representing the extracted local name.
213       * @throws ParseException In case of invalid local name.
214       */
215      private static String makeName(String str, int start, int end)
216          throws ParseException {
217        String name = str.substring(start, end);
218        if (!Naming.isNcname(name))
219          throw new ParseException("invalid_name");
220        return name;
221      }
222    
223      /**
224       * Main method, for test. 
225       * @param args Command line arguments, the first argument is a path.
226       * @throws ParseException In case the parsing fails.
227       */
228      static public void main(String[] args) throws ParseException {
229        Vector paths = parse(args[0]);
230        for (int i = 0; i < paths.size(); i++) {
231          if (i != 0)
232            System.out.println("---");
233          Path path = (Path) paths.elementAt(i);
234          if (path.isRoot())
235            System.out.println("/");
236          for (int j = 0; j < path.getNames().size(); j++)
237            System.out.println(path.getNames().elementAt(j));
238        }
239      }
240    }