001: /*
002: * Copyright (c) 2003 The Visigoth Software Society. All rights
003: * reserved.
004: *
005: * Redistribution and use in source and binary forms, with or without
006: * modification, are permitted provided that the following conditions
007: * are met:
008: *
009: * 1. Redistributions of source code must retain the above copyright
010: * notice, this list of conditions and the following disclaimer.
011: *
012: * 2. Redistributions in binary form must reproduce the above copyright
013: * notice, this list of conditions and the following disclaimer in
014: * the documentation and/or other materials provided with the
015: * distribution.
016: *
017: * 3. The end-user documentation included with the redistribution, if
018: * any, must include the following acknowledgement:
019: * "This product includes software developed by the
020: * Visigoth Software Society (http://www.visigoths.org/)."
021: * Alternately, this acknowledgement may appear in the software itself,
022: * if and wherever such third-party acknowledgements normally appear.
023: *
024: * 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the
025: * project contributors may be used to endorse or promote products derived
026: * from this software without prior written permission. For written
027: * permission, please contact visigoths@visigoths.org.
028: *
029: * 5. Products derived from this software may not be called "FreeMarker" or "Visigoth"
030: * nor may "FreeMarker" or "Visigoth" appear in their names
031: * without prior written permission of the Visigoth Software Society.
032: *
033: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
034: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
035: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
036: * DISCLAIMED. IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
037: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
038: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
039: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
040: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
041: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
042: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
043: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
044: * SUCH DAMAGE.
045: * ====================================================================
046: *
047: * This software consists of voluntary contributions made by many
048: * individuals on behalf of the Visigoth Software Society. For more
049: * information on the Visigoth Software Society, please see
050: * http://www.visigoths.org/
051: */
052:
053: package freemarker.template;
054:
055: import java.io.*;
056: import java.util.*;
057: import javax.swing.tree.TreePath;
058: import freemarker.core.*;
059:
060: import freemarker.debug.impl.DebuggerService;
061:
062: /**
063: * <p>A core FreeMarker API that represents a compiled template.
064: * Typically, you will use a {@link Configuration} object to instantiate a template.
065: *
066: * <PRE>
067: Configuration cfg = new Configuration();
068: ...
069: Template myTemplate = cfg.getTemplate("myTemplate.html");
070: </PRE>
071: *
072: * <P>However, you can also construct a template directly by passing in to
073: * the appropriate constructor a java.io.Reader instance that is set to
074: * read the raw template text. The compiled template is
075: * stored in an an efficient data structure for later use.
076: *
077: * <p>To render the template, i.e. to merge it with a data model, and
078: * thus produce "cooked" output, call the <tt>process</tt> method.
079: *
080: * <p>Any error messages from exceptions thrown during compilation will be
081: * included in the output stream and thrown back to the calling code.
082: * To change this behavior, you can install custom exception handlers using
083: * {@link Configurable#setTemplateExceptionHandler(TemplateExceptionHandler)} on
084: * a Configuration object (for all templates belonging to a configuration) or on
085: * a Template object (for a single template).
086: *
087: * <p>It's not legal to modify the values of FreeMarker settings: a) while the
088: * template is executing; b) if the template object is already accessible from
089: * multiple threads.
090: *
091: * @version $Id: Template.java,v 1.216.2.3 2006/03/10 17:49:02 revusky Exp $
092: */
093:
094: public class Template extends Configurable {
095: public static final String DEFAULT_NAMESPACE_PREFIX = "D";
096: public static final String NO_NS_PREFIX = "N";
097:
098: private Map macros = new HashMap();
099: private List imports = new Vector();
100: private TemplateElement rootElement;
101: private String encoding, defaultNS;
102: private final String name;
103: private final ArrayList lines = new ArrayList();
104: private Map prefixToNamespaceURILookup = new HashMap();
105: private Map namespaceURIToPrefixLookup = new HashMap();
106:
107: /**
108: * A prime constructor to which all other constructors should
109: * delegate directly or indirectly.
110: */
111: private Template(String name, Configuration cfg) {
112: super (cfg != null ? cfg : Configuration
113: .getDefaultConfiguration());
114: this .name = name;
115: }
116:
117: /**
118: * Constructs a template from a character stream.
119: *
120: * @param name the path of the template file relative to the directory what you use to store
121: * the templates. See {@link #getName} for more details.
122: * @param reader the character stream to read from. It will always be closed (Reader.close()).
123: * @param cfg the Configuration object that this Template is associated with.
124: * If this is null, the "default" {@link Configuration} object is used,
125: * which is highly discouraged, because it can easily lead to
126: * erroneous, unpredictable behaviour.
127: * (See more {@link Configuration#getDefaultConfiguration() here...})
128: * @param encoding This is the encoding that we are supposed to be using. If this is
129: * non-null (It's not actually necessary because we are using a Reader) then it is
130: * checked against the encoding specified in the FTL header -- assuming that is specified,
131: * and if they don't match a WrongEncodingException is thrown.
132: */
133: public Template(String name, Reader reader, Configuration cfg,
134: String encoding) throws IOException {
135: this (name, cfg);
136: this .encoding = encoding;
137:
138: if (!(reader instanceof BufferedReader)) {
139: reader = new BufferedReader(reader, 0x1000);
140: }
141: LineTableBuilder ltb = new LineTableBuilder(reader);
142: try {
143: FMParser parser = new FMParser(this , ltb,
144: getConfiguration().getStrictSyntaxMode(),
145: getConfiguration().getWhitespaceStripping(),
146: getConfiguration().getTagSyntax());
147: this .rootElement = parser.Root();
148: } catch (TokenMgrError exc) {
149: throw new ParseException("Token manager error: " + exc, 0,
150: 0);
151: } finally {
152: ltb.close();
153: }
154: DebuggerService.registerTemplate(this );
155: namespaceURIToPrefixLookup = Collections
156: .unmodifiableMap(namespaceURIToPrefixLookup);
157: prefixToNamespaceURILookup = Collections
158: .unmodifiableMap(prefixToNamespaceURILookup);
159: }
160:
161: /**
162: * This is equivalent to Template(name, reader, cfg, null)
163: */
164:
165: public Template(String name, Reader reader, Configuration cfg)
166: throws IOException {
167: this (name, reader, cfg, null);
168: }
169:
170: /**
171: * Constructs a template from a character stream.
172: *
173: * This is the same as the 3 parameter version when you pass null
174: * as the cfg parameter.
175: *
176: * @deprecated This constructor uses the "default" {@link Configuration}
177: * instance, which can easily lead to erroneous, unpredictable behaviour.
178: * See more {@link Configuration#getDefaultConfiguration() here...}.
179: */
180: public Template(String name, Reader reader) throws IOException {
181: this (name, reader, null);
182: }
183:
184: /**
185: * This constructor is only used internally.
186: */
187: Template(String name, TemplateElement root, Configuration config) {
188: this (name, config);
189: this .rootElement = root;
190: DebuggerService.registerTemplate(this );
191: }
192:
193: /**
194: * Returns a trivial template, one that is just a single block of
195: * plain text, no dynamic content. (Used by the cache module to create
196: * unparsed templates.)
197: * @param name the path of the template file relative to the directory what you use to store
198: * the templates. See {@link #getName} for more details.
199: * @param content the block of text that this template represents
200: * @param config the configuration to which this template belongs
201: */
202: static public Template getPlainTextTemplate(String name,
203: String content, Configuration config) {
204: Template template = new Template(name, config);
205: TextBlock block = new TextBlock(content);
206: template.rootElement = block;
207: DebuggerService.registerTemplate(template);
208: return template;
209: }
210:
211: /**
212: * Processes the template, using data from the map, and outputs
213: * the resulting text to the supplied <tt>Writer</tt> The elements of the
214: * map are converted to template models using the default object wrapper
215: * returned by the {@link Configuration#getObjectWrapper() getObjectWrapper()}
216: * method of the <tt>Configuration</tt>.
217: * @param rootMap the root node of the data model. If null, an
218: * empty data model is used. Can be any object that the effective object
219: * wrapper can turn into a <tt>TemplateHashModel</tt>. Basically, simple and
220: * beans wrapper can turn <tt>java.util.Map</tt> objects into hashes
221: * and the Jython wrapper can turn both a <tt>PyDictionary</tt> as well as
222: * any object that implements <tt>__getitem__</tt> into a template hash.
223: * Naturally, you can pass any object directly implementing
224: * <tt>TemplateHashModel</tt> as well.
225: * @param out a <tt>Writer</tt> to output the text to.
226: * @throws TemplateException if an exception occurs during template processing
227: * @throws IOException if an I/O exception occurs during writing to the writer.
228: */
229: public void process(Object rootMap, Writer out)
230: throws TemplateException, IOException {
231: createProcessingEnvironment(rootMap, out, null).process();
232: }
233:
234: /**
235: * Processes the template, using data from the root map object, and outputs
236: * the resulting text to the supplied writer, using the supplied
237: * object wrapper to convert map elements to template models.
238: * @param rootMap the root node of the data model. If null, an
239: * empty data model is used. Can be any object that the effective object
240: * wrapper can turn into a <tt>TemplateHashModel</tt> Basically, simple and
241: * beans wrapper can turn <tt>java.util.Map</tt> objects into hashes
242: * and the Jython wrapper can turn both a <tt>PyDictionary</tt> as well as any
243: * object that implements <tt>__getitem__</tt> into a template hash.
244: * Naturally, you can pass any object directly implementing
245: * <tt>TemplateHashModel</tt> as well.
246: * @param wrapper The object wrapper to use to wrap objects into
247: * {@link TemplateModel} instances. If null, the default wrapper retrieved
248: * by {@link Configurable#getObjectWrapper()} is used.
249: * @param out the writer to output the text to.
250: * @param rootNode The root node for recursive processing, this may be null.
251: *
252: * @throws TemplateException if an exception occurs during template processing
253: * @throws IOException if an I/O exception occurs during writing to the writer.
254: */
255: public void process(Object rootMap, Writer out,
256: ObjectWrapper wrapper, TemplateNodeModel rootNode)
257: throws TemplateException, IOException {
258: Environment env = createProcessingEnvironment(rootMap, out,
259: wrapper);
260: if (rootNode != null) {
261: env.setCurrentVisitorNode(rootNode);
262: }
263: env.process();
264: }
265:
266: /**
267: * Processes the template, using data from the root map object, and outputs
268: * the resulting text to the supplied writer, using the supplied
269: * object wrapper to convert map elements to template models.
270: * @param rootMap the root node of the data model. If null, an
271: * empty data model is used. Can be any object that the effective object
272: * wrapper can turn into a <tt>TemplateHashModel</tt> Basically, simple and
273: * beans wrapper can turn <tt>java.util.Map</tt> objects into hashes
274: * and the Jython wrapper can turn both a <tt>PyDictionary</tt> as well as any
275: * object that implements <tt>__getitem__</tt> into a template hash.
276: * Naturally, you can pass any object directly implementing
277: * <tt>TemplateHashModel</tt> as well.
278: * @param wrapper The object wrapper to use to wrap objects into
279: * {@link TemplateModel} instances. If null, the default wrapper retrieved
280: * by {@link Configurable#getObjectWrapper()} is used.
281: * @param out the writer to output the text to.
282: *
283: * @throws TemplateException if an exception occurs during template processing
284: * @throws IOException if an I/O exception occurs during writing to the writer.
285: */
286: public void process(Object rootMap, Writer out,
287: ObjectWrapper wrapper) throws TemplateException,
288: IOException {
289: process(rootMap, out, wrapper, null);
290: }
291:
292: /**
293: * Creates a {@link freemarker.core.Environment Environment} object,
294: * using this template, the data model provided as the root map object, and
295: * the supplied object wrapper to convert map elements to template models.
296: * You can then call Environment.process() on the returned environment
297: * to set off the actual rendering.
298: * Use this method if you want to do some special initialization on the environment
299: * before template processing, or if you want to read the environment after template
300: * processing.
301: *
302: * <p>Example:
303: *
304: * <p>This:
305: * <pre>
306: * Environment env = myTemplate.createProcessingEnvironment(root, out, null);
307: * env.process();
308: * </pre>
309: * is equivalent with this:
310: * <pre>
311: * myTemplate.process(root, out);
312: * </pre>
313: * But with <tt>createProcessingEnvironment</tt>, you can manipulate the environment
314: * before and after the processing:
315: * <pre>
316: * Environment env = myTemplate.createProcessingEnvironment(root, out);
317: * env.include("include/common.ftl", null, true); // before processing
318: * env.process();
319: * TemplateModel x = env.getVariable("x"); // after processing
320: * </pre>
321: *
322: * @param rootMap the root node of the data model. If null, an
323: * empty data model is used. Can be any object that the effective object
324: * wrapper can turn into a <tt>TemplateHashModel</tt> Basically, simple and
325: * beans wrapper can turn <tt>java.util.Map</tt> objects into hashes
326: * and the Jython wrapper can turn both a <tt>PyDictionary</tt> as well as any
327: * object that implements <tt>__getitem__</tt> into a template hash.
328: * Naturally, you can pass any object directly implementing
329: * <tt>TemplateHashModel</tt> as well.
330: * @param wrapper The object wrapper to use to wrap objects into
331: * {@link TemplateModel} instances. If null, the default wrapper retrieved
332: * by {@link Configurable#getObjectWrapper()} is used.
333: * @param out the writer to output the text to.
334: * @return the {@link freemarker.core.Environment Environment} object created for processing
335: * @throws TemplateException if an exception occurs while setting up the Environment object.
336: * @throws IOException if an exception occurs doing any auto-imports
337: */
338: public Environment createProcessingEnvironment(Object rootMap,
339: Writer out, ObjectWrapper wrapper)
340: throws TemplateException, IOException {
341: TemplateHashModel root = null;
342: if (rootMap instanceof TemplateHashModel) {
343: root = (TemplateHashModel) rootMap;
344: } else {
345: if (wrapper == null) {
346: wrapper = getObjectWrapper();
347: }
348:
349: try {
350: root = rootMap != null ? (TemplateHashModel) wrapper
351: .wrap(rootMap) : new SimpleHash(wrapper);
352: if (root == null) {
353: throw new IllegalArgumentException(wrapper
354: .getClass().getName()
355: + " converted "
356: + rootMap.getClass().getName()
357: + " to null.");
358: }
359: } catch (ClassCastException e) {
360: throw new IllegalArgumentException(wrapper.getClass()
361: .getName()
362: + " could not convert "
363: + rootMap.getClass().getName()
364: + " to a TemplateHashModel.");
365: }
366: }
367: Environment env = new Environment(this , root, out);
368: getConfiguration().doAutoImports(env);
369: getConfiguration().doAutoIncludes(env);
370: return env;
371: }
372:
373: /**
374: * Same as <code>createProcessingEnvironment(rootMap, out, null)</code>.
375: * @see #createProcessingEnvironment(Object rootMap, Writer out, ObjectWrapper wrapper)
376: */
377: public Environment createProcessingEnvironment(Object rootMap,
378: Writer out) throws TemplateException, IOException {
379: return createProcessingEnvironment(rootMap, out, null);
380: }
381:
382: /**
383: * Returns a string representing the raw template
384: * text in canonical form.
385: */
386: public String toString() {
387: StringWriter sw = new StringWriter();
388: try {
389: dump(sw);
390: } catch (IOException ioe) {
391: throw new RuntimeException(ioe.getMessage());
392: }
393: return sw.toString();
394: }
395:
396: /**
397: * The path of the template file relative to the directory what you use to store the templates.
398: * For example, if the real path of template is <tt>"/www/templates/community/forum.fm"</tt>,
399: * and you use "<tt>"/www/templates"</tt> as
400: * {@link Configuration#setDirectoryForTemplateLoading "directoryForTemplateLoading"},
401: * then <tt>name</tt> should be <tt>"community/forum.fm"</tt>. The <tt>name</tt> is used for example when you
402: * use <tt><include ...></tt> and you give a path that is relative to the current
403: * template, or in error messages when FreeMarker logs an error while it processes the template.
404: */
405: public String getName() {
406: return name;
407: }
408:
409: /**
410: * Returns the Configuration object associated with this template.
411: */
412: public Configuration getConfiguration() {
413: return (Configuration) getParent();
414: }
415:
416: /**
417: * Sets the character encoding to use for
418: * included files. Usually you don't set this value manually,
419: * instead it is assigned to the template upon loading.
420: */
421:
422: public void setEncoding(String encoding) {
423: this .encoding = encoding;
424: }
425:
426: /**
427: * Returns the character encoding used for reading included files.
428: */
429: public String getEncoding() {
430: return this .encoding;
431: }
432:
433: /**
434: * Dump the raw template in canonical form.
435: */
436: public void dump(PrintStream ps) {
437: ps.print(rootElement.getCanonicalForm());
438: }
439:
440: /**
441: * Dump the raw template in canonical form.
442: */
443: public void dump(Writer out) throws IOException {
444: out.write(rootElement.getCanonicalForm());
445: }
446:
447: /**
448: * Called by code internally to maintain
449: * a table of macros
450: */
451: public void addMacro(Macro macro) {
452: macros.put(macro.getName(), macro);
453: }
454:
455: /**
456: * Called by code internally to maintain
457: * a list of imports
458: */
459: public void addImport(LibraryLoad ll) {
460: imports.add(ll);
461: }
462:
463: /**
464: * Returns the template source at the location
465: * specified by the coordinates given.
466: * @param beginColumn the first column of the requested source, 1-based
467: * @param beginLine the first line of the requested source, 1-based
468: * @param endColumn the last column of the requested source, 1-based
469: * @param endLine the last line of the requested source, 1-based
470: * @see freemarker.core.TemplateObject#getSource()
471: */
472: public String getSource(int beginColumn, int beginLine,
473: int endColumn, int endLine) {
474: // Our container is zero-based.
475: --beginLine;
476: --beginColumn;
477: --endColumn;
478: --endLine;
479: StringBuffer buf = new StringBuffer();
480: for (int i = beginLine; i <= endLine; i++) {
481: if (i < lines.size()) {
482: buf.append(lines.get(i));
483: }
484: }
485: int lastLineLength = lines.get(endLine).toString().length();
486: int trailingCharsToDelete = lastLineLength - endColumn - 1;
487: buf.delete(0, beginColumn);
488: buf.delete(buf.length() - trailingCharsToDelete, buf.length());
489: return buf.toString();
490: }
491:
492: /**
493: * This is a helper class that builds up the line table
494: * info for us.
495: */
496: private class LineTableBuilder extends FilterReader {
497:
498: StringBuffer lineBuf = new StringBuffer();
499: int lastChar;
500:
501: /**
502: * @param r the character stream to wrap
503: */
504: LineTableBuilder(Reader r) {
505: super (r);
506: }
507:
508: public int read() throws IOException {
509: int c = in.read();
510: handleChar(c);
511: return c;
512: }
513:
514: public int read(char cbuf[], int off, int len)
515: throws IOException {
516: int numchars = in.read(cbuf, off, len);
517: for (int i = off; i < off + numchars; i++) {
518: char c = cbuf[i];
519: handleChar(c);
520: }
521: return numchars;
522: }
523:
524: public void close() throws IOException {
525: if (lineBuf.length() > 0) {
526: lines.add(lineBuf.toString());
527: lineBuf.setLength(0);
528: }
529: super .close();
530: }
531:
532: private void handleChar(int c) {
533: if (c == '\n' || c == '\r') {
534: if (lastChar == '\r' && c == '\n') { // CRLF under Windoze
535: int lastIndex = lines.size() - 1;
536: String lastLine = (String) lines.get(lastIndex);
537: lines.set(lastIndex, lastLine + '\n');
538: } else {
539: lineBuf.append((char) c);
540: lines.add(lineBuf.toString());
541: lineBuf.setLength(0);
542: }
543: } else if (c == '\t') {
544: int numSpaces = 8 - (lineBuf.length() % 8);
545: for (int i = 0; i < numSpaces; i++) {
546: lineBuf.append(' ');
547: }
548: } else {
549: lineBuf.append((char) c);
550: }
551: lastChar = c;
552: }
553: }
554:
555: /**
556: * @return the root TemplateElement object.
557: */
558: public TemplateElement getRootTreeNode() {
559: return rootElement;
560: }
561:
562: public Map getMacros() {
563: return macros;
564: }
565:
566: public List getImports() {
567: return imports;
568: }
569:
570: /**
571: * This is used internally.
572: */
573: public void addPrefixNSMapping(String prefix, String nsURI) {
574: if (nsURI.length() == 0) {
575: throw new IllegalArgumentException(
576: "Cannot map empty string URI");
577: }
578: if (prefix.length() == 0) {
579: throw new IllegalArgumentException(
580: "Cannot map empty string prefix");
581: }
582: if (prefix.equals(NO_NS_PREFIX)) {
583: throw new IllegalArgumentException(
584: "The prefix: "
585: + prefix
586: + " cannot be registered, it is reserved for special internal use.");
587: }
588: if (prefixToNamespaceURILookup.containsKey(prefix)) {
589: throw new IllegalArgumentException("The prefix: '" + prefix
590: + "' was repeated. This is illegal.");
591: }
592: if (namespaceURIToPrefixLookup.containsKey(nsURI)) {
593: throw new IllegalArgumentException("The namespace URI: "
594: + nsURI
595: + " cannot be mapped to 2 different prefixes.");
596: }
597: if (prefix.equals(DEFAULT_NAMESPACE_PREFIX)) {
598: this .defaultNS = nsURI;
599: } else {
600: prefixToNamespaceURILookup.put(prefix, nsURI);
601: namespaceURIToPrefixLookup.put(nsURI, prefix);
602: }
603: }
604:
605: public String getDefaultNS() {
606: return this .defaultNS;
607: }
608:
609: /**
610: * @return the NamespaceUri mapped to this prefix in this template. (Or null if there is none.)
611: */
612: public String getNamespaceForPrefix(String prefix) {
613: if (prefix.equals("")) {
614: return defaultNS == null ? "" : defaultNS;
615: }
616: return (String) prefixToNamespaceURILookup.get(prefix);
617: }
618:
619: /**
620: * @return the prefix mapped to this nsURI in this template. (Or null if there is none.)
621: */
622: public String getPrefixForNamespace(String nsURI) {
623: if (nsURI == null) {
624: return null;
625: }
626: if (nsURI.length() == 0) {
627: return defaultNS == null ? "" : NO_NS_PREFIX;
628: }
629: if (nsURI.equals(defaultNS)) {
630: return "";
631: }
632: return (String) namespaceURIToPrefixLookup.get(nsURI);
633: }
634:
635: /**
636: * @return the prefixed name, based on the ns_prefixes defined
637: * in this template's header for the local name and node namespace
638: * passed in as parameters.
639: */
640: public String getPrefixedName(String localName, String nsURI) {
641: if (nsURI == null || nsURI.length() == 0) {
642: if (defaultNS != null) {
643: return NO_NS_PREFIX + ":" + localName;
644: } else {
645: return localName;
646: }
647: }
648: if (nsURI.equals(defaultNS)) {
649: return localName;
650: }
651: String prefix = getPrefixForNamespace(nsURI);
652: if (prefix == null) {
653: return null;
654: }
655: return prefix + ":" + localName;
656: }
657:
658: /**
659: * @return an array of the elements containing the given column and line numbers.
660: * @param column the column
661: * @param line the line
662: */
663: public TreePath containingElements(int column, int line) {
664: ArrayList elements = new ArrayList();
665: TemplateElement element = rootElement;
666: mainloop: while (element.contains(column, line)) {
667: elements.add(element);
668: for (Enumeration enumeration = element.children(); enumeration
669: .hasMoreElements();) {
670: TemplateElement elem = (TemplateElement) enumeration
671: .nextElement();
672: if (elem.contains(column, line)) {
673: element = elem;
674: continue mainloop;
675: }
676: }
677: break;
678: }
679: if (elements == null || elements.isEmpty()) {
680: return null;
681: }
682: return new TreePath(elements.toArray());
683: }
684:
685: static public class WrongEncodingException extends ParseException {
686:
687: public String specifiedEncoding;
688:
689: public WrongEncodingException(String specifiedEncoding) {
690: this.specifiedEncoding = specifiedEncoding;
691: }
692:
693: }
694: }
|