001: /* NonValidator.java NanoXML/Java
002: *
003: * $Revision: 2056 $
004: * $Date: 2008-02-25 00:29:28 -0800 (Mon, 25 Feb 2008) $
005: * $Name$
006: *
007: * This file is part of NanoXML 2 for Java.
008: * Copyright (C) 2001 Marc De Scheemaecker, All Rights Reserved.
009: *
010: * This software is provided 'as-is', without any express or implied warranty.
011: * In no event will the authors be held liable for any damages arising from the
012: * use of this software.
013: *
014: * Permission is granted to anyone to use this software for any purpose,
015: * including commercial applications, and to alter it and redistribute it
016: * freely, subject to the following restrictions:
017: *
018: * 1. The origin of this software must not be misrepresented; you must not
019: * claim that you wrote the original software. If you use this software in
020: * a product, an acknowledgment in the product documentation would be
021: * appreciated but is not required.
022: *
023: * 2. Altered source versions must be plainly marked as such, and must not be
024: * misrepresented as being the original software.
025: *
026: * 3. This notice may not be removed or altered from any source distribution.
027: */
028:
029: package net.n3.nanoxml;
030:
031: import java.io.Reader;
032: import java.io.StringReader;
033: import java.util.Enumeration;
034: import java.util.Hashtable;
035: import java.util.Properties;
036: import java.util.Stack;
037:
038: /**
039: * NonValidator processes the DTD and handles entity definitions. It does not do any validation
040: * itself.
041: *
042: * @author Marc De Scheemaecker
043: * @version $Name$, $Revision: 2056 $
044: */
045: public class NonValidator implements IXMLValidator {
046:
047: /**
048: * Delimiter for CDATA sections.
049: */
050: private static final char[] END_OF_CONDSECTION = { '>', ']', ']' };
051:
052: /**
053: * The parameter entity resolver.
054: */
055: protected IXMLEntityResolver parameterEntityResolver;
056:
057: /**
058: * The parameter entity level.
059: */
060: protected int peLevel;
061:
062: /**
063: * Contains the default values for attributes for the different element types.
064: */
065: protected Hashtable<String, Properties> attributeDefaultValues;
066:
067: /**
068: * The stack of elements to be processed.
069: */
070: protected Stack<Properties> currentElements;
071:
072: /**
073: * Creates the "validator".
074: */
075: public NonValidator() {
076: this .attributeDefaultValues = new Hashtable<String, Properties>();
077: this .currentElements = new Stack<Properties>();
078: this .parameterEntityResolver = new XMLEntityResolver();
079: this .peLevel = 0;
080: }
081:
082: /**
083: * Cleans up the object when it's destroyed.
084: */
085: protected void finalize() throws Throwable {
086: this .parameterEntityResolver = null;
087: this .attributeDefaultValues.clear();
088: this .attributeDefaultValues = null;
089: this .currentElements.clear();
090: this .currentElements = null;
091: super .finalize();
092: }
093:
094: /**
095: * Sets the parameter entity resolver.
096: *
097: * @param resolver the entity resolver.
098: */
099: public void setParameterEntityResolver(IXMLEntityResolver resolver) {
100: this .parameterEntityResolver = resolver;
101: }
102:
103: /**
104: * Returns the parameter entity resolver.
105: *
106: * @return the entity resolver.
107: */
108: public IXMLEntityResolver getParameterEntityResolver() {
109: return this .parameterEntityResolver;
110: }
111:
112: /**
113: * Parses the DTD. The validator object is responsible for reading the full DTD.
114: *
115: * @param publicID the public ID, which may be null.
116: * @param reader the reader to read the DTD from.
117: * @param entityResolver the entity resolver.
118: * @param external true if the DTD is external.
119: *
120: * @throws java.lang.Exception if something went wrong.
121: */
122: public void parseDTD(String publicID, IXMLReader reader,
123: IXMLEntityResolver entityResolver, boolean external)
124: throws Exception {
125: XMLUtil.skipWhitespace(reader, '%', null, null);
126:
127: for (;;) {
128: char ch = XMLUtil.read(reader, null, '%',
129: this .parameterEntityResolver);
130:
131: if (ch == '<') {
132: this .processElement(reader, entityResolver);
133: } else if (ch == ']') {
134: return; // end internal DTD
135: } else {
136: XMLUtil.errorInvalidInput(reader.getSystemID(), reader
137: .getLineNr(), "" + ch);
138: }
139:
140: do {
141: if (external && (peLevel == 0)
142: && reader.atEOFOfCurrentStream()) {
143: return; // end
144: // external
145: // DTD
146: }
147:
148: ch = reader.read();
149: } while ((ch == ' ') || (ch == '\t') || (ch == '\n')
150: || (ch == '\r'));
151:
152: reader.unread(ch);
153: XMLUtil.skipWhitespace(reader, '%', null, null);
154: }
155: }
156:
157: /**
158: * Processes an element in the DTD.
159: *
160: * @param reader the reader to read data from
161: * @param entityResolver the entity resolver
162: *
163: * @throws java.lang.Exception if something went wrong.
164: */
165: protected void processElement(IXMLReader reader,
166: IXMLEntityResolver entityResolver) throws Exception {
167: char ch = XMLUtil.read(reader, null, '%',
168: this .parameterEntityResolver);
169:
170: if (ch != '!') {
171: XMLUtil.skipTag(reader, '%', this .parameterEntityResolver);
172: return;
173: }
174:
175: ch = XMLUtil.read(reader, null, '%',
176: this .parameterEntityResolver);
177:
178: switch (ch) {
179: case '-':
180: XMLUtil.skipComment(reader, this .parameterEntityResolver);
181: break;
182:
183: case '[':
184: this .processConditionalSection(reader, entityResolver);
185: break;
186:
187: case 'E':
188: this .processEntity(reader, entityResolver);
189: break;
190:
191: case 'A':
192: this .processAttList(reader, entityResolver);
193: break;
194:
195: default:
196: XMLUtil.skipTag(reader, '%', this .parameterEntityResolver);
197: }
198: }
199:
200: /**
201: * Processes a conditional section.
202: *
203: * @param reader the reader to read data from
204: * @param entityResolver the entity resolver
205: *
206: * @throws java.lang.Exception if something went wrong.
207: */
208: protected void processConditionalSection(IXMLReader reader,
209: IXMLEntityResolver entityResolver) throws Exception {
210: XMLUtil.skipWhitespace(reader, '%', null, null);
211:
212: char ch = XMLUtil.read(reader, null, '%',
213: this .parameterEntityResolver);
214:
215: if (ch != 'I') {
216: XMLUtil.skipTag(reader, '%', this .parameterEntityResolver);
217: return;
218: }
219:
220: ch = XMLUtil.read(reader, null, '%',
221: this .parameterEntityResolver);
222:
223: switch (ch) {
224: case 'G':
225: this .processIgnoreSection(reader, entityResolver);
226: return;
227:
228: case 'N':
229: break;
230:
231: default:
232: XMLUtil.skipTag(reader, '%', this .parameterEntityResolver);
233: return;
234: }
235:
236: if (!XMLUtil.checkLiteral(reader, '%',
237: this .parameterEntityResolver, "CLUDE")) {
238: XMLUtil.skipTag(reader, '%', this .parameterEntityResolver);
239: return;
240: }
241:
242: XMLUtil.skipWhitespace(reader, '%', null, null);
243:
244: ch = XMLUtil.read(reader, null, '%',
245: this .parameterEntityResolver);
246:
247: if (ch != '[') {
248: XMLUtil.skipTag(reader, '%', this .parameterEntityResolver);
249: return;
250: }
251:
252: Reader subreader = new ContentReader(reader,
253: this .parameterEntityResolver, '\0',
254: NonValidator.END_OF_CONDSECTION, true, "");
255: StringBuffer buf = new StringBuffer(1024);
256:
257: for (;;) {
258: int ch2 = subreader.read();
259:
260: if (ch2 < 0) {
261: break;
262: }
263:
264: buf.append((char) ch2);
265: }
266:
267: subreader.close();
268: reader.startNewStream(new StringReader(buf.toString()));
269: }
270:
271: /**
272: * Processes an ignore section.
273: *
274: * @param reader the reader to read data from
275: * @param entityResolver the entity resolver
276: *
277: * @throws java.lang.Exception if something went wrong.
278: */
279: protected void processIgnoreSection(IXMLReader reader,
280: IXMLEntityResolver entityResolver) throws Exception {
281: if (!XMLUtil.checkLiteral(reader, '%',
282: this .parameterEntityResolver, "NORE")) {
283: XMLUtil.skipTag(reader, '%', this .parameterEntityResolver);
284: return;
285: }
286:
287: XMLUtil.skipWhitespace(reader, '%', null, null);
288:
289: char ch = XMLUtil.read(reader, null, '%',
290: this .parameterEntityResolver);
291:
292: if (ch != '[') {
293: XMLUtil.skipTag(reader, '%', this .parameterEntityResolver);
294: return;
295: }
296:
297: Reader subreader = new ContentReader(reader,
298: this .parameterEntityResolver, '\0',
299: NonValidator.END_OF_CONDSECTION, true, "");
300: subreader.close();
301: }
302:
303: /**
304: * Processes an ATTLIST element.
305: *
306: * @param reader the reader to read data from
307: * @param entityResolver the entity resolver
308: *
309: * @throws java.lang.Exception if something went wrong.
310: */
311: protected void processAttList(IXMLReader reader,
312: IXMLEntityResolver entityResolver) throws Exception {
313: if (!XMLUtil.checkLiteral(reader, '%',
314: this .parameterEntityResolver, "TTLIST")) {
315: XMLUtil.skipTag(reader, '%', this .parameterEntityResolver);
316: return;
317: }
318:
319: XMLUtil.skipWhitespace(reader, '%', null, null);
320: String elementName = XMLUtil.scanIdentifier(reader, '%',
321: this .parameterEntityResolver);
322: XMLUtil.skipWhitespace(reader, '%', null, null);
323: char ch = XMLUtil.read(reader, null, '%',
324: this .parameterEntityResolver);
325: Properties props = new Properties();
326:
327: while (ch != '>') {
328: reader.unread(ch);
329: String attName = XMLUtil.scanIdentifier(reader, '%',
330: this .parameterEntityResolver);
331: XMLUtil.skipWhitespace(reader, '%', null, null);
332: ch = XMLUtil.read(reader, null, '%',
333: this .parameterEntityResolver);
334:
335: if (ch == '(') {
336: while (ch != ')') {
337: ch = XMLUtil.read(reader, null, '%',
338: this .parameterEntityResolver);
339: }
340: } else {
341: reader.unread(ch);
342: XMLUtil.scanIdentifier(reader, '%',
343: this .parameterEntityResolver);
344: }
345:
346: XMLUtil.skipWhitespace(reader, '%', null, null);
347: ch = XMLUtil.read(reader, null, '%',
348: this .parameterEntityResolver);
349:
350: if (ch == '#') {
351: String str = XMLUtil.scanIdentifier(reader, '%',
352: this .parameterEntityResolver);
353: XMLUtil.skipWhitespace(reader, '%', null, null);
354:
355: if (!"FIXED".equals(str)) {
356: XMLUtil.skipWhitespace(reader, '%', null, null);
357: ch = XMLUtil.read(reader, null, '%',
358: this .parameterEntityResolver);
359: continue;
360: }
361: } else {
362: reader.unread(ch);
363: }
364:
365: String value = XMLUtil.scanString(reader, '%', false,
366: this .parameterEntityResolver);
367: props.put(attName, value);
368: XMLUtil.skipWhitespace(reader, '%', null, null);
369: ch = XMLUtil.read(reader, null, '%',
370: this .parameterEntityResolver);
371: }
372:
373: if (!props.isEmpty()) {
374: this .attributeDefaultValues.put(elementName, props);
375: }
376: }
377:
378: /**
379: * Processes an ENTITY element.
380: *
381: * @param reader the reader to read data from
382: * @param entityResolver the entity resolver
383: *
384: * @throws java.lang.Exception if something went wrong.
385: */
386: protected void processEntity(IXMLReader reader,
387: IXMLEntityResolver entityResolver) throws Exception {
388: if (!XMLUtil.checkLiteral(reader, '%',
389: this .parameterEntityResolver, "NTITY")) {
390: XMLUtil.skipTag(reader, '%', this .parameterEntityResolver);
391: return;
392: }
393:
394: XMLUtil.skipWhitespace(reader, '\0', null, null);
395: char ch = XMLUtil.read(reader, null, '\0',
396: this .parameterEntityResolver);
397:
398: if (ch == '%') {
399: XMLUtil.skipWhitespace(reader, '%', null, null);
400: entityResolver = this .parameterEntityResolver;
401: } else {
402: reader.unread(ch);
403: }
404:
405: String key = XMLUtil.scanIdentifier(reader, '%',
406: this .parameterEntityResolver);
407: XMLUtil.skipWhitespace(reader, '%', null, null);
408: ch = XMLUtil.read(reader, null, '%',
409: this .parameterEntityResolver);
410: String systemID = null;
411: String publicID = null;
412:
413: switch (ch) {
414: case 'P':
415: if (!XMLUtil.checkLiteral(reader, '%',
416: this .parameterEntityResolver, "UBLIC")) {
417: XMLUtil.skipTag(reader, '%',
418: this .parameterEntityResolver);
419: return;
420: }
421:
422: XMLUtil.skipWhitespace(reader, '%', null, null);
423: publicID = XMLUtil.scanString(reader, '%', false,
424: this .parameterEntityResolver);
425: XMLUtil.skipWhitespace(reader, '%', null, null);
426: systemID = XMLUtil.scanString(reader, '%', false,
427: this .parameterEntityResolver);
428: XMLUtil.skipWhitespace(reader, '%', null, null);
429: XMLUtil.read(reader, null, '%',
430: this .parameterEntityResolver);
431: break;
432:
433: case 'S':
434: if (!XMLUtil.checkLiteral(reader, '%',
435: this .parameterEntityResolver, "YSTEM")) {
436: XMLUtil.skipTag(reader, '%',
437: this .parameterEntityResolver);
438: return;
439: }
440:
441: XMLUtil.skipWhitespace(reader, '%', null, null);
442: systemID = XMLUtil.scanString(reader, '%', false,
443: this .parameterEntityResolver);
444: XMLUtil.skipWhitespace(reader, '%', null, null);
445: XMLUtil.read(reader, null, '%',
446: this .parameterEntityResolver);
447: break;
448:
449: case '"':
450: case '\'':
451: reader.unread(ch);
452: String value = XMLUtil.scanString(reader, '%', false,
453: this .parameterEntityResolver);
454: entityResolver.addInternalEntity(key, value);
455: XMLUtil.skipWhitespace(reader, '%', null, null);
456: XMLUtil.read(reader, null, '%',
457: this .parameterEntityResolver);
458: break;
459: default:
460: XMLUtil.skipTag(reader, '%', this .parameterEntityResolver);
461: }
462:
463: if (systemID != null) {
464: entityResolver.addExternalEntity(key, publicID, systemID);
465: }
466: }
467:
468: /**
469: * Indicates that an element has been started.
470: *
471: * @param name the name of the element.
472: * @param nsPrefix the prefix used to identify the namespace
473: * @param nsSystemId the system ID associated with the namespace
474: * @param systemId the system ID of the XML data of the element.
475: * @param lineNr the line number in the XML data of the element.
476: */
477: public void elementStarted(String name, String nsPrefix,
478: String nsSystemId, String systemId, int lineNr) {
479: Properties attribs = this .attributeDefaultValues.get(name);
480:
481: if (attribs == null) {
482: attribs = new Properties();
483: } else {
484: attribs = (Properties) attribs.clone();
485: }
486:
487: this .currentElements.push(attribs);
488: }
489:
490: /**
491: * Indicates that the current element has ended.
492: *
493: * @param name the name of the element.
494: * @param nsPrefix the prefix used to identify the namespace
495: * @param nsSystemId the system ID associated with the namespace
496: * @param systemId the system ID of the XML data of the element.
497: * @param lineNr the line number in the XML data of the element.
498: */
499: public void elementEnded(String name, String nsPrefix,
500: String nsSystemId, String systemId, int lineNr) {
501: // nothing to do
502: }
503:
504: /**
505: * This method is called when the attributes of an XML element have been processed. If there are
506: * attributes with a default value which have not been specified yet, they have to be put into
507: * <I>extraAttributes</I>.
508: *
509: * @param name the name of the element.
510: * @param nsPrefix the prefix used to identify the namespace
511: * @param nsSystemId the system ID associated with the namespace
512: * @param extraAttributes where to put extra attributes.
513: * @param systemId the system ID of the XML data of the element.
514: * @param lineNr the line number in the XML data of the element.
515: */
516: public void elementAttributesProcessed(String name,
517: String nsPrefix, String nsSystemId,
518: Properties extraAttributes, String systemId, int lineNr) {
519: Properties props = this .currentElements.pop();
520: Enumeration enumeration = props.keys();
521:
522: while (enumeration.hasMoreElements()) {
523: String key = (String) enumeration.nextElement();
524: extraAttributes.put(key, props.get(key));
525: }
526: }
527:
528: /**
529: * Indicates that an attribute has been added to the current element.
530: *
531: * @param key the name of the attribute.
532: * @param nsPrefix the prefix used to identify the namespace
533: * @param nsSystemId the system ID associated with the namespace
534: * @param value the value of the attribute.
535: * @param systemId the system ID of the XML data of the element.
536: * @param lineNr the line number in the XML data of the element.
537: */
538: public void attributeAdded(String key, String nsPrefix,
539: String nsSystemId, String value, String systemId, int lineNr) {
540: Properties props = this .currentElements.peek();
541:
542: if (props.containsKey(key)) {
543: props.remove(key);
544: }
545: }
546:
547: /**
548: * Indicates that a new #PCDATA element has been encountered.
549: *
550: * @param systemId the system ID of the XML data of the element.
551: * @param lineNr the line number in the XML data of the element.
552: */
553: public void PCDataAdded(String systemId, int lineNr) {
554: // nothing to do
555: }
556:
557: }
|