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 }