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