| 1 |
/******************************************************************************* |
|---|
| 2 |
License: |
|---|
| 3 |
Copyright (c) 2005 John Demme |
|---|
| 4 |
|
|---|
| 5 |
This software is provided 'as-is', without any express or implied |
|---|
| 6 |
warranty. In no event will the authors be held liable for damages |
|---|
| 7 |
of any kind arising from the use of this software. |
|---|
| 8 |
|
|---|
| 9 |
Permission is hereby granted to anyone to use this software for any |
|---|
| 10 |
purpose, including commercial applications, and to alter it and/or |
|---|
| 11 |
redistribute it freely, subject to the following restrictions: |
|---|
| 12 |
|
|---|
| 13 |
1. The origin of this software must not be misrepresented; you must |
|---|
| 14 |
not claim that you wrote the original software. If you use this |
|---|
| 15 |
software in a product, an acknowledgment within documentation of |
|---|
| 16 |
said product would be appreciated but is not required. |
|---|
| 17 |
|
|---|
| 18 |
2. Altered source versions must be plainly marked as such, and must |
|---|
| 19 |
not be misrepresented as being the original software. |
|---|
| 20 |
|
|---|
| 21 |
3. This notice may not be removed or altered from any distribution |
|---|
| 22 |
of the source. |
|---|
| 23 |
|
|---|
| 24 |
4. Derivative works are permitted, but they must carry this notice |
|---|
| 25 |
in full and credit the original source. |
|---|
| 26 |
|
|---|
| 27 |
|
|---|
| 28 |
Date: Initial version, December 2005 |
|---|
| 29 |
Authors: John Demme (me@teqdruid.com) |
|---|
| 30 |
|
|---|
| 31 |
Version: 0.1b |
|---|
| 32 |
History: Converted from the original mango.xml.dom.parser.teqXML |
|---|
| 33 |
|
|---|
| 34 |
See_Also: ISAXReader, ISAXHandler |
|---|
| 35 |
|
|---|
| 36 |
*******************************************************************************/ |
|---|
| 37 |
|
|---|
| 38 |
module mango.xml.sax.parser.teqXML; |
|---|
| 39 |
|
|---|
| 40 |
private import Text = tango.text.Util; |
|---|
| 41 |
|
|---|
| 42 |
private import tango.text.convert.UnicodeBom, |
|---|
| 43 |
Integer = tango.text.convert.Integer, |
|---|
| 44 |
Utf = tango.text.convert.Utf; |
|---|
| 45 |
|
|---|
| 46 |
private import mango.xml.sax.Exceptions, |
|---|
| 47 |
mango.xml.sax.model.ISAXHandler, |
|---|
| 48 |
mango.xml.sax.model.ISAXParser; |
|---|
| 49 |
|
|---|
| 50 |
private import tango.io.model.IConduit, |
|---|
| 51 |
tango.io.model.IBuffer; |
|---|
| 52 |
|
|---|
| 53 |
private import tango.io.Buffer; |
|---|
| 54 |
|
|---|
| 55 |
private import tango.stdc.stdlib; |
|---|
| 56 |
|
|---|
| 57 |
private import tango.io.Stdout; |
|---|
| 58 |
|
|---|
| 59 |
public class TeqXMLException : SAXException |
|---|
| 60 |
{ |
|---|
| 61 |
char[] msg; |
|---|
| 62 |
|
|---|
| 63 |
this(char[] msg) |
|---|
| 64 |
{ |
|---|
| 65 |
super(msg); |
|---|
| 66 |
this.msg = msg; |
|---|
| 67 |
} |
|---|
| 68 |
|
|---|
| 69 |
char[] toString() |
|---|
| 70 |
{ |
|---|
| 71 |
return msg; |
|---|
| 72 |
} |
|---|
| 73 |
} |
|---|
| 74 |
|
|---|
| 75 |
public class TeqXMLEofException : TeqXMLException |
|---|
| 76 |
{ |
|---|
| 77 |
this() |
|---|
| 78 |
{ |
|---|
| 79 |
super("Unexpectedly reached EOF"); |
|---|
| 80 |
} |
|---|
| 81 |
} |
|---|
| 82 |
|
|---|
| 83 |
deprecated alias XMLWriter TeqXMLWriter; |
|---|
| 84 |
|
|---|
| 85 |
/** |
|---|
| 86 |
A simplistic XML encoder. Reusable, so a pool of these would work rather |
|---|
| 87 |
nicely. Not thread safe, however, so be sure that only one thread is trying |
|---|
| 88 |
to use it at once. |
|---|
| 89 |
*/ |
|---|
| 90 |
class XMLWriter(T=char): ISAXWriter!(T) |
|---|
| 91 |
{ |
|---|
| 92 |
static assert ( is (T == char) |
|---|
| 93 |
|| is (T == wchar) |
|---|
| 94 |
|| is (T == dchar)); |
|---|
| 95 |
|
|---|
| 96 |
private class MyWriter { |
|---|
| 97 |
MyWriter opCall(T[] str) { |
|---|
| 98 |
void[] output = encoder.encode(str); |
|---|
| 99 |
buffer.consume(output); |
|---|
| 100 |
return this; |
|---|
| 101 |
} |
|---|
| 102 |
|
|---|
| 103 |
MyWriter opCall(T c) { |
|---|
| 104 |
T[] str = (&c)[0..1]; |
|---|
| 105 |
void[] output = encoder.encode(str); |
|---|
| 106 |
buffer.consume(output); |
|---|
| 107 |
return this; |
|---|
| 108 |
} |
|---|
| 109 |
} |
|---|
| 110 |
|
|---|
| 111 |
private static enum Status |
|---|
| 112 |
{ |
|---|
| 113 |
Start, |
|---|
| 114 |
End, |
|---|
| 115 |
InTag, |
|---|
| 116 |
InElement |
|---|
| 117 |
} |
|---|
| 118 |
|
|---|
| 119 |
private Status status; |
|---|
| 120 |
private uint tabs; |
|---|
| 121 |
private bool pretty; |
|---|
| 122 |
private UnicodeBom!(T) encoder; |
|---|
| 123 |
private MyWriter writer; |
|---|
| 124 |
private IBuffer buffer; |
|---|
| 125 |
|
|---|
| 126 |
private static struct Const |
|---|
| 127 |
{ |
|---|
| 128 |
static: |
|---|
| 129 |
const T[] lt = "<"; |
|---|
| 130 |
const T[] ltE = "<"; // The capitol E is for entity |
|---|
| 131 |
const T[] gt = ">"; |
|---|
| 132 |
const T[] gtE = ">"; |
|---|
| 133 |
const T[] ampE = "&"; |
|---|
| 134 |
const T[] aposE = "'"; |
|---|
| 135 |
const T[] quest = "?"; |
|---|
| 136 |
const T[] space = " "; |
|---|
| 137 |
const T[] tab = "\t"; |
|---|
| 138 |
const T[] dqm = "\""; |
|---|
| 139 |
const T[] dqmE = """; |
|---|
| 140 |
const T[] equals = "="; |
|---|
| 141 |
const T[] fs = "/"; |
|---|
| 142 |
const T[] openComment = "<!--"; |
|---|
| 143 |
const T[] closeComment = "-->"; |
|---|
| 144 |
const T[] preambleStart = "<?xml version=\"1.0\" encoding=\""; |
|---|
| 145 |
const T[] preambleEnd = "\"?>"; |
|---|
| 146 |
const T[] utf8 = "UTF-8"; |
|---|
| 147 |
const T[] utf16 = "UTF-16"; |
|---|
| 148 |
const T[] utf32 = "UTF32"; |
|---|
| 149 |
const T[] CR = "\n"; |
|---|
| 150 |
} |
|---|
| 151 |
|
|---|
| 152 |
public this() |
|---|
| 153 |
{ |
|---|
| 154 |
status = Status.End; |
|---|
| 155 |
tabs = 0; |
|---|
| 156 |
writer = new MyWriter(); |
|---|
| 157 |
encoder = new UnicodeBom!(T)(Encoding.Unknown); |
|---|
| 158 |
} |
|---|
| 159 |
|
|---|
| 160 |
private void writeTabs() |
|---|
| 161 |
{ |
|---|
| 162 |
if (pretty) |
|---|
| 163 |
for(uint i=0; i<tabs; i++) |
|---|
| 164 |
writer(Const.tab); |
|---|
| 165 |
} |
|---|
| 166 |
|
|---|
| 167 |
void startDocument(OutputStream outStream, Encoding outputType = Encoding.UTF_8N, bool pretty=true) |
|---|
| 168 |
{ |
|---|
| 169 |
buffer = cast(IBuffer) outStream; |
|---|
| 170 |
if (buffer is null) |
|---|
| 171 |
buffer = new Buffer(outStream.conduit()); |
|---|
| 172 |
|
|---|
| 173 |
assert(status == Status.End); |
|---|
| 174 |
if (outputType == Encoding.UTF_8) |
|---|
| 175 |
outputType = Encoding.UTF_8N; |
|---|
| 176 |
encoder.setup(outputType); |
|---|
| 177 |
|
|---|
| 178 |
status = Status.Start; |
|---|
| 179 |
tabs = 0; |
|---|
| 180 |
this.pretty = pretty; |
|---|
| 181 |
|
|---|
| 182 |
writer(Const.preambleStart); |
|---|
| 183 |
switch (outputType) { |
|---|
| 184 |
case Encoding.UTF_8: |
|---|
| 185 |
case Encoding.UTF_8N: |
|---|
| 186 |
writer(Const.utf8); |
|---|
| 187 |
break; |
|---|
| 188 |
case Encoding.UTF_16: |
|---|
| 189 |
case Encoding.UTF_16LE: |
|---|
| 190 |
case Encoding.UTF_16BE: |
|---|
| 191 |
writer(Const.utf16); |
|---|
| 192 |
break; |
|---|
| 193 |
case Encoding.UTF_32: |
|---|
| 194 |
case Encoding.UTF_32LE: |
|---|
| 195 |
case Encoding.UTF_32BE: |
|---|
| 196 |
writer(Const.utf32); |
|---|
| 197 |
break; |
|---|
| 198 |
default: |
|---|
| 199 |
throw new TeqXMLException("Specified encoding not supported"); |
|---|
| 200 |
} |
|---|
| 201 |
writer(Const.preambleEnd); |
|---|
| 202 |
if (pretty) writer(Const.CR); |
|---|
| 203 |
} |
|---|
| 204 |
|
|---|
| 205 |
void endDocument() |
|---|
| 206 |
{ |
|---|
| 207 |
buffer.flush(); |
|---|
| 208 |
status = Status.End; |
|---|
| 209 |
} |
|---|
| 210 |
|
|---|
| 211 |
void processingInstruction(T[] target, T[] data) |
|---|
| 212 |
{ |
|---|
| 213 |
assert(status != Status.End); |
|---|
| 214 |
writeTabs(); |
|---|
| 215 |
writer(Const.lt)(Const.quest)(target)(Const.space)(data)(Const.quest)(Const.gt); |
|---|
| 216 |
if (pretty) writer(Const.CR); |
|---|
| 217 |
} |
|---|
| 218 |
|
|---|
| 219 |
void startElement(T[] name) |
|---|
| 220 |
{ |
|---|
| 221 |
assert(status == Status.Start || status == Status.InElement || status == Status.InTag); |
|---|
| 222 |
if (status == Status.InTag) |
|---|
| 223 |
{ |
|---|
| 224 |
status = Status.InElement; |
|---|
| 225 |
writer(Const.gt); |
|---|
| 226 |
if (pretty) writer(Const.CR); |
|---|
| 227 |
} |
|---|
| 228 |
writeTabs(); |
|---|
| 229 |
writer(Const.lt)(name); |
|---|
| 230 |
tabs++; |
|---|
| 231 |
status = Status.InTag; |
|---|
| 232 |
} |
|---|
| 233 |
|
|---|
| 234 |
void addAttribute(T[] key, T[] value) |
|---|
| 235 |
{ |
|---|
| 236 |
assert(status == Status.InTag); |
|---|
| 237 |
writer(Const.space)(key)(Const.equals)(Const.dqm); |
|---|
| 238 |
outputChecked(value); |
|---|
| 239 |
writer(Const.dqm); |
|---|
| 240 |
} |
|---|
| 241 |
|
|---|
| 242 |
void endElement(T[] name) |
|---|
| 243 |
{ |
|---|
| 244 |
tabs--; |
|---|
| 245 |
if (status == Status.InTag) |
|---|
| 246 |
{ |
|---|
| 247 |
writer(Const.fs)(Const.gt); |
|---|
| 248 |
status = Status.InElement; |
|---|
| 249 |
} |
|---|
| 250 |
else |
|---|
| 251 |
{ |
|---|
| 252 |
assert(status == Status.InElement); |
|---|
| 253 |
if (pretty) writeTabs(); |
|---|
| 254 |
writer(Const.lt)(Const.fs)(name)(Const.gt); |
|---|
| 255 |
} |
|---|
| 256 |
|
|---|
| 257 |
if (pretty) writer(Const.CR); |
|---|
| 258 |
} |
|---|
| 259 |
|
|---|
| 260 |
void characterData (T[] data, CDataStatus cstatus, bool check = true) |
|---|
| 261 |
{ |
|---|
| 262 |
assert(status != Status.End); |
|---|
| 263 |
|
|---|
| 264 |
if (status == Status.InElement) |
|---|
| 265 |
{ |
|---|
| 266 |
if (cstatus.containsStart()) |
|---|
| 267 |
writeTabs(); |
|---|
| 268 |
} |
|---|
| 269 |
else if (status == Status.InTag) |
|---|
| 270 |
{ |
|---|
| 271 |
assert(cstatus.containsStart()); |
|---|
| 272 |
status = Status.InElement; |
|---|
| 273 |
writer(Const.gt); |
|---|
| 274 |
if (pretty) writer(Const.CR); |
|---|
| 275 |
writeTabs(); |
|---|
| 276 |
} |
|---|
| 277 |
|
|---|
| 278 |
if (!check) |
|---|
| 279 |
{ |
|---|
| 280 |
writer(data); |
|---|
| 281 |
} |
|---|
| 282 |
else |
|---|
| 283 |
{ |
|---|
| 284 |
outputChecked(data); |
|---|
| 285 |
} |
|---|
| 286 |
|
|---|
| 287 |
if (pretty && cstatus.containsEnd()) |
|---|
| 288 |
writer(Const.CR); |
|---|
| 289 |
} |
|---|
| 290 |
|
|---|
| 291 |
private void outputChecked(T[] data) |
|---|
| 292 |
{ |
|---|
| 293 |
foreach(T c; data) |
|---|
| 294 |
{ |
|---|
| 295 |
if (c == '<') |
|---|
| 296 |
writer(Const.ltE); |
|---|
| 297 |
else if (c == '>') |
|---|
| 298 |
writer(Const.gtE); |
|---|
| 299 |
else if (c == '&') |
|---|
| 300 |
writer(Const.ampE); |
|---|
| 301 |
else if (c == '\'') |
|---|
| 302 |
writer(Const.aposE); |
|---|
| 303 |
else if (c == '"') |
|---|
| 304 |
writer(Const.dqmE); |
|---|
| 305 |
else |
|---|
| 306 |
writer(c); |
|---|
| 307 |
} |
|---|
| 308 |
return; |
|---|
| 309 |
} |
|---|
| 310 |
|
|---|
| 311 |
void comment(T[] text) |
|---|
| 312 |
{ |
|---|
| 313 |
assert(status != Status.End); |
|---|
| 314 |
writeTabs(); |
|---|
| 315 |
writer(Const.openComment)(text)(Const.closeComment); |
|---|
| 316 |
if (pretty) writer(Const.CR); |
|---|
| 317 |
} |
|---|
| 318 |
} |
|---|
| 319 |
|
|---|
| 320 |
private final const uint LONG_ENTITY = 1024; //Longest entity allowed is 1024 - 2 |
|---|
| 321 |
|
|---|
| 322 |
deprecated alias XMLReader TeqXMLReader; |
|---|
| 323 |
|
|---|
| 324 |
/** |
|---|
| 325 |
A fairly simplistic SAX parser. Reusable, so a pool of these would work nicely. This Reader |
|---|
| 326 |
is not thread-safe, however, so be sure that only one thread attempts to call parse at once. |
|---|
| 327 |
Non-validating. Memory efficient: the only heap allocations after initialization are increasing |
|---|
| 328 |
the size of the input buffer (if necessary) and (small constant)*(maximum depth of XML tree) |
|---|
| 329 |
for a tag validation stack. |
|---|
| 330 |
|
|---|
| 331 |
Standards: Mostly conforms to the XML 1.0 spec |
|---|
| 332 |
|
|---|
| 333 |
Bugs: |
|---|
| 334 |
Does not support parsed entities or external references. |
|---|
| 335 |
*/ |
|---|
| 336 |
class XMLReader(T=char): ISAXReader!(T) |
|---|
| 337 |
{ |
|---|
| 338 |
private alias bool function(T) PropCheck; |
|---|
| 339 |
|
|---|
| 340 |
private ISAXHandler!(T) myHandler; |
|---|
| 341 |
private uint[] myStack; //Just store hashes instead of names |
|---|
| 342 |
|
|---|
| 343 |
package ReaderString!(T) reader; |
|---|
| 344 |
private ReaderString!(T).Marker start, end; |
|---|
| 345 |
|
|---|
| 346 |
private T[][T[]] entities; |
|---|
| 347 |
private T[] amp = "amp", lt = "lt", gt = "gt", apos = "apos", quot = "quot"; |
|---|
| 348 |
private T[] ampV = "&", ltV = "<", gtV = ">", aposV = "'", quotV = "\""; |
|---|
| 349 |
|
|---|
| 350 |
private bool first; |
|---|
| 351 |
|
|---|
| 352 |
/** |
|---|
| 353 |
Initializes the parser. This parser is reusable, so you can process multiple documents with |
|---|
| 354 |
only one instance (just not at once). |
|---|
| 355 |
|
|---|
| 356 |
Params: |
|---|
| 357 |
bufferLength = When we initialize our internal buffer, we'll use this value (if supplied). Unit |
|---|
| 358 |
is code-points, so a buffer length of 1024 allows for 1024 transcoded code-points in the buffer. |
|---|
| 359 |
This buffer size must be at least a large as the longest tag in a document if resizing is to be |
|---|
| 360 |
avoided. Default is 512. |
|---|
| 361 |
*/ |
|---|
| 362 |
public this(uint bufferLength=0) |
|---|
| 363 |
{ |
|---|
| 364 |
if (bufferLength == 0) |
|---|
| 365 |
bufferLength = 512; |
|---|
| 366 |
reader = new ReaderString!(T)(bufferLength); |
|---|
| 367 |
start = reader.markers[0]; |
|---|
| 368 |
end = reader.markers[1]; |
|---|
| 369 |
} |
|---|
| 370 |
|
|---|
| 371 |
private void parse(ISAXHandler!(T) handler) { |
|---|
| 372 |
myStack.length = 0; |
|---|
| 373 |
this.myHandler = handler; |
|---|
| 374 |
foreach(T[] key; entities.keys) |
|---|
| 375 |
entities.remove(key); |
|---|
| 376 |
entities[amp] = ampV; |
|---|
| 377 |
entities[lt] = ltV; |
|---|
| 378 |
entities[gt] = gtV; |
|---|
| 379 |
entities[apos] = aposV; |
|---|
| 380 |
entities[quot] = quotV; |
|---|
| 381 |
myHandler.startDocument(); |
|---|
| 382 |
go(); |
|---|
| 383 |
myHandler.endDocument(); |
|---|
| 384 |
} |
|---|
| 385 |
|
|---|
| 386 |
/** |
|---|
| 387 |
Tells the parser to begin processing a document |
|---|
| 388 |
|
|---|
| 389 |
Params: |
|---|
| 390 |
source = The IBuffer that the parser should draw xml content from |
|---|
| 391 |
handler = The client interface who's methods the parser should call |
|---|
| 392 |
encoding = If the client knows it, tell the parser the text encoding of the incoming XML. |
|---|
| 393 |
If not specified, the parser will guess, and failing that assume UTF8N. The enum for these types |
|---|
| 394 |
is in mango.convert.Unicode. |
|---|
| 395 |
*/ |
|---|
| 396 |
public void parse(InputStream source, ISAXHandler!(T) handler, Encoding inputEncoding=Encoding.Unknown) |
|---|
| 397 |
in |
|---|
| 398 |
{ |
|---|
| 399 |
assert(source !is null); |
|---|
| 400 |
assert(handler !is null); |
|---|
| 401 |
} |
|---|
| 402 |
body |
|---|
| 403 |
{ |
|---|
| 404 |
//Initialize the parser |
|---|
| 405 |
reader.init(source, inputEncoding); |
|---|
| 406 |
parse(handler); |
|---|
| 407 |
} |
|---|
| 408 |
|
|---|
| 409 |
private static bool isWhiteSpace(T wc) |
|---|
| 410 |
{ |
|---|
| 411 |
uint n = cast(uint)wc; |
|---|
| 412 |
return (n == 32 || n == 13 || n == 10 || n == 9); |
|---|
| 413 |
} |
|---|
| 414 |
|
|---|
| 415 |
private static bool isWhiteSpace(T[] str) |
|---|
| 416 |
{ |
|---|
| 417 |
foreach(T wc; str) |
|---|
| 418 |
if (!isWhiteSpace(wc)) |
|---|
| 419 |
return false; |
|---|
| 420 |
return true; |
|---|
| 421 |
} |
|---|
| 422 |
|
|---|
| 423 |
private void go() |
|---|
| 424 |
{ |
|---|
| 425 |
first = true; |
|---|
| 426 |
CDataStatus status = CDataStatus.End; |
|---|
| 427 |
T[] whiteSpace; |
|---|
| 428 |
do |
|---|
| 429 |
{ |
|---|
| 430 |
end.advanceToNoFill('<'); |
|---|
| 431 |
|
|---|
| 432 |
if (start.isBefore(end)) |
|---|
| 433 |
{ |
|---|
| 434 |
T[] text = start.sliceTo(end); |
|---|
| 435 |
if (!status.containsEnd() || !isWhiteSpace(text)) |
|---|
| 436 |
{ |
|---|
| 437 |
if (myStack.length == 0) { |
|---|
| 438 |
static if (is(T == char)) { |
|---|
| 439 |
throw new TeqXMLException("Cannot have text (" ~ text ~ ") before first tag!"); |
|---|
| 440 |
} else { |
|---|
| 441 |
throw new TeqXMLException("Cannot have text (" ~ Utf.toString(text) ~ ") before first tag!"); |
|---|
| 442 |
} |
|---|
| 443 |
} |
|---|
| 444 |
|
|---|
| 445 |
if (whiteSpace.length > 0) { |
|---|
| 446 |
status = CDataStatus.Start; |
|---|
| 447 |
sendCData(whiteSpace, status); |
|---|
| 448 |
} |
|---|
| 449 |
|
|---|
| 450 |
int amp = Text.locatePrior!(T)(text, '&'); |
|---|
| 451 |
if (amp < text.length) { |
|---|
| 452 |
T[] lct = text[amp..$]; |
|---|
| 453 |
int semi = Text.locate!(T)(lct, ';'); |
|---|
| 454 |
if (semi >= lct.length) { |
|---|
| 455 |
if (amp == 0 && text.length > LONG_ENTITY) { //Entities can only be so long |
|---|
| 456 |
throw new TeqXMLException("Entity name longer than allowed or semicolon missing after &"); |
|---|
| 457 |
} |
|---|
| 458 |
if (status.containsEnd()) |
|---|
| 459 |
status = CDataStatus.Start; |
|---|
| 460 |
else |
|---|
| 461 |
status = CDataStatus.Append; |
|---|
| 462 |
|
|---|
| 463 |
sendCData(text[0..amp], status); |
|---|
| 464 |
end.backupTo('&'); |
|---|
| 465 |
start.moveTo(end); |
|---|
| 466 |
reader.fillBuffer(); |
|---|
| 467 |
goto doneCData; |
|---|
| 468 |
} |
|---|
| 469 |
} |
|---|
| 470 |
|
|---|
| 471 |
if (end.getAdvance() == '<') |
|---|
| 472 |
if (status.containsEnd()) |
|---|
| 473 |
status = CDataStatus.All; |
|---|
| 474 |
else |
|---|
| 475 |
status = CDataStatus.End; |
|---|
| 476 |
else |
|---|
| 477 |
if (status.containsEnd()) |
|---|
| 478 |
status = CDataStatus.Start; |
|---|
| 479 |
else |
|---|
| 480 |
status = CDataStatus.Append; |
|---|
| 481 |
|
|---|
| 482 |
text = start.sliceTo(end); |
|---|
| 483 |
sendCData(text, status); |
|---|
| 484 |
} else if (isWhiteSpace(text)) { |
|---|
| 485 |
whiteSpace ~= text; |
|---|
| 486 |
} |
|---|
| 487 |
} |
|---|
| 488 |
|
|---|
| 489 |
doneCData: |
|---|
| 490 |
start.moveTo(end); |
|---|
| 491 |
|
|---|
| 492 |
if (end.read() == '<') |
|---|
| 493 |
{ |
|---|
| 494 |
whiteSpace.length = 0;; |
|---|
| 495 |
parseOpen(); |
|---|
| 496 |
start.moveTo(end); |
|---|
| 497 |
} |
|---|
| 498 |
} while (first || myStack.length > 0); |
|---|
| 499 |
} |
|---|
| 500 |
|
|---|
| 501 |
//If the data to be sent has any entities, they must be entirely contained within one call. |
|---|
| 502 |
void sendCData(T[] data, CDataStatus status) |
|---|
| 503 |
{ |
|---|
| 504 |
int ampIndx = Text.locate!(T)(data, '&'); |
|---|
| 505 |
int last = 0; |
|---|
| 506 |
bool sentStart = false; |
|---|
| 507 |
while (ampIndx < data.length) |
|---|
| 508 |
{ |
|---|
| 509 |
int scIndx = Text.locate!(T)(data, ';', ampIndx); |
|---|
| 510 |
if (scIndx >= data.length) |
|---|
| 511 |
break; |
|---|
| 512 |
|
|---|
| 513 |
T[] str = data[last..ampIndx]; |
|---|
| 514 |
if (status.containsStart && !sentStart) |
|---|
| 515 |
{ |
|---|
| 516 |
myHandler.characterData(str, CDataStatus.Start); |
|---|
| 517 |
sentStart = true; |
|---|
| 518 |
} |
|---|
| 519 |
else |
|---|
| 520 |
myHandler.characterData(str, CDataStatus.Append); |
|---|
| 521 |
|
|---|
| 522 |
T[] ent = data[ampIndx+1..scIndx]; |
|---|
| 523 |
T[1] chr; |
|---|
| 524 |
if (!(ent in entities)) { |
|---|
| 525 |
if (ent[0] == '#' && ent.length >= 2) { |
|---|
| 526 |
ent = ent[1..$]; |
|---|
| 527 |
if (ent[0] == 'x' && ent.length >= 2) |
|---|
| 528 |
chr[0] = Integer.toInt(ent, 16); |
|---|
| 529 |
else |
|---|
| 530 |
chr[0] = Integer.toInt(ent, 10); |
|---|
| 531 |
str = chr; |
|---|
| 532 |
} else { |
|---|
| 533 |
char[] str8; |
|---|
| 534 |
static if (is(T == char)) { |
|---|
| 535 |
str8 = str; |
|---|
| 536 |
} else { |
|---|
| 537 |
str8 = tango.text.convert.Utf.toString(str); |
|---|
| 538 |
} |
|---|
| 539 |
throw new TeqXMLException( |
|---|
| 540 |
"Cannot resolve entity '" ~ str8 ~ "'"); |
|---|
| 541 |
} |
|---|
| 542 |
} else { |
|---|
| 543 |
str = entities[ent]; |
|---|
| 544 |
} |
|---|
| 545 |
|
|---|
| 546 |
if (status.containsEnd() && scIndx == (data.length-1)) |
|---|
| 547 |
myHandler.characterData(str, CDataStatus.End); |
|---|
| 548 |
else |
|---|
| 549 |
myHandler.characterData(str, CDataStatus.Append); |
|---|
| 550 |
|
|---|
| 551 |
last = scIndx + 1; |
|---|
| 552 |
ampIndx = Text.locate!(T)(data, '&', scIndx); |
|---|
| 553 |
} |
|---|
| 554 |
|
|---|
| 555 |
if (last < data.length) |
|---|
| 556 |
{ |
|---|
| 557 |
T[] str = data[last..$]; |
|---|
| 558 |
if (last > 0) |
|---|
| 559 |
if (status.containsEnd) |
|---|
| 560 |
myHandler.characterData(str, CDataStatus.End); |
|---|
| 561 |
else |
|---|
| 562 |
myHandler.characterData(str, CDataStatus.Append); |
|---|
| 563 |
else |
|---|
| 564 |
myHandler.characterData(str, status); |
|---|
| 565 |
} |
|---|
| 566 |
} |
|---|
| 567 |
|
|---|
| 568 |
void parseOpen() |
|---|
| 569 |
{ |
|---|
| 570 |
start.moveTo(end); |
|---|
| 571 |
switch (end.read()) |
|---|
| 572 |
{ |
|---|
| 573 |
case '/': |
|---|
| 574 |
parseCloseTag(); |
|---|
| 575 |
break; |
|---|
| 576 |
case '?': |
|---|
| 577 |
parseQuest(); |
|---|
| 578 |
break; |
|---|
| 579 |
case '!': |
|---|
| 580 |
parseExcl(); |
|---|
| 581 |
break; |
|---|
| 582 |
default: |
|---|
| 583 |
parseElement(); |
|---|
| 584 |
break; |
|---|
| 585 |
} |
|---|
| 586 |
} |
|---|
| 587 |
|
|---|
| 588 |
void parseElement() { |
|---|
| 589 |
static bool stop( |
|---|