001: /* XMLUtil.java NanoXML/Java
002: *
003: * $Revision: 1421 $
004: * $Date: 2006-03-12 09:32:32 -0700 (Sun, 12 Mar 2006) $
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.CharArrayReader;
032: import java.io.IOException;
033: import java.io.Reader;
034:
035: /**
036: * Utility methods for NanoXML.
037: *
038: * @author Marc De Scheemaecker
039: * @version $Name$, $Revision: 1421 $
040: */
041: class XMLUtil {
042:
043: /**
044: * Skips the remainder of a comment. It is assumed that <!- is already read.
045: *
046: * @param reader the reader
047: * @param entityResolver the entity resolver
048: *
049: * @throws java.io.IOException if an error occurred reading the data
050: */
051: static void skipComment(IXMLReader reader,
052: IXMLEntityResolver entityResolver) throws IOException,
053: XMLParseException {
054: if (reader.read() != '-') {
055: XMLUtil.skipTag(reader, '\0', entityResolver);
056: return;
057: }
058:
059: int dashesRead = 0;
060:
061: for (;;) {
062: char ch = reader.read();
063:
064: switch (ch) {
065: case '-':
066: dashesRead++;
067: break;
068:
069: case '>':
070: if (dashesRead == 2) {
071: return;
072: }
073:
074: default:
075: dashesRead = 0;
076: }
077: }
078: }
079:
080: /**
081: * Skips the remainder of the current XML tag.
082: *
083: * @param reader the reader
084: * @param escapeChar the escape character (& or %)
085: * @param entityResolver the entity resolver
086: *
087: * @throws java.io.IOException if an error occurred reading the data
088: */
089: static void skipTag(IXMLReader reader, char escapeChar,
090: IXMLEntityResolver entityResolver) throws IOException,
091: XMLParseException {
092: int level = 1;
093:
094: while (level > 0) {
095: char ch = XMLUtil.read(reader, null, escapeChar,
096: entityResolver);
097:
098: switch (ch) {
099: case '<':
100: ++level;
101: break;
102:
103: case '>':
104: --level;
105: break;
106: }
107: }
108: }
109:
110: /**
111: * Scans a public ID.
112: *
113: * @param publicID will contain the public ID
114: * @param reader the reader
115: * @param escapeChar the escape character (& or %)
116: * @param entityResolver the entity resolver
117: *
118: * @return the system ID
119: *
120: * @throws java.io.IOException if an error occurred reading the data
121: */
122: static String scanPublicID(StringBuffer publicID,
123: IXMLReader reader, char escapeChar,
124: IXMLEntityResolver entityResolver) throws IOException,
125: XMLParseException {
126: if (!XMLUtil.checkLiteral(reader, escapeChar, entityResolver,
127: "UBLIC")) {
128: return null;
129: }
130:
131: XMLUtil.skipWhitespace(reader, escapeChar, null, null);
132: publicID.append(XMLUtil.scanString(reader, escapeChar, false,
133: entityResolver));
134: XMLUtil.skipWhitespace(reader, escapeChar, null, null);
135: return XMLUtil.scanString(reader, escapeChar, false,
136: entityResolver);
137: }
138:
139: /**
140: * Scans a system ID.
141: *
142: * @param reader the reader
143: * @param escapeChar the escape character (& or %)
144: * @param entityResolver the entity resolver
145: *
146: * @return the system ID
147: *
148: * @throws java.io.IOException if an error occurred reading the data
149: */
150: static String scanSystemID(IXMLReader reader, char escapeChar,
151: IXMLEntityResolver entityResolver) throws IOException,
152: XMLParseException {
153: if (!XMLUtil.checkLiteral(reader, escapeChar, entityResolver,
154: "YSTEM")) {
155: return null;
156: }
157:
158: XMLUtil.skipWhitespace(reader, escapeChar, null, null);
159: return XMLUtil.scanString(reader, escapeChar, false,
160: entityResolver);
161: }
162:
163: /**
164: * Retrieves an identifier from the data.
165: *
166: * @param reader the reader
167: * @param escapeChar the escape character (& or %)
168: * @param entityResolver the entity resolver
169: *
170: * @throws java.io.IOException if an error occurred reading the data
171: */
172: static String scanIdentifier(IXMLReader reader, char escapeChar,
173: IXMLEntityResolver entityResolver) throws IOException,
174: XMLParseException {
175: StringBuffer result = new StringBuffer();
176:
177: for (;;) {
178: char ch = XMLUtil.read(reader, null, escapeChar,
179: entityResolver);
180:
181: if ((ch == '_') || (ch == ':') || (ch == '-')
182: || (ch == '.') || ((ch >= 'a') && (ch <= 'z'))
183: || ((ch >= 'A') && (ch <= 'Z'))
184: || ((ch >= '0') && (ch <= '9')) || (ch > '\u007E')) {
185: result.append(ch);
186: } else {
187: reader.unread(ch);
188: break;
189: }
190: }
191:
192: return result.toString();
193: }
194:
195: /**
196: * Retrieves a delimited string from the data.
197: *
198: * @param reader the reader
199: * @param escapeChar the escape character (& or %)
200: * @param normalizeWhitespace if all whitespace chars need to be converted to spaces
201: * @param entityResolver the entity resolver
202: *
203: * @throws java.io.IOException if an error occurred reading the data
204: */
205: static String scanString(IXMLReader reader, char escapeChar,
206: boolean normalizeWhitespace,
207: IXMLEntityResolver entityResolver) throws IOException,
208: XMLParseException {
209: StringBuffer result = new StringBuffer();
210: boolean isEntity[] = new boolean[1];
211: char delim = XMLUtil.read(reader, null, escapeChar,
212: entityResolver);
213:
214: if ((delim != '\'') && (delim != '"')) {
215: XMLUtil.errorExpectedInput(reader.getSystemID(), reader
216: .getLineNr(), "delimited string");
217: }
218:
219: for (;;) {
220: char ch = XMLUtil.read(reader, isEntity, escapeChar,
221: entityResolver);
222:
223: if ((!isEntity[0]) && (ch == escapeChar)) {
224: reader.startNewStream(XMLUtil.scanEntity(isEntity,
225: reader, escapeChar, entityResolver));
226: ch = reader.read();
227: }
228:
229: if ((!isEntity[0]) && (ch == delim)) {
230: break;
231: } else if (normalizeWhitespace && (ch < ' ')) {
232: result.append(' ');
233: } else {
234: result.append(ch);
235: }
236: }
237:
238: return result.toString();
239: }
240:
241: /**
242: * Processes an entity.
243: *
244: * @param isCharLiteral will contain true if the entity is a char literal
245: * @param reader the reader
246: * @param escapeChar the escape character (& or %)
247: * @param entityResolver the entity resolver
248: *
249: * @return a reader from which the entity value can be retrieved
250: *
251: * @throws java.io.IOException if an error occurred reading the data
252: */
253: static Reader scanEntity(boolean[] isCharLiteral,
254: IXMLReader reader, char escapeChar,
255: IXMLEntityResolver entityResolver) throws IOException,
256: XMLParseException {
257: char ch = reader.read();
258: StringBuffer keyBuf = new StringBuffer();
259:
260: while (ch != ';') {
261: keyBuf.append(ch);
262: ch = reader.read();
263: }
264:
265: String key = keyBuf.toString();
266:
267: if (key.charAt(0) == '#') {
268: if (isCharLiteral != null) {
269: isCharLiteral[0] = true;
270: }
271:
272: char[] chArr = new char[1];
273:
274: if (key.charAt(1) == 'x') {
275: chArr[0] = (char) Integer
276: .parseInt(key.substring(2), 16);
277: } else {
278: chArr[0] = (char) Integer
279: .parseInt(key.substring(1), 10);
280: }
281:
282: return new CharArrayReader(chArr);
283: } else {
284: Reader entityReader = entityResolver.getEntity(reader, key);
285:
286: if (entityReader == null) {
287: XMLUtil.errorInvalidEntity(reader.getSystemID(), reader
288: .getLineNr(), key);
289: }
290:
291: return entityReader;
292: }
293: }
294:
295: /**
296: * Skips whitespace from the reader.
297: *
298: * @param reader the reader
299: * @param escapeChar the escape character (& or %)
300: * @param buffer where to put the whitespace; null if the whitespace does not have to be stored.
301: * @param isEntity if not null, will contain true if the data following the whitespace is an
302: * entity
303: *
304: * @throws java.io.IOException if an error occurred reading the data
305: */
306: static void skipWhitespace(IXMLReader reader, char escapeChar,
307: StringBuffer buffer, boolean[] isEntity) throws IOException {
308: char ch;
309:
310: if (buffer == null) {
311: do {
312: ch = reader.read();
313: } while ((ch == ' ') || (ch == '\t') || (ch == '\n')
314: || (ch == '\r'));
315: } else {
316: for (;;) {
317: ch = reader.read();
318:
319: if ((ch != ' ') && (ch != '\t') && (ch != '\n')
320: && (ch != '\r')) {
321: break;
322: }
323:
324: buffer.append(ch);
325: }
326: }
327:
328: reader.unread(ch);
329:
330: if (isEntity != null) {
331: isEntity[0] = (ch == escapeChar);
332: }
333: }
334:
335: /**
336: * Reads a character from the reader.
337: *
338: * @param reader the reader
339: * @param isEntityValue if the character is the first character in an entity
340: * @param escapeChar the escape character (& or %)
341: * @param entityResolver the entity resolver
342: *
343: * @throws java.io.IOException if an error occurred reading the data
344: */
345: static char read(IXMLReader reader, boolean[] isEntityValue,
346: char escapeChar, IXMLEntityResolver entityResolver)
347: throws IOException, XMLParseException {
348: char ch = reader.read();
349:
350: if (isEntityValue != null) {
351: isEntityValue[0] = false;
352: }
353:
354: if (ch == escapeChar) {
355: boolean[] charLiteral = new boolean[1];
356: reader.startNewStream(XMLUtil.scanEntity(charLiteral,
357: reader, escapeChar, entityResolver));
358:
359: if (charLiteral[0]) {
360: ch = reader.read();
361:
362: if (isEntityValue != null) {
363: isEntityValue[0] = true;
364: }
365: } else {
366: ch = XMLUtil.read(reader, null, escapeChar,
367: entityResolver);
368: }
369: }
370:
371: return ch;
372: }
373:
374: /**
375: * Returns true if the data starts with <I>literal</I>. Enough chars are read to determine this
376: * result.
377: *
378: * @param reader the reader
379: * @param escapeChar the escape character (& or %)
380: * @param entityResolver the entity resolver
381: * @param literal the literal to check
382: *
383: * @throws java.io.IOException if an error occurred reading the data
384: */
385: static boolean checkLiteral(IXMLReader reader, char escapeChar,
386: IXMLEntityResolver entityResolver, String literal)
387: throws IOException, XMLParseException {
388: for (int i = 0; i < literal.length(); i++) {
389: char ch = XMLUtil.read(reader, null, escapeChar,
390: entityResolver);
391:
392: if (ch != literal.charAt(i)) {
393: return false;
394: }
395: }
396:
397: return true;
398: }
399:
400: /**
401: * Throws an XMLParseException to indicate that an expected string is not encountered.
402: *
403: * @param systemID the system ID from where the data came
404: * @param lineNr the line number in the XML data where the exception occurred.
405: * @param expectedString the string that is expected
406: */
407: static void errorExpectedInput(String systemID, int lineNr,
408: String expectedString) throws XMLParseException {
409: throw new XMLParseException(systemID, lineNr, "Expected: "
410: + expectedString);
411: }
412:
413: /**
414: * Throws an XMLParseException to indicate that an entity could not be resolved.
415: *
416: * @param systemID the system ID from where the data came
417: * @param lineNr the line number in the XML data where the exception occurred.
418: * @param key the name of the entity
419: */
420: static void errorInvalidEntity(String systemID, int lineNr,
421: String key) throws XMLParseException {
422: throw new XMLParseException(systemID, lineNr,
423: "Invalid entity: `&" + key + ";'");
424: }
425:
426: /**
427: * Throws an XMLParseException to indicate that a string is not expected at this point.
428: *
429: * @param systemID the system ID from where the data came
430: * @param lineNr the line number in the XML data where the exception occurred.
431: * @param unexpectedString the string that is unexpected
432: */
433: static void errorInvalidInput(String systemID, int lineNr,
434: String unexpectedString) throws XMLParseException {
435: throw new XMLParseException(systemID, lineNr, "Invalid input: "
436: + unexpectedString);
437: }
438:
439: /**
440: * Throws an XMLParseException to indicate that the closing tag of an element does not match the
441: * opening tag.
442: *
443: * @param systemID the system ID from where the data came
444: * @param lineNr the line number in the XML data where the exception occurred.
445: * @param expectedName the name of the opening tag
446: * @param wrongName the name of the closing tag
447: */
448: static void errorWrongClosingTag(String systemID, int lineNr,
449: String expectedName, String wrongName)
450: throws XMLParseException {
451: throw new XMLParseException(systemID, lineNr,
452: "Closing tag does not match opening tag: `" + wrongName
453: + "' != `" + expectedName + "'");
454: }
455:
456: /**
457: * Throws an XMLParseException to indicate that extra data is encountered in a closing tag.
458: *
459: * @param systemID the system ID from where the data came
460: * @param lineNr the line number in the XML data where the exception occurred.
461: */
462: static void errorClosingTagNotEmpty(String systemID, int lineNr)
463: throws XMLParseException {
464: throw new XMLParseException(systemID, lineNr,
465: "Closing tag must be empty");
466: }
467:
468: /**
469: * Throws an XMLValidationException to indicate that an element is missing.
470: *
471: * @param systemID the system ID from where the data came
472: * @param lineNr the line number in the XML data where the exception occurred.
473: * @param parentElementName the name of the offending element
474: * @param missingElementName the name of the offending attribute
475: */
476: static void errorMissingElement(String systemID, int lineNr,
477: String parentElementName, String missingElementName)
478: throws XMLValidationException {
479: throw new XMLValidationException(
480: XMLValidationException.MISSING_ELEMENT, systemID,
481: lineNr, missingElementName,
482: /* attributeName */null,
483: /* attributeValue */null, "Element "
484: + parentElementName + " expects to have a "
485: + missingElementName);
486: }
487:
488: /**
489: * Throws an XMLValidationException to indicate that an element is unexpected.
490: *
491: * @param systemID the system ID from where the data came
492: * @param lineNr the line number in the XML data where the exception occurred.
493: * @param parentElementName the name of the parent attribute
494: * @param unexpectedElementName the name of the offending attribute
495: */
496: static void errorUnexpectedElement(String systemID, int lineNr,
497: String parentElementName, String unexpectedElementName)
498: throws XMLValidationException {
499: throw new XMLValidationException(
500: XMLValidationException.UNEXPECTED_ELEMENT, systemID,
501: lineNr, unexpectedElementName,
502: /* attributeName */null,
503: /* attributeValue */null, "Unexpected "
504: + unexpectedElementName + " in a "
505: + parentElementName);
506: }
507:
508: /**
509: * Throws an XMLValidationException to indicate that an attribute is missing.
510: *
511: * @param systemID the system ID from where the data came
512: * @param lineNr the line number in the XML data where the exception occurred.
513: * @param elementName the name of the offending element
514: * @param attributeName the name of the offending attribute
515: */
516: static void errorMissingAttribute(String systemID, int lineNr,
517: String elementName, String attributeName)
518: throws XMLValidationException {
519: throw new XMLValidationException(
520: XMLValidationException.MISSING_ATTRIBUTE, systemID,
521: lineNr, elementName, attributeName,
522: /* attributeValue */null, "Element " + elementName
523: + " expects an attribute named "
524: + attributeName);
525: }
526:
527: /**
528: * Throws an XMLValidationException to indicate that an attribute is unexpected.
529: *
530: * @param systemID the system ID from where the data came
531: * @param lineNr the line number in the XML data where the exception occurred.
532: * @param elementName the name of the offending element
533: * @param attributeName the name of the offending attribute
534: */
535: static void errorUnexpectedAttribute(String systemID, int lineNr,
536: String elementName, String attributeName)
537: throws XMLValidationException {
538: throw new XMLValidationException(
539: XMLValidationException.UNEXPECTED_ATTRIBUTE, systemID,
540: lineNr, elementName, attributeName,
541: /* attributeValue */null, "Element " + elementName
542: + " did not expect an attribute " + "named "
543: + attributeName);
544: }
545:
546: /**
547: * Throws an XMLValidationException to indicate that an attribute has an invalid value.
548: *
549: * @param systemID the system ID from where the data came
550: * @param lineNr the line number in the XML data where the exception occurred.
551: * @param elementName the name of the offending element
552: * @param attributeName the name of the offending attribute
553: * @param attributeValue the value of the offending attribute
554: */
555: static void errorInvalidAttributeValue(String systemID, int lineNr,
556: String elementName, String attributeName,
557: String attributeValue) throws XMLValidationException {
558: throw new XMLValidationException(
559: XMLValidationException.ATTRIBUTE_WITH_INVALID_VALUE,
560: systemID, lineNr, elementName, attributeName,
561: attributeValue, "Invalid value for attribute "
562: + attributeName);
563: }
564:
565: /**
566: * Throws an XMLValidationException to indicate that a #PCDATA element was missing.
567: *
568: * @param systemID the system ID from where the data came
569: * @param lineNr the line number in the XML data where the exception occurred.
570: * @param parentElementName the name of the offending element
571: */
572: static void errorMissingPCData(String systemID, int lineNr,
573: String parentElementName) throws XMLValidationException {
574: throw new XMLValidationException(
575: XMLValidationException.MISSING_PCDATA, systemID,
576: lineNr,
577: /* elementName */null,
578: /* attributeName */null,
579: /* attributeValue */null,
580: "Missing #PCDATA in element " + parentElementName);
581: }
582:
583: /**
584: * Throws an XMLValidationException to indicate that a #PCDATA element was unexpected.
585: *
586: * @param systemID the system ID from where the data came
587: * @param lineNr the line number in the XML data where the exception occurred.
588: * @param parentElementName the name of the offending element
589: */
590: static void errorUnexpectedPCData(String systemID, int lineNr,
591: String parentElementName) throws XMLValidationException {
592: throw new XMLValidationException(
593: XMLValidationException.UNEXPECTED_PCDATA, systemID,
594: lineNr,
595: /* elementName */null,
596: /* attributeName */null,
597: /* attributeValue */null,
598: "Unexpected #PCDATA in element " + parentElementName);
599: }
600:
601: /**
602: * Throws an XMLValidationException.
603: *
604: * @param systemID the system ID from where the data came
605: * @param lineNr the line number in the XML data where the exception occurred.
606: * @param message the message of the exception.
607: * @param elementName the name of the offending element
608: * @param attributeName the name of the offending attribute
609: * @param attributeValue the value of the offending attribute
610: */
611: static void validationError(String systemID, int lineNr,
612: String message, String elementName, String attributeName,
613: String attributeValue) throws XMLValidationException {
614: throw new XMLValidationException(
615: XMLValidationException.MISC_ERROR, systemID, lineNr,
616: elementName, attributeName, attributeValue, message);
617: }
618:
619: }
|