1/*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements.  See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License.  You may obtain a copy of the License at
8 *
9 *      http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package com.sun.org.apache.xml.internal.resolver.readers;
19
20import com.sun.org.apache.xml.internal.resolver.Catalog;
21import com.sun.org.apache.xml.internal.resolver.CatalogEntry;
22import com.sun.org.apache.xml.internal.resolver.CatalogException;
23import com.sun.org.apache.xml.internal.resolver.readers.CatalogReader;
24import java.io.FileNotFoundException;
25import java.io.IOException;
26import java.io.InputStream;
27import java.net.MalformedURLException;
28import java.net.URL;
29import java.net.URLConnection;
30import java.util.Locale;
31import java.util.Stack;
32import java.util.Vector;
33
34/**
35 * Parses plain text Catalog files.
36 *
37 * <p>This class reads plain text Open Catalog files.</p>
38 *
39 * @see Catalog
40 *
41 * @author Norman Walsh
42 * <a href="mailto:Norman.Walsh@Sun.COM">Norman.Walsh@Sun.COM</a>
43 *
44 */
45public class TextCatalogReader implements CatalogReader {
46  /** The input stream used to read the catalog */
47  protected InputStream catfile = null;
48
49  /**
50   * Character lookahead stack. Reading a catalog sometimes requires
51   * up to two characters of lookahead.
52   */
53  protected int[] stack = new int[3];
54
55  /**
56   * Token stack. Recognizing an unexpected catalog entry requires
57   * the ability to "push back" a token.
58   */
59  protected Stack tokenStack = new Stack();
60
61  /** The current position on the lookahead stack */
62  protected int top = -1;
63
64  /** Are keywords in the catalog case sensitive? */
65  protected boolean caseSensitive = false;
66
67  /**
68   * Construct a CatalogReader object.
69   */
70  public TextCatalogReader() { }
71
72  public void setCaseSensitive(boolean isCaseSensitive) {
73    caseSensitive = isCaseSensitive;
74  }
75
76  public boolean getCaseSensitive() {
77    return caseSensitive;
78  }
79
80  /**
81   * Start parsing a text catalog file. The file is
82   * actually read and parsed
83   * as needed by <code>nextEntry</code>.</p>
84   *
85   * @param fileUrl  The URL or filename of the catalog file to process
86   *
87   * @throws MalformedURLException Improper fileUrl
88   * @throws IOException Error reading catalog file
89   */
90  public void readCatalog(Catalog catalog, String fileUrl)
91    throws MalformedURLException, IOException {
92    URL catURL = null;
93
94    try {
95      catURL = new URL(fileUrl);
96    } catch (MalformedURLException e) {
97      catURL = new URL("file:///" + fileUrl);
98    }
99
100    URLConnection urlCon = catURL.openConnection();
101    try {
102      readCatalog(catalog, urlCon.getInputStream());
103    } catch (FileNotFoundException e) {
104      catalog.getCatalogManager().debug.message(1, "Failed to load catalog, file not found",
105                                                catURL.toString());
106    }
107  }
108
109  public void readCatalog(Catalog catalog, InputStream is)
110    throws MalformedURLException, IOException {
111
112    catfile = is;
113
114    if (catfile == null) {
115      return;
116    }
117
118    Vector unknownEntry = null;
119
120    try {
121      while (true) {
122        String token = nextToken();
123
124        if (token == null) {
125          if (unknownEntry != null) {
126            catalog.unknownEntry(unknownEntry);
127            unknownEntry = null;
128          }
129          catfile.close();
130          catfile = null;
131          return;
132        }
133
134        String entryToken = null;
135        if (caseSensitive) {
136          entryToken = token;
137        } else {
138          entryToken = token.toUpperCase(Locale.ENGLISH);
139        }
140
141        try {
142          int type = CatalogEntry.getEntryType(entryToken);
143          int numArgs = CatalogEntry.getEntryArgCount(type);
144          Vector args = new Vector();
145
146          if (unknownEntry != null) {
147            catalog.unknownEntry(unknownEntry);
148            unknownEntry = null;
149          }
150
151          for (int count = 0; count < numArgs; count++) {
152            args.addElement(nextToken());
153          }
154
155          catalog.addEntry(new CatalogEntry(entryToken, args));
156        } catch (CatalogException cex) {
157          if (cex.getExceptionType() == CatalogException.INVALID_ENTRY_TYPE) {
158            if (unknownEntry == null) {
159              unknownEntry = new Vector();
160            }
161            unknownEntry.addElement(token);
162          } else if (cex.getExceptionType() == CatalogException.INVALID_ENTRY) {
163            catalog.getCatalogManager().debug.message(1, "Invalid catalog entry", token);
164            unknownEntry = null;
165          } else if (cex.getExceptionType() == CatalogException.UNENDED_COMMENT) {
166            catalog.getCatalogManager().debug.message(1, cex.getMessage());
167          }
168        }
169      }
170    } catch (CatalogException cex2) {
171      if (cex2.getExceptionType() == CatalogException.UNENDED_COMMENT) {
172        catalog.getCatalogManager().debug.message(1, cex2.getMessage());
173      }
174    }
175  }
176
177  /**
178     * The destructor.
179     *
180     * <p>Makes sure the catalog file is closed.</p>
181     */
182  protected void finalize() {
183    if (catfile != null) {
184      try {
185        catfile.close();
186      } catch (IOException e) {
187        // whatever...
188      }
189    }
190    catfile = null;
191  }
192
193  // -----------------------------------------------------------------
194
195    /**
196     * Return the next token in the catalog file.
197     *
198     * <p>FYI: This code does not throw any sort of exception for
199     * a file that contains an n
200     *
201     * @return The Catalog file token from the input stream.
202     * @throws IOException If an error occurs reading from the stream.
203     */
204  protected String nextToken() throws IOException, CatalogException {
205    String token = "";
206    int ch, nextch;
207
208    if (!tokenStack.empty()) {
209      return (String) tokenStack.pop();
210    }
211
212    // Skip over leading whitespace and comments
213    while (true) {
214      // skip leading whitespace
215      ch = catfile.read();
216      while (ch <= ' ') {      // all ctrls are whitespace
217        ch = catfile.read();
218        if (ch < 0) {
219          return null;
220        }
221      }
222
223      // now 'ch' is the current char from the file
224      nextch = catfile.read();
225      if (nextch < 0) {
226        return null;
227      }
228
229      if (ch == '-' && nextch == '-') {
230        // we've found a comment, skip it...
231        ch = ' ';
232        nextch = nextChar();
233        while ((ch != '-' || nextch != '-') && nextch > 0) {
234          ch = nextch;
235          nextch = nextChar();
236        }
237
238        if (nextch < 0) {
239          throw new CatalogException(CatalogException.UNENDED_COMMENT,
240                                     "Unterminated comment in catalog file; EOF treated as end-of-comment.");
241        }
242
243        // Ok, we've found the end of the comment,
244        // loop back to the top and start again...
245      } else {
246        stack[++top] = nextch;
247        stack[++top] = ch;
248        break;
249      }
250    }
251
252    ch = nextChar();
253    if (ch == '"' || ch == '\'') {
254      int quote = ch;
255      while ((ch = nextChar()) != quote) {
256        char[] chararr = new char[1];
257        chararr[0] = (char) ch;
258        String s = new String(chararr);
259        token = token.concat(s);
260      }
261      return token;
262    } else {
263      // return the next whitespace or comment delimited
264      // string
265      while (ch > ' ') {
266        nextch = nextChar();
267        if (ch == '-' && nextch == '-') {
268          stack[++top] = ch;
269          stack[++top] = nextch;
270          return token;
271        } else {
272          char[] chararr = new char[1];
273          chararr[0] = (char) ch;
274          String s = new String(chararr);
275          token = token.concat(s);
276          ch = nextch;
277        }
278      }
279      return token;
280    }
281  }
282
283  /**
284     * Return the next logical character from the input stream.
285     *
286     * @return The next (logical) character from the input stream. The
287     * character may be buffered from a previous lookahead.
288     *
289     * @throws IOException If an error occurs reading from the stream.
290     */
291  protected int nextChar() throws IOException {
292    if (top < 0) {
293      return catfile.read();
294    } else {
295      return stack[top--];
296    }
297  }
298}
299