001: /* XMLUtil.java NanoXML/Java
002: *
003: * $Revision: 1.5 $
004: * $Date: 2002/02/03 21:19:38 $
005: * $Name: RELEASE_2_2_1 $
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: RELEASE_2_2_1 $, $Revision: 1.5 $
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: StringBuffer buf = new StringBuffer();
333: buf.append(ch);
334:
335: if (ch == entityChar) {
336: while (ch != ';') {
337: ch = reader.read();
338: buf.append(ch);
339: }
340: }
341:
342: return buf.toString();
343: }
344:
345: /**
346: * Reads a character from the reader disallowing entities.
347: *
348: * @param reader the reader
349: * @param entityChar the escape character (& or %) used to indicate
350: * an entity
351: */
352: static char readChar(IXMLReader reader, char entityChar)
353: throws IOException, XMLParseException {
354: String str = XMLUtil.read(reader, entityChar);
355: char ch = str.charAt(0);
356:
357: if (ch == entityChar) {
358: XMLUtil.errorUnexpectedEntity(reader.getSystemID(), reader
359: .getLineNr(), str);
360: }
361:
362: return ch;
363: }
364:
365: /**
366: * Returns true if the data starts with <I>literal</I>.
367: * Enough chars are read to determine this result.
368: *
369: * @param reader the reader
370: * @param literal the literal to check
371: *
372: * @throws java.io.IOException
373: * if an error occurred reading the data
374: */
375: static boolean checkLiteral(IXMLReader reader, String literal)
376: throws IOException, XMLParseException {
377: for (int i = 0; i < literal.length(); i++) {
378: if (reader.read() != literal.charAt(i)) {
379: return false;
380: }
381: }
382:
383: return true;
384: }
385:
386: /**
387: * Throws an XMLParseException to indicate that an expected string is not
388: * encountered.
389: *
390: * @param systemID the system ID of the data source
391: * @param lineNr the line number in the data source
392: * @param expectedString the string that is expected
393: */
394: static void errorExpectedInput(String systemID, int lineNr,
395: String expectedString) throws XMLParseException {
396: throw new XMLParseException(systemID, lineNr, "Expected: "
397: + expectedString);
398: }
399:
400: /**
401: * Throws an XMLParseException to indicate that an entity could not be
402: * resolved.
403: *
404: * @param systemID the system ID of the data source
405: * @param lineNr the line number in the data source
406: * @param entity the name of the entity
407: */
408: static void errorInvalidEntity(String systemID, int lineNr,
409: String entity) throws XMLParseException {
410: throw new XMLParseException(systemID, lineNr,
411: "Invalid entity: `&" + entity + ";'");
412: }
413:
414: /**
415: * Throws an XMLParseException to indicate that an entity reference is
416: * unexpected at this point.
417: *
418: * @param systemID the system ID of the data source
419: * @param lineNr the line number in the data source
420: * @param entity the name of the entity
421: */
422: static void errorUnexpectedEntity(String systemID, int lineNr,
423: String entity) throws XMLParseException {
424: throw new XMLParseException(systemID, lineNr,
425: "No entity reference is expected here (" + entity + ")");
426: }
427:
428: /**
429: * Throws an XMLParseException to indicate that a CDATA section is
430: * unexpected at this point.
431: *
432: * @param systemID the system ID of the data source
433: * @param lineNr the line number in the data source
434: */
435: static void errorUnexpectedCDATA(String systemID, int lineNr)
436: throws XMLParseException {
437: throw new XMLParseException(systemID, lineNr,
438: "No CDATA section is expected here");
439: }
440:
441: /**
442: * Throws an XMLParseException to indicate that a string is not expected
443: * at this point.
444: *
445: * @param systemID the system ID of the data source
446: * @param lineNr the line number in the data source
447: * @param unexpectedString the string that is unexpected
448: */
449: static void errorInvalidInput(String systemID, int lineNr,
450: String unexpectedString) throws XMLParseException {
451: throw new XMLParseException(systemID, lineNr, "Invalid input: "
452: + unexpectedString);
453: }
454:
455: /**
456: * Throws an XMLParseException to indicate that the closing tag of an
457: * element does not match the opening tag.
458: *
459: * @param systemID the system ID of the data source
460: * @param lineNr the line number in the data source
461: * @param expectedName the name of the opening tag
462: * @param wrongName the name of the closing tag
463: */
464: static void errorWrongClosingTag(String systemID, int lineNr,
465: String expectedName, String wrongName)
466: throws XMLParseException {
467: throw new XMLParseException(systemID, lineNr,
468: "Closing tag does not match opening tag: `" + wrongName
469: + "' != `" + expectedName + "'");
470: }
471:
472: /**
473: * Throws an XMLParseException to indicate that extra data is encountered
474: * in a closing tag.
475: *
476: * @param systemID the system ID of the data source
477: * @param lineNr the line number in the data source
478: */
479: static void errorClosingTagNotEmpty(String systemID, int lineNr)
480: throws XMLParseException {
481: throw new XMLParseException(systemID, lineNr,
482: "Closing tag must be empty");
483: }
484:
485: /**
486: * Throws an XMLValidationException to indicate that an element is missing.
487: *
488: * @param systemID the system ID of the data source
489: * @param lineNr the line number in the data source
490: * @param parentElementName the name of the parent element
491: * @param missingElementName the name of the missing element
492: */
493: static void errorMissingElement(String systemID, int lineNr,
494: String parentElementName, String missingElementName)
495: throws XMLValidationException {
496: throw new XMLValidationException(
497: XMLValidationException.MISSING_ELEMENT, systemID,
498: lineNr, missingElementName,
499: /*attributeName*/null,
500: /*attributeValue*/null, "Element " + parentElementName
501: + " expects to have a " + missingElementName);
502: }
503:
504: /**
505: * Throws an XMLValidationException to indicate that an element is
506: * unexpected.
507: *
508: * @param systemID the system ID of the data source
509: * @param lineNr the line number in the data source
510: * @param parentElementName the name of the parent element
511: * @param unexpectedElementName the name of the unexpected element
512: */
513: static void errorUnexpectedElement(String systemID, int lineNr,
514: String parentElementName, String unexpectedElementName)
515: throws XMLValidationException {
516: throw new XMLValidationException(
517: XMLValidationException.UNEXPECTED_ELEMENT, systemID,
518: lineNr, unexpectedElementName,
519: /*attributeName*/null,
520: /*attributeValue*/null, "Unexpected "
521: + unexpectedElementName + " in a "
522: + parentElementName);
523: }
524:
525: /**
526: * Throws an XMLValidationException to indicate that an attribute is
527: * missing.
528: *
529: * @param systemID the system ID of the data source
530: * @param lineNr the line number in the data source
531: * @param elementName the name of the element
532: * @param attributeName the name of the missing attribute
533: */
534: static void errorMissingAttribute(String systemID, int lineNr,
535: String elementName, String attributeName)
536: throws XMLValidationException {
537: throw new XMLValidationException(
538: XMLValidationException.MISSING_ATTRIBUTE, systemID,
539: lineNr, elementName, attributeName,
540: /*attributeValue*/null, "Element " + elementName
541: + " expects an attribute named "
542: + attributeName);
543: }
544:
545: /**
546: * Throws an XMLValidationException to indicate that an attribute is
547: * unexpected.
548: *
549: * @param systemID the system ID of the data source
550: * @param lineNr the line number in the data source
551: * @param elementName the name of the element
552: * @param attributeName the name of the unexpected attribute
553: */
554: static void errorUnexpectedAttribute(String systemID, int lineNr,
555: String elementName, String attributeName)
556: throws XMLValidationException {
557: throw new XMLValidationException(
558: XMLValidationException.UNEXPECTED_ATTRIBUTE, systemID,
559: lineNr, elementName, attributeName,
560: /*attributeValue*/null, "Element " + elementName
561: + " did not expect an attribute " + "named "
562: + attributeName);
563: }
564:
565: /**
566: * Throws an XMLValidationException to indicate that an attribute has an
567: * invalid value.
568: *
569: * @param systemID the system ID of the data source
570: * @param lineNr the line number in the data source
571: * @param elementName the name of the element
572: * @param attributeName the name of the attribute
573: * @param attributeValue the value of that attribute
574: */
575: static void errorInvalidAttributeValue(String systemID, int lineNr,
576: String elementName, String attributeName,
577: String attributeValue) throws XMLValidationException {
578: throw new XMLValidationException(
579: XMLValidationException.ATTRIBUTE_WITH_INVALID_VALUE,
580: systemID, lineNr, elementName, attributeName,
581: attributeValue, "Invalid value for attribute "
582: + attributeName);
583: }
584:
585: /**
586: * Throws an XMLValidationException to indicate that a #PCDATA element was
587: * missing.
588: *
589: * @param systemID the system ID of the data source
590: * @param lineNr the line number in the data source
591: * @param parentElementName the name of the parent element
592: */
593: static void errorMissingPCData(String systemID, int lineNr,
594: String parentElementName) throws XMLValidationException {
595: throw new XMLValidationException(
596: XMLValidationException.MISSING_PCDATA, systemID,
597: lineNr,
598: /*elementName*/null,
599: /*attributeName*/null,
600: /*attributeValue*/null, "Missing #PCDATA in element "
601: + parentElementName);
602: }
603:
604: /**
605: * Throws an XMLValidationException to indicate that a #PCDATA element was
606: * unexpected.
607: *
608: * @param systemID the system ID of the data source
609: * @param lineNr the line number in the data source
610: * @param parentElementName the name of the parent element
611: */
612: static void errorUnexpectedPCData(String systemID, int lineNr,
613: String parentElementName) throws XMLValidationException {
614: throw new XMLValidationException(
615: XMLValidationException.UNEXPECTED_PCDATA, systemID,
616: lineNr,
617: /*elementName*/null,
618: /*attributeName*/null,
619: /*attributeValue*/null,
620: "Unexpected #PCDATA in element " + parentElementName);
621: }
622:
623: /**
624: * Throws an XMLValidationException.
625: *
626: * @param systemID the system ID of the data source
627: * @param lineNr the line number in the data source
628: * @param message the error message
629: * @param elementName the name of the element
630: * @param attributeName the name of the attribute
631: * @param attributeValue the value of that attribute
632: */
633: static void validationError(String systemID, int lineNr,
634: String message, String elementName, String attributeName,
635: String attributeValue) throws XMLValidationException {
636: throw new XMLValidationException(
637: XMLValidationException.MISC_ERROR, systemID, lineNr,
638: elementName, attributeName, attributeValue, message);
639: }
640:
641: }
|