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 }