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