Changeset 46
- Timestamp:
- 04/18/05 19:29:12 (4 years ago)
- Files:
-
- trunk/xml/ILexerStream.d (modified) (2 diffs)
- trunk/xml/IXMLConsumer.d (modified) (1 diff)
- trunk/xml/IXMLConsumerAdapter.d (modified) (1 diff)
- trunk/xml/IXMLProvider.d (modified) (1 diff)
- trunk/xml/OfflineProvider.d (modified) (1 diff)
- trunk/xml/SimpleStream.d (modified) (2 diffs)
- trunk/xml/XMLException.d (modified) (1 diff)
- trunk/xml/XMLLexer.d (modified) (7 diffs)
- trunk/xml/XMLParser.d (modified) (13 diffs)
- trunk/xml/XMLToken.d (modified) (3 diffs)
- trunk/xml/all.d (modified) (2 diffs)
- trunk/xmltest.d (modified) (7 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/xml/ILexerStream.d
r43 r46 30 30 31 31 interface ILexerStream{ 32 char[] getStreamName(); 32 33 bit hasMore(); 33 34 char get(); … … 35 36 char peek(); 36 37 Position getPosition(); 38 Position getLastPosition(); 37 39 bit isMatch(char[] match); 38 40 char[] getUntil(char match); trunk/xml/IXMLConsumer.d
r43 r46 30 30 interface IXMLConsumer{ 31 31 void xmlProlog(XMLAttributes attribs); 32 void xmlStartDoctype(char[] name ,char[] pubidLiteral,char[] systemLiteral);33 void xmlEndDoctype(char[] name ,char[] pubidLiteral,char[] systemLiteral);32 void xmlStartDoctype(char[] name); 33 void xmlEndDoctype(char[] name); 34 34 35 35 void xmlProcessingInstruction(char[] name,char[] data); trunk/xml/IXMLConsumerAdapter.d
r43 r46 31 31 class IXMLConsumerAdapter : IXMLConsumer{ 32 32 void xmlProlog(XMLAttributes attribs){} 33 void xmlStartDoctype(char[] name ,char[] pubidLiteral,char[] systemLiteral){}34 void xmlEndDoctype(char[] name ,char[] pubidLiteral,char[] systemLiteral){}33 void xmlStartDoctype(char[] name){} 34 void xmlEndDoctype(char[] name){} 35 35 36 36 void xmlProcessingInstruction(char[] name,char[] data){} trunk/xml/IXMLProvider.d
r43 r46 29 29 30 30 interface IXMLProvider{ 31 ILexerStream resolve URI(char[] pubid,char[] system);31 ILexerStream resolveExternalID(char[] pubid,char[] system); 32 32 } trunk/xml/OfflineProvider.d
r43 r46 27 27 28 28 private import xml.ILexerStream; 29 private import xml.SimpleStream; 29 30 private import xml.IXMLProvider; 31 private import xml.XMLException; 30 32 31 33 private import std.file; 34 private import std.utf; 32 35 33 36 class OfflineProvider : IXMLProvider{ 34 ILexerStream resolveURI(char[] pubid,char[] system){ 35 //throw out the public identifier 37 char[] currentPath; 38 39 public this(){ 40 currentPath = ""; 41 } 42 43 public this(char[] currentPath){ 44 this.currentPath = currentPath; 45 } 46 47 public void setCurrentPath(char[] currentPath){ 48 this.currentPath = currentPath; 49 } 50 51 ILexerStream resolveExternalID(char[] pubid,char[] system){ 52 //throw out the public identifier (not useful here) 36 53 37 if(system[0..5] == "http:"){ 38 return null; 54 if(system.length == 0) return new SimpleStream(""); 55 56 57 58 if(system.length < 5 || system[0..5] == "http:"){ 59 return null; // can't do this offline 39 60 } 40 61 41 62 // attempt to load the systemURI as a file 42 63 64 char[] filename; 65 66 if(system[0] != '/' || system[0] != '\\'){ 67 filename = currentPath ~ system; 68 } 69 else{ 70 filename = system; 71 } 72 73 try{ 74 return new SimpleStream(toUTF8(cast(char[])std.file.read(filename))); 75 } 76 catch(Exception e){ 77 throw new XMLException("failure to resolve file: " ~ system); 78 } 79 return null; 43 80 } 44 81 } trunk/xml/SimpleStream.d
r43 r46 32 32 class SimpleStream : ILexerStream{ 33 33 char[] data; 34 char[] name; 34 35 uint position; 35 36 uint lastPosition; 36 37 public this(char[] utf8data){ 37 38 public this(char[] name,char[] utf8data){ 39 this.name = name; 38 40 this.data = utf8data; 39 41 reset(); 42 } 43 44 public this(char[] utf8data){ 45 this("",utf8data); 46 } 47 48 public char[] getStreamName(){ 49 return name; 40 50 } 41 51 … … 70 80 } 71 81 82 Position getLastPosition(){ 83 Position pos; 84 pos.row = 1; 85 pos.col = 0; 86 87 for(int i=0; i<data.length && i<=lastPosition; i++){ 88 if(data[i] == '\n' || data[i] == '\r'){ 89 pos.row++; 90 pos.col = 1; 91 while(i<data.length && (data[i] == '\n' || data[i] == '\r')) i++; 92 } 93 else{ 94 pos.col++; 95 } 96 } 97 return pos; 98 } 99 72 100 public Position getPosition(){ 73 101 Position pos; trunk/xml/XMLException.d
r43 r46 27 27 28 28 private import xml.Position; 29 private import xml.BaseParser; 30 private import xml.BaseLexer; 31 private import xml.ILexerStream; 29 32 30 33 private import std.string; 31 34 35 //TODO: add chaining, named streams and references to originating lexer 36 32 37 class XMLException : Exception{ 38 XMLException cause; 39 33 40 public this(char[] reason){ 34 41 super(reason); 35 42 } 36 43 37 public this(Position pos,char[] reason){ 38 super(format("(%d,%d) %s",pos.row,pos.col,reason)); 44 public this(XMLException cause,char[] reason){ 45 super(reason); 46 this.cause = cause; 47 } 48 49 public char[] toString(){ 50 if(!cause) return super.toString(); 51 return super.toString() ~ "\n" ~ cause.toString(); 39 52 } 40 53 } 54 55 class XMLLexerException : XMLException{ 56 public BaseLexer lex; 57 public Position pos; 58 public char[] streamName; 59 60 public this(BaseLexer lex,ILexerStream stream,char[] reason){ 61 super(reason); 62 this.lex = lex; 63 pos = stream.getPosition(); 64 streamName = stream.getStreamName(); 65 } 66 67 public this(XMLException cause,BaseLexer lex,ILexerStream stream,char[] reason){ 68 super(cause,reason); 69 this.lex = lex; 70 pos = stream.getPosition(); 71 streamName = stream.getStreamName(); 72 } 73 74 public char[] toString(){ 75 return format("%s (%d,%d): %s\nToken Dump:\n%s",streamName,pos.row,pos.col,super.toString(),lex.toString()); 76 } 77 } 78 79 class XMLParserException : XMLException{ 80 public BaseParser parser; 81 public Position pos; 82 public char[] streamName; 83 84 public this(BaseParser parser,Position pos,char[] reason){ 85 super(reason); 86 this.parser = parser; 87 this.pos = pos; 88 streamName = parser.getStreamName(); 89 } 90 91 public this(XMLException cause,BaseParser parser,Position pos,char[] reason){ 92 super(cause,reason); 93 this.parser = parser; 94 this.pos = pos; 95 streamName = parser.getStreamName(); 96 } 97 98 public char[] toString(){ 99 return format("%s (%d,%d): %s\nToken Dump:\n%s",streamName,pos.row,pos.col,super.toString(),parser.toString()); 100 } 101 } trunk/xml/XMLLexer.d
r43 r46 26 26 module xml.XMLLexer; 27 27 28 private import xml.BaseLexer; 28 29 private import xml.ILexerStream; 29 30 private import xml.XMLToken; … … 35 36 private import std.string; 36 37 37 class XMLLexer{ 38 private XMLToken[] tokens; 39 private uint position; 40 private ILexerStream stream; // used while tokenizing 41 42 public this(){ 43 reset(); 44 } 45 46 public void reset(){ 47 position = 0; 48 tokens.length = 0; 49 } 50 51 public XMLToken[] getTokens(){ 52 return tokens; 53 } 54 55 protected bit hasMore(){ 56 return position < tokens.length; 57 } 58 59 protected XMLToken getNext(){ 60 if(position == tokens.length) throw new XMLException("unexpected end of file"); 61 XMLToken token = tokens[position]; 62 position++; 63 return token; 64 } 65 66 protected XMLToken getNext(uint type){ 67 if(position == tokens.length) throw new XMLException("unexpected end of file"); 68 XMLToken token = tokens[position]; 69 if(token.type != type) throw new XMLException(token.pos,"invalid token " ~ token.toName() ~ ", expected " ~ XMLToken.toName(type)); 70 position++; 71 return token; 72 } 73 74 protected XMLToken getNextOptional(uint type){ 75 if(position == tokens.length) return XMLToken.Null; 76 XMLToken token = tokens[position]; 77 if(token.type != type) return XMLToken.Null; 78 position++; 79 return token; 80 } 81 82 protected XMLToken getNext(uint type,char[] value){ 83 if(position == tokens.length) throw new XMLException("unexpected end of file"); 84 XMLToken token = tokens[position]; 85 if(token.type != type) throw new XMLException(token.pos,"invalid token " ~ token.toName() ~ ", expected " ~ XMLToken.toName(type)); 86 position++; 87 return token; 88 } 89 /* 90 protected void unget(uint times=1){ 91 position-=times; 92 if(position < 0) position = 0; 93 } 94 */ 95 protected XMLToken peek(){ 96 if(position == tokens.length) throw new XMLException("unexpected end of file"); 97 return tokens[position]; 98 } 99 100 /* 101 used to allow parsed entities to inject themselves into the token stream *at* the read position 102 note: this will have the side effect of changing the result of peek() before and after the call, 103 since the position will point to a different token 104 */ 105 protected void insertTokens(XMLToken[] newTokens){ 106 tokens = tokens[0..position] ~ newTokens ~ tokens[position..$]; 107 } 108 109 private void addToken(char value){ 110 addToken(value,toString(value)); 111 } 112 113 private void addToken(uint type,char[] value){ 114 XMLToken token; 115 116 token.pos = stream.getPosition(); 117 token.type = type; 118 token.value = value; 119 120 tokens.length = tokens.length + 1; 121 tokens[$-1] = token; 122 123 //printf("%.*s\n",token.toString()); 124 } 125 126 protected void tokenize(ILexerStream stream){ 38 class XMLLexer : BaseLexer{ 39 public void tokenize(ILexerStream stream){ 127 40 assert(this.stream == null); 128 41 this.stream = stream; … … 156 69 157 70 case '>': 158 throw new XML Exception(stream.getPosition(),"illegal token '>' (missing '<')");71 throw new XMLLexerException(this,stream,"illegal token '>' (missing '<')"); 159 72 break; 160 73 … … 171 84 this.stream = null; 172 85 } 173 174 /* hook to allow parsed entities to function */175 public void tokenizeDTD(ILexerStream stream){176 assert(this.stream == null);177 this.stream = stream;178 reset();179 tokenizeDTDElement();180 stream = null;181 }182 86 183 private void tokenizePI(){ 184 tokenizeName(); 185 addToken(TokSpace,stream.getWhileIn(" \t\r\n")); 186 187 // use element rules to lex xml directives 188 if(tolower(tokens[$-2].value) == "xml"){ 189 tokenizeElement(); 190 } 191 // use raw character data instead 192 else{ 193 addToken(TokChars,stream.getUntil("?>")); 194 addToken(stream.get()); 195 addToken(stream.get()); 196 } 197 } 198 199 private void tokenizeElement(){ 87 protected void tokenizeElement(){ 200 88 while(stream.hasMore()){ 201 89 switch(stream.peek()){ … … 203 91 addToken(stream.get()); 204 92 return; 205 93 94 case '=': 206 95 case '!': 96 case '/': 207 97 case '?': 208 case '/':209 case '=':210 98 addToken(stream.get()); 211 99 break; 212 100 213 101 case '[': 214 addToken(stream.get());102 stream.get(); 215 103 tokenizeDTD(); 216 104 break; … … 225 113 case '\r': 226 114 case '\n': 227 addToken(TokSpace,stream.getWhileIn(" /t/r/n")); 228 break; 229 230 case '&': 231 tokenizeEntity(); 115 addToken(TokSpace,stream.getWhileIn(" \t\r\n")); 232 116 break; 233 117 … … 240 124 } 241 125 242 private void tokenizeDTD(){ 243 while(stream.hasMore()){ 244 switch(stream.peek()){ 245 case '<': 246 if(stream.isMatch("<!--")){ 247 //TODO: the spec maintains that '-' is also illegal inside the comment - fix this 248 stream.get(4); 249 addToken(TokComment,stream.getUntil("-->")); 250 stream.get(3); 251 } 252 else if(stream.isMatch("<?")){ 253 addToken(stream.get()); // eat '<' 254 addToken(stream.get()); // eat '?' 255 tokenizePI(); 256 } 257 else{ 258 addToken(stream.get()); 259 tokenizeDTDElement(); 260 } 261 break; 262 263 case '%': 264 // handle parsed entity 265 //TODO: break into separate function 266 addToken(stream.get()); 267 addToken(TokName,stream.getUntil(';')); 268 addToken(stream.get()); 269 break; 270 271 case ']': 272 addToken(stream.get()); 273 return; 274 275 case ' ': 276 case '\t': 277 case '\r': 278 case '\n': 279 addToken(TokSpace,stream.getWhileIn(" \t\r\n")); 280 break; 281 282 default: 283 throw new XMLException(stream.getPosition(),"illegal token '" ~ toString(stream.get) ~ "'"); 284 break; 285 } 286 } 287 } 288 289 private void tokenizeDTDElement(){ 290 while(stream.hasMore()){ 291 switch(stream.peek()){ 292 case '>': 293 addToken(stream.get()); 294 return; 295 296 case '!': 297 case '?': 298 case '/': 299 case '=': 300 case '|': 301 case ',': 302 case '+': 303 case '-': 304 case '(': 305 case ')': 306 case '*': 307 case '%': 308 case ';': 309 addToken(stream.get()); 310 break; 311 312 case '\'': 313 case '\"': 314 tokenizeString(stream.get()); 315 break; 316 317 case ' ': 318 case '\t': 319 case '\r': 320 case '\n': 321 addToken(TokSpace,stream.getWhileIn(" \t\r\n")); 322 break; 323 324 case '&': 325 tokenizeEntity(); 326 break; 327 328 case '#': 329 tokenizeNmtoken(); 330 break; 331 332 default: 333 tokenizeName(); 334 break; 335 } 336 } 337 } 338 339 private bit verifyNameChar(char ch){ 340 return 341 ch == '_' || 342 ch == ':' || 343 ch == '.' || 344 (ch >= 'a' && ch <= 'z') || 345 (ch >= 'A' && ch <= 'Z') || 346 (ch >= '0' && ch <= '9') 347 ; 348 } 349 350 private bit verifyNameStartChar(char ch){ 351 return 352 ch == '_' || 353 ch == ':' || 354 (ch >= 'a' && ch <= 'z') || 355 (ch >= 'A' && ch <= 'Z') 356 ; 357 } 358 359 private void tokenizeName(){ 360 361 if(!verifyNameStartChar(stream.peek())){ 362 throw new XMLException(stream.getPosition(),"illegal token '" ~ toString(stream.peek()) ~ "'"); 363 } 364 365 addToken(TokName,stream.getWhile(&verifyNameChar)); 366 } 367 368 private void tokenizeNmtoken(){ 369 stream.get(); //eat '#' token 370 addToken(TokName,stream.getWhile(&verifyNameChar)); 371 } 372 373 private void tokenizeString(char terminator){ 374 char[] value; 375 char[4] term; 376 term[0] = terminator; 377 term[1] = '&'; 378 term[2] = '%'; // capture tokens useful for PE-sensitive areas 379 term[3] = ';'; 380 381 addToken(TokString,toString(terminator)); 382 383 while(stream.hasMore()){ 384 addToken(TokChars,stream.getUntilIn(term)); 385 char ch = stream.peek(); 386 if(ch == terminator){ 387 addToken(TokString,toString(stream.get())); 388 return; 389 } 390 else if(ch == '&'){ 391 tokenizeEntity(); 392 } 393 else{ 394 addToken(stream.get); // plug '%' and ';' in as separate bits 395 } 396 } 397 throw new XMLException(stream.getPosition(),"unexpected end of file"); 398 } 399 400 private void tokenizeEntity(){ 401 stream.get(); //eat '&' token 402 if(stream.peek() == '#'){ 403 char[] characterValue; 404 ulong value; 405 406 stream.get(); //eat '#' token 407 if(stream.peek() == 'x'){ 408 stream.get(); //eat 'x' token 409 characterValue = stream.getUntil(';'); 410 411 //convert using radix16 412 value = 0; 413 for(uint i=0; i<characterValue.length; i++){ 414 char ch = characterValue[i]; 415 value *= 16; 416 417 if(ch >= 'a' && ch <= 'z'){ 418 value += (ch - 'a') + 10; 419 } 420 else if(ch >= 'A' && ch <= 'Z'){ 421 value += (ch - 'A') + 10; 422 } 423 else if(ch >= '0' && ch <= '9'){ 424 value += (ch - '0'); 425 } 426 else{ 427 throw new XMLException(stream.getPosition(),"invalid entity (not valid hex number)"); 428 } 429 } 430 } 431 else{ 432 characterValue = stream.getUntil(';'); 433 value = toUlong(characterValue); 434 } 435 dchar[1] newValue; 436 newValue[0] = cast(dchar)value; 437 438 addToken(TokChars,toUTF8(newValue)); 439 } 440 else{ 441 addToken(TokEntity,stream.getUntil(';')); 442 } 443 stream.get(); //eat ';' token 444 } 445 446 private static char[] toString(char ch){ 447 char[] val = new char[1]; 448 val[0] = ch; 449 return val; 450 } 451 452 public char[] dumpTokens(){ 453 char[] output = ""; 454 foreach(XMLToken tok; tokens){ 455 output ~= tok.toString() ~ "\n"; 456 } 457 return output; 126 protected void tokenizeDTD(){ 127 stream.getWhileIn(" \t\r\n"); 128 addToken(TokDTD,stream.getUntil(']')); 129 stream.get(); 458 130 } 459 131 } trunk/xml/XMLParser.d
r43 r46 31 31 private import xml.XMLLexer; 32 32 private import xml.XMLToken; 33 private import xml.BaseLexer; 34 private import xml.BaseParser; 33 35 private import xml.XMLException; 34 36 private import xml.DTDParser; 35 37 private import xml.SimpleStream; 36 38 private import xml.OfflineProvider; … … 46 48 */ 47 49 48 class XMLParser : XMLLexer{ 49 private IXMLProvider provider; 50 private IXMLConsumer consumer; 51 52 char[][char[]] entities; 53 XMLToken[][char[]] parsedEntities; 54 55 public this(){ 56 entities["apos"] = "'"; 57 entities["quot"] = "\""; 58 entities["lt"] = "<"; 59 entities["gt"] = ">"; 60 entities["amp"] = "&"; 61 } 62 63 public void parse(IXMLConsumer consumer, IXMLProvider provider, ILexerStream stream){ 64 this.provider = provider; 65 this.consumer = consumer; 66 67 tokenize(stream); 68 startParse(); 69 } 70 71 public void parse(IXMLConsumer consumer, ILexerStream stream){ 72 parse(consumer, new OfflineProvider(), stream); 73 } 74 75 public void parse(IXMLConsumer consumer, char[] utf8data){ 76 parse(consumer, new OfflineProvider(), new SimpleStream(utf8data)); 77 } 78 79 private void startParse(){ 80 parseProlog(); 50 class XMLParser : BaseParser{ 51 protected BaseLexer getLexer(){ 52 return new XMLLexer(); 53 } 54 55 protected void startParse(){ 56 getNext(TokStartElem); 57 58 if(peek().type == TokQuestion){ 59 parseProlog(); 60 } 61 parseMisc(); 81 62 82 63 if(peek().type == TokName){ … … 86 67 } 87 68 88 pr ivatevoid parseProlog(){69 protected void parseProlog(){ 89 70 /* 90 71 [22] prolog ::= XMLDecl? Misc* (doctypedecl Misc*)? … … 95 76 */ 96 77 97 //<?xml 98 getNext(TokStartElem); 78 //?xml 99 79 getNext(TokQuestion); 100 if( getNext().value!= "xml"){101 throw new XML Exception(peek().pos,"document prolog must begin with an 'xml' processing instruction");80 if(std.string.tolower(getNext().value) != "xml"){ 81 throw new XMLParserException(this,peek().pos,"document prolog must begin with an 'xml' processing instruction"); 102 82 } 103 83 … … 116 96 getNext(); // eat token 117 97 parseDoctype(); 118 parseMisc(); 119 } 120 } 121 122 private void parseDoctype(){ 98 } 99 } 100 101 protected void parseMisc(){ 102 /* 103 [27] Misc ::= Comment | PI | S 104 */ 105 while(hasMore()){ 106 switch(peek().type){ 107 case TokSpace: 108 getNext(); // eat whitespace 109 break; 110 111 case TokComment: 112 consumer.xmlComment(getNext().value); 113 break; 114 115 case TokStartElem: 116 getNext(); // eat '<' token 117 if(peek().type == TokQuestion){ 118 getNext(); // eat '?' token 119 parseProcessingInstruction(); 120 break; 121 } 122 else return; // something other than a PI, let parent worry about it 123 124 default: 125 throw new XMLParserException(this,peek().pos,"expected processing instruction"); 126 } 127 } 128 } 129 130 protected void parseDoctype(){ 123 131 /* 124 132 [28] doctypedecl ::= '<!DOCTYPE' S Name (S ExternalID)? S? ('[' intSubset ']' S?)? '>' [VC: Root Element Type][WFC: External Subset] … … 134 142 135 143 name = getNext(TokName).value; 136 getNextOptional(TokSpace); 144 getNextOptional(TokSpace); 137 145 138 146 if(peek().type == TokName){ 139 if(peek().value == "SYSTEM"){ 140 getNext(); // eat token 141 getNext(TokSpace); 142 systemLiteral = parseString(); 143 } 144 else if(peek().value == "PUBLIC"){ 145 getNext(); // eat token 146 getNext(TokSpace); 147 pubidLiteral = parseString(); 148 getNext(TokSpace); 149 systemLiteral = parseString(); 147 ILexerStream newStream = parseExternalID(); 148 149 if(newStream){ 150 // check for non-empty stream 151 if(newStream.hasMore()){ 152 //parse against DTD 153 DTDParser parser = new DTDParser(); 154 parser.parse(consumer,provider,newStream); 155 156 //copy any useful bits 157 copyParserData(parser); 158 } 150 159 } 151 160 else{ 152 throw new XMLException(peek().pos,"document external identity must begin with 'PUBLIC' or 'SYSTEM'"); 153 } 154 getNextOptional(TokSpace); 155 } 156 157 consumer.xmlStartDoctype(name,pubidLiteral,systemLiteral); 161 throw new XMLParserException(this,peek().pos,"could not resolve external DTD"); 162 } 163 164 getNextOptional(TokSpace); 165 } 166 167 consumer.xmlStartDoctype(name); 158 168 159 169 // parse all the elements under the doctype 160 if(peek().type == TokStartDTD){ 161 getNext(); // eat '[' token 162 parseDTD(); 170 if(peek().type == TokDTD){ 171 SimpleStream newStream = new SimpleStream(getNext().value); 172 DTDParser parser; 173 try{ 174 parser = new DTDParser(); 175 parser.parse(consumer,provider,newStream); 176 } 177 catch(XMLException e){ 178 throw new XMLParserException(e,this,peek().pos,"Error in parsing DTD"); 179 } 180 181 //copy any useful bits 182 copyParserData(parser); 163 183 getNextOptional(TokSpace); 164 184 } … … 166 186 getNext(TokEndElem); 167 187 168 consumer.xmlEndDoctype(name,pubidLiteral,systemLiteral); 169 } 170 171 private void parseDTD(){ 172 while(hasMore()){ 173 switch(peek().type){ 174 case TokSpace: 175 getNext(); // eat token; 176 break; 177 178 case TokComment: 179 consumer.xmlComment(getNext().value); 180 break; 181 182 case TokPercent: 183 getNext(); // eat token 184 parseParsedEntity(); 185 break; 186 187 case TokStartElem: 188 getNext(); // eat token; 189 190 if(peek().type == TokQuestion){ 191 getNext(); // eat token; 192 parseProcesingInstruction(); 193 } 194 else{ 195 getNext(TokBang); // next must be a '!' 196 197 // check out the element name 198 XMLToken tok = getNext(TokName); 199 switch(tok.value){ 200 case "ELEMENT": 201 parseDTDElement(); 202 break; 203 204 case "ATTLIST": 205 parseDTDAttlist(); 206 break; 207 208 case "ENTITY": 209 parseDTDEntity(); 210 break; 211 212 case "NOTATION": 213 parseDTDNotation(); 214 break; 215 216 default: 217 throw new XMLException(tok.pos,"invalid DTD element: '" ~ tok.value ~ "'"); 218 } 219 } 220 break; 221 222 case TokEndDTD: 223 getNext(); // eat ']' token 224 return; 225 226 default: 227 // shouldn't ever get here 228 throw new XMLException(peek().pos,"expected DTD element or closing ']'"); 229 break; 230 } 231 } 232 } 233 234 private void parseDTDElement(){ 235 /* 236 [45] elementdecl ::= '<!ELEMENT' S Name S contentspec S? '>' [VC: Unique Element Type Declaration] 237 [46] contentspec ::= 'EMPTY' | 'ANY' | Mixed | children 238 */ 239 240 char[] name; 241 242 getNext(TokSpace); 243 name = getNext(TokName).value; 244 245 //TODO: unbreak me 246 while(hasMore()){ 247 if(getNext().type == TokEndElem){ 248 break; 249 } 250 } 251 } 252 253 private void parseDTDAttlist(){ 254 /* 255 [45] elementdecl ::= '<!ELEMENT' S Name S contentspec S? '>' [VC: Unique Element Type Declaration] 256 [46] contentspec ::= 'EMPTY' | 'ANY' | Mixed | children 257 */ 258 259 //TODO: unbreak me 260 while(hasMore()){ 261 if(getNext().type == TokEndElem){ 262 break; 263 } 264 } 265 } 266 267 private void parseDTDEntity(){ 268 /* 269 [70] EntityDecl ::= GEDecl | PEDecl 270 [71] GEDecl ::= '<!ENTITY' S Name S EntityDef S? '>' 271 [72] PEDecl ::= '<!ENTITY' S '%' S Name S PEDef S? '>' 272 [73] EntityDef ::= EntityValue| (ExternalID NDataDecl?) 273 [74] PEDef ::= EntityValue | ExternalID 274 275 [9] EntityValue ::= '"' ([^%&"] | PEReference | Reference)* '"' 276 | "'" ([^%&'] | PEReference | Reference)* "'" 277 278 [67] Reference ::= EntityRef | CharRef 279 [68] EntityRef ::= '&' Name ';' 280 [69] PEReference ::= '%' Name ';' 281 282 [75] ExternalID ::= 'SYSTEM' S SystemLiteral 283 284 [76] NDataDecl ::= S 'NDATA' S Name 285 | 'PUBLIC' S PubidLiteral S SystemLiteral 286 */ 287 char[] name, value; 288 289 getNext(TokSpace); 290 291 switch(peek().type){ 292 case TokPercent: 293 // handle parsed entity 294 getNext(); // eat token; 188 consumer.xmlEndDoctype(name); 189 } 190 191 protected ILexerStream parseExternalID(){ 192 char[] pubidLiteral; 193 char[] systemLiteral; 194 195 if(peek().value == "SYSTEM"){ 196 pubidLiteral = ""; // empty value 197 getNext(); // eat token 295 198 getNext(TokSpace); 296 297 name = getNext(TokName).value; 199 systemLiteral = parseString(); 200 } 201 else if(peek().value == "PUBLIC"){ 202 getNext(); // eat token 298 203 getNext(TokSpace); 299  
