parser/htmlparser/src/nsScanner.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* vim: set ts=2 sw=2 et tw=78: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 //#define __INCREMENTAL 1
michael@0 8
michael@0 9 #include "mozilla/DebugOnly.h"
michael@0 10
michael@0 11 #include "nsScanner.h"
michael@0 12 #include "nsDebug.h"
michael@0 13 #include "nsReadableUtils.h"
michael@0 14 #include "nsIInputStream.h"
michael@0 15 #include "nsIFile.h"
michael@0 16 #include "nsNetUtil.h"
michael@0 17 #include "nsUTF8Utils.h" // for LossyConvertEncoding
michael@0 18 #include "nsCRT.h"
michael@0 19 #include "nsParser.h"
michael@0 20 #include "nsCharsetSource.h"
michael@0 21
michael@0 22 #include "mozilla/dom/EncodingUtils.h"
michael@0 23
michael@0 24 using mozilla::dom::EncodingUtils;
michael@0 25
michael@0 26 // We replace NUL characters with this character.
michael@0 27 static char16_t sInvalid = UCS2_REPLACEMENT_CHAR;
michael@0 28
michael@0 29 nsReadEndCondition::nsReadEndCondition(const char16_t* aTerminateChars) :
michael@0 30 mChars(aTerminateChars), mFilter(char16_t(~0)) // All bits set
michael@0 31 {
michael@0 32 // Build filter that will be used to filter out characters with
michael@0 33 // bits that none of the terminal chars have. This works very well
michael@0 34 // because terminal chars often have only the last 4-6 bits set and
michael@0 35 // normal ascii letters have bit 7 set. Other letters have even higher
michael@0 36 // bits set.
michael@0 37
michael@0 38 // Calculate filter
michael@0 39 const char16_t *current = aTerminateChars;
michael@0 40 char16_t terminalChar = *current;
michael@0 41 while (terminalChar) {
michael@0 42 mFilter &= ~terminalChar;
michael@0 43 ++current;
michael@0 44 terminalChar = *current;
michael@0 45 }
michael@0 46 }
michael@0 47
michael@0 48 /**
michael@0 49 * Use this constructor if you want i/o to be based on
michael@0 50 * a single string you hand in during construction.
michael@0 51 * This short cut was added for Javascript.
michael@0 52 *
michael@0 53 * @update gess 5/12/98
michael@0 54 * @param aMode represents the parser mode (nav, other)
michael@0 55 * @return
michael@0 56 */
michael@0 57 nsScanner::nsScanner(const nsAString& anHTMLString)
michael@0 58 {
michael@0 59 MOZ_COUNT_CTOR(nsScanner);
michael@0 60
michael@0 61 mSlidingBuffer = nullptr;
michael@0 62 mCountRemaining = 0;
michael@0 63 mFirstNonWhitespacePosition = -1;
michael@0 64 if (AppendToBuffer(anHTMLString)) {
michael@0 65 mSlidingBuffer->BeginReading(mCurrentPosition);
michael@0 66 } else {
michael@0 67 /* XXX see hack below, re: bug 182067 */
michael@0 68 memset(&mCurrentPosition, 0, sizeof(mCurrentPosition));
michael@0 69 mEndPosition = mCurrentPosition;
michael@0 70 }
michael@0 71 mMarkPosition = mCurrentPosition;
michael@0 72 mIncremental = false;
michael@0 73 mUnicodeDecoder = 0;
michael@0 74 mCharsetSource = kCharsetUninitialized;
michael@0 75 mHasInvalidCharacter = false;
michael@0 76 mReplacementCharacter = char16_t(0x0);
michael@0 77 }
michael@0 78
michael@0 79 /**
michael@0 80 * Use this constructor if you want i/o to be based on strings
michael@0 81 * the scanner receives. If you pass a null filename, you
michael@0 82 * can still provide data to the scanner via append.
michael@0 83 */
michael@0 84 nsScanner::nsScanner(nsString& aFilename, bool aCreateStream)
michael@0 85 : mFilename(aFilename)
michael@0 86 {
michael@0 87 MOZ_COUNT_CTOR(nsScanner);
michael@0 88 NS_ASSERTION(!aCreateStream, "This is always true.");
michael@0 89
michael@0 90 mSlidingBuffer = nullptr;
michael@0 91
michael@0 92 // XXX This is a big hack. We need to initialize the iterators to something.
michael@0 93 // What matters is that mCurrentPosition == mEndPosition, so that our methods
michael@0 94 // believe that we are at EOF (see bug 182067). We null out mCurrentPosition
michael@0 95 // so that we have some hope of catching null pointer dereferences associated
michael@0 96 // with this hack. --darin
michael@0 97 memset(&mCurrentPosition, 0, sizeof(mCurrentPosition));
michael@0 98 mMarkPosition = mCurrentPosition;
michael@0 99 mEndPosition = mCurrentPosition;
michael@0 100
michael@0 101 mIncremental = true;
michael@0 102 mFirstNonWhitespacePosition = -1;
michael@0 103 mCountRemaining = 0;
michael@0 104
michael@0 105 mUnicodeDecoder = 0;
michael@0 106 mCharsetSource = kCharsetUninitialized;
michael@0 107 mHasInvalidCharacter = false;
michael@0 108 mReplacementCharacter = char16_t(0x0);
michael@0 109 // XML defaults to UTF-8 and about:blank is UTF-8, too.
michael@0 110 SetDocumentCharset(NS_LITERAL_CSTRING("UTF-8"), kCharsetFromDocTypeDefault);
michael@0 111 }
michael@0 112
michael@0 113 nsresult nsScanner::SetDocumentCharset(const nsACString& aCharset , int32_t aSource)
michael@0 114 {
michael@0 115 if (aSource < mCharsetSource) // priority is lower than the current one
michael@0 116 return NS_OK;
michael@0 117
michael@0 118 mCharsetSource = aSource;
michael@0 119
michael@0 120 nsCString charsetName;
michael@0 121 mozilla::DebugOnly<bool> valid =
michael@0 122 EncodingUtils::FindEncodingForLabel(aCharset, charsetName);
michael@0 123 MOZ_ASSERT(valid, "Should never call with a bogus aCharset.");
michael@0 124
michael@0 125 if (!mCharset.IsEmpty() && charsetName.Equals(mCharset)) {
michael@0 126 return NS_OK; // no difference, don't change it
michael@0 127 }
michael@0 128
michael@0 129 // different, need to change it
michael@0 130
michael@0 131 mCharset.Assign(charsetName);
michael@0 132
michael@0 133 mUnicodeDecoder = EncodingUtils::DecoderForEncoding(mCharset);
michael@0 134 mUnicodeDecoder->SetInputErrorBehavior(nsIUnicodeDecoder::kOnError_Signal);
michael@0 135
michael@0 136 return NS_OK;
michael@0 137 }
michael@0 138
michael@0 139
michael@0 140 /**
michael@0 141 * default destructor
michael@0 142 *
michael@0 143 * @update gess 3/25/98
michael@0 144 * @param
michael@0 145 * @return
michael@0 146 */
michael@0 147 nsScanner::~nsScanner() {
michael@0 148
michael@0 149 delete mSlidingBuffer;
michael@0 150
michael@0 151 MOZ_COUNT_DTOR(nsScanner);
michael@0 152 }
michael@0 153
michael@0 154 /**
michael@0 155 * Resets current offset position of input stream to marked position.
michael@0 156 * This allows us to back up to this point if the need should arise,
michael@0 157 * such as when tokenization gets interrupted.
michael@0 158 * NOTE: IT IS REALLY BAD FORM TO CALL RELEASE WITHOUT CALLING MARK FIRST!
michael@0 159 *
michael@0 160 * @update gess 5/12/98
michael@0 161 * @param
michael@0 162 * @return
michael@0 163 */
michael@0 164 void nsScanner::RewindToMark(void){
michael@0 165 if (mSlidingBuffer) {
michael@0 166 mCountRemaining += (Distance(mMarkPosition, mCurrentPosition));
michael@0 167 mCurrentPosition = mMarkPosition;
michael@0 168 }
michael@0 169 }
michael@0 170
michael@0 171
michael@0 172 /**
michael@0 173 * Records current offset position in input stream. This allows us
michael@0 174 * to back up to this point if the need should arise, such as when
michael@0 175 * tokenization gets interrupted.
michael@0 176 *
michael@0 177 * @update gess 7/29/98
michael@0 178 * @param
michael@0 179 * @return
michael@0 180 */
michael@0 181 int32_t nsScanner::Mark() {
michael@0 182 int32_t distance = 0;
michael@0 183 if (mSlidingBuffer) {
michael@0 184 nsScannerIterator oldStart;
michael@0 185 mSlidingBuffer->BeginReading(oldStart);
michael@0 186
michael@0 187 distance = Distance(oldStart, mCurrentPosition);
michael@0 188
michael@0 189 mSlidingBuffer->DiscardPrefix(mCurrentPosition);
michael@0 190 mSlidingBuffer->BeginReading(mCurrentPosition);
michael@0 191 mMarkPosition = mCurrentPosition;
michael@0 192 }
michael@0 193
michael@0 194 return distance;
michael@0 195 }
michael@0 196
michael@0 197 /**
michael@0 198 * Insert data to our underlying input buffer as
michael@0 199 * if it were read from an input stream.
michael@0 200 *
michael@0 201 * @update harishd 01/12/99
michael@0 202 * @return error code
michael@0 203 */
michael@0 204 bool nsScanner::UngetReadable(const nsAString& aBuffer) {
michael@0 205 if (!mSlidingBuffer) {
michael@0 206 return false;
michael@0 207 }
michael@0 208
michael@0 209 mSlidingBuffer->UngetReadable(aBuffer,mCurrentPosition);
michael@0 210 mSlidingBuffer->BeginReading(mCurrentPosition); // Insertion invalidated our iterators
michael@0 211 mSlidingBuffer->EndReading(mEndPosition);
michael@0 212
michael@0 213 uint32_t length = aBuffer.Length();
michael@0 214 mCountRemaining += length; // Ref. bug 117441
michael@0 215 return true;
michael@0 216 }
michael@0 217
michael@0 218 /**
michael@0 219 * Append data to our underlying input buffer as
michael@0 220 * if it were read from an input stream.
michael@0 221 *
michael@0 222 * @update gess4/3/98
michael@0 223 * @return error code
michael@0 224 */
michael@0 225 nsresult nsScanner::Append(const nsAString& aBuffer) {
michael@0 226 if (!AppendToBuffer(aBuffer))
michael@0 227 return NS_ERROR_OUT_OF_MEMORY;
michael@0 228 return NS_OK;
michael@0 229 }
michael@0 230
michael@0 231 /**
michael@0 232 *
michael@0 233 *
michael@0 234 * @update gess 5/21/98
michael@0 235 * @param
michael@0 236 * @return
michael@0 237 */
michael@0 238 nsresult nsScanner::Append(const char* aBuffer, uint32_t aLen,
michael@0 239 nsIRequest *aRequest)
michael@0 240 {
michael@0 241 nsresult res = NS_OK;
michael@0 242 if (mUnicodeDecoder) {
michael@0 243 int32_t unicharBufLen = 0;
michael@0 244 mUnicodeDecoder->GetMaxLength(aBuffer, aLen, &unicharBufLen);
michael@0 245 nsScannerString::Buffer* buffer = nsScannerString::AllocBuffer(unicharBufLen + 1);
michael@0 246 NS_ENSURE_TRUE(buffer,NS_ERROR_OUT_OF_MEMORY);
michael@0 247 char16_t *unichars = buffer->DataStart();
michael@0 248
michael@0 249 int32_t totalChars = 0;
michael@0 250 int32_t unicharLength = unicharBufLen;
michael@0 251 int32_t errorPos = -1;
michael@0 252
michael@0 253 do {
michael@0 254 int32_t srcLength = aLen;
michael@0 255 res = mUnicodeDecoder->Convert(aBuffer, &srcLength, unichars, &unicharLength);
michael@0 256
michael@0 257 totalChars += unicharLength;
michael@0 258 // Continuation of failure case
michael@0 259 if(NS_FAILED(res)) {
michael@0 260 // if we failed, we consume one byte, replace it with the replacement
michael@0 261 // character and try the conversion again.
michael@0 262
michael@0 263 // This is only needed because some decoders don't follow the
michael@0 264 // nsIUnicodeDecoder contract: they return a failure when *aDestLength
michael@0 265 // is 0 rather than the correct NS_OK_UDEC_MOREOUTPUT. See bug 244177
michael@0 266 if ((unichars + unicharLength) >= buffer->DataEnd()) {
michael@0 267 NS_ERROR("Unexpected end of destination buffer");
michael@0 268 break;
michael@0 269 }
michael@0 270
michael@0 271 if (mReplacementCharacter == 0x0 && errorPos == -1) {
michael@0 272 errorPos = totalChars;
michael@0 273 }
michael@0 274 unichars[unicharLength++] = mReplacementCharacter == 0x0 ?
michael@0 275 mUnicodeDecoder->GetCharacterForUnMapped() :
michael@0 276 mReplacementCharacter;
michael@0 277
michael@0 278 unichars = unichars + unicharLength;
michael@0 279 unicharLength = unicharBufLen - (++totalChars);
michael@0 280
michael@0 281 mUnicodeDecoder->Reset();
michael@0 282
michael@0 283 if(((uint32_t) (srcLength + 1)) > aLen) {
michael@0 284 srcLength = aLen;
michael@0 285 }
michael@0 286 else {
michael@0 287 ++srcLength;
michael@0 288 }
michael@0 289
michael@0 290 aBuffer += srcLength;
michael@0 291 aLen -= srcLength;
michael@0 292 }
michael@0 293 } while (NS_FAILED(res) && (aLen > 0));
michael@0 294
michael@0 295 buffer->SetDataLength(totalChars);
michael@0 296 // Don't propagate return code of unicode decoder
michael@0 297 // since it doesn't reflect on our success or failure
michael@0 298 // - Ref. bug 87110
michael@0 299 res = NS_OK;
michael@0 300 if (!AppendToBuffer(buffer, aRequest, errorPos))
michael@0 301 res = NS_ERROR_OUT_OF_MEMORY;
michael@0 302 }
michael@0 303 else {
michael@0 304 NS_WARNING("No decoder found.");
michael@0 305 res = NS_ERROR_FAILURE;
michael@0 306 }
michael@0 307
michael@0 308 return res;
michael@0 309 }
michael@0 310
michael@0 311 /**
michael@0 312 * retrieve next char from scanners internal input stream
michael@0 313 *
michael@0 314 * @update gess 3/25/98
michael@0 315 * @param
michael@0 316 * @return error code reflecting read status
michael@0 317 */
michael@0 318 nsresult nsScanner::GetChar(char16_t& aChar) {
michael@0 319 if (!mSlidingBuffer || mCurrentPosition == mEndPosition) {
michael@0 320 aChar = 0;
michael@0 321 return kEOF;
michael@0 322 }
michael@0 323
michael@0 324 aChar = *mCurrentPosition++;
michael@0 325 --mCountRemaining;
michael@0 326
michael@0 327 return NS_OK;
michael@0 328 }
michael@0 329
michael@0 330
michael@0 331 /**
michael@0 332 * peek ahead to consume next char from scanner's internal
michael@0 333 * input buffer
michael@0 334 *
michael@0 335 * @update gess 3/25/98
michael@0 336 * @param
michael@0 337 * @return
michael@0 338 */
michael@0 339 nsresult nsScanner::Peek(char16_t& aChar, uint32_t aOffset) {
michael@0 340 aChar = 0;
michael@0 341
michael@0 342 if (!mSlidingBuffer || mCurrentPosition == mEndPosition) {
michael@0 343 return kEOF;
michael@0 344 }
michael@0 345
michael@0 346 if (aOffset > 0) {
michael@0 347 if (mCountRemaining <= aOffset)
michael@0 348 return kEOF;
michael@0 349
michael@0 350 nsScannerIterator pos = mCurrentPosition;
michael@0 351 pos.advance(aOffset);
michael@0 352 aChar=*pos;
michael@0 353 }
michael@0 354 else {
michael@0 355 aChar=*mCurrentPosition;
michael@0 356 }
michael@0 357
michael@0 358 return NS_OK;
michael@0 359 }
michael@0 360
michael@0 361 nsresult nsScanner::Peek(nsAString& aStr, int32_t aNumChars, int32_t aOffset)
michael@0 362 {
michael@0 363 if (!mSlidingBuffer || mCurrentPosition == mEndPosition) {
michael@0 364 return kEOF;
michael@0 365 }
michael@0 366
michael@0 367 nsScannerIterator start, end;
michael@0 368
michael@0 369 start = mCurrentPosition;
michael@0 370
michael@0 371 if ((int32_t)mCountRemaining <= aOffset) {
michael@0 372 return kEOF;
michael@0 373 }
michael@0 374
michael@0 375 if (aOffset > 0) {
michael@0 376 start.advance(aOffset);
michael@0 377 }
michael@0 378
michael@0 379 if (mCountRemaining < uint32_t(aNumChars + aOffset)) {
michael@0 380 end = mEndPosition;
michael@0 381 }
michael@0 382 else {
michael@0 383 end = start;
michael@0 384 end.advance(aNumChars);
michael@0 385 }
michael@0 386
michael@0 387 CopyUnicodeTo(start, end, aStr);
michael@0 388
michael@0 389 return NS_OK;
michael@0 390 }
michael@0 391
michael@0 392
michael@0 393 /**
michael@0 394 * Skip whitespace on scanner input stream
michael@0 395 *
michael@0 396 * @update gess 3/25/98
michael@0 397 * @param
michael@0 398 * @return error status
michael@0 399 */
michael@0 400 nsresult nsScanner::SkipWhitespace(int32_t& aNewlinesSkipped) {
michael@0 401
michael@0 402 if (!mSlidingBuffer) {
michael@0 403 return kEOF;
michael@0 404 }
michael@0 405
michael@0 406 char16_t theChar = 0;
michael@0 407 nsresult result = Peek(theChar);
michael@0 408
michael@0 409 if (NS_FAILED(result)) {
michael@0 410 return result;
michael@0 411 }
michael@0 412
michael@0 413 nsScannerIterator current = mCurrentPosition;
michael@0 414 bool done = false;
michael@0 415 bool skipped = false;
michael@0 416
michael@0 417 while (!done && current != mEndPosition) {
michael@0 418 switch(theChar) {
michael@0 419 case '\n':
michael@0 420 case '\r': ++aNewlinesSkipped;
michael@0 421 case ' ' :
michael@0 422 case '\t':
michael@0 423 {
michael@0 424 skipped = true;
michael@0 425 char16_t thePrevChar = theChar;
michael@0 426 theChar = (++current != mEndPosition) ? *current : '\0';
michael@0 427 if ((thePrevChar == '\r' && theChar == '\n') ||
michael@0 428 (thePrevChar == '\n' && theChar == '\r')) {
michael@0 429 theChar = (++current != mEndPosition) ? *current : '\0'; // CRLF == LFCR => LF
michael@0 430 }
michael@0 431 }
michael@0 432 break;
michael@0 433 default:
michael@0 434 done = true;
michael@0 435 break;
michael@0 436 }
michael@0 437 }
michael@0 438
michael@0 439 if (skipped) {
michael@0 440 SetPosition(current);
michael@0 441 if (current == mEndPosition) {
michael@0 442 result = kEOF;
michael@0 443 }
michael@0 444 }
michael@0 445
michael@0 446 return result;
michael@0 447 }
michael@0 448
michael@0 449 /**
michael@0 450 * Skip over chars as long as they equal given char
michael@0 451 *
michael@0 452 * @update gess 3/25/98
michael@0 453 * @param
michael@0 454 * @return error code
michael@0 455 */
michael@0 456 nsresult nsScanner::SkipOver(char16_t aSkipChar){
michael@0 457
michael@0 458 if (!mSlidingBuffer) {
michael@0 459 return kEOF;
michael@0 460 }
michael@0 461
michael@0 462 char16_t ch=0;
michael@0 463 nsresult result=NS_OK;
michael@0 464
michael@0 465 while(NS_OK==result) {
michael@0 466 result=Peek(ch);
michael@0 467 if(NS_OK == result) {
michael@0 468 if(ch!=aSkipChar) {
michael@0 469 break;
michael@0 470 }
michael@0 471 GetChar(ch);
michael@0 472 }
michael@0 473 else break;
michael@0 474 } //while
michael@0 475 return result;
michael@0 476
michael@0 477 }
michael@0 478
michael@0 479 #if 0
michael@0 480 void DoErrTest(nsString& aString) {
michael@0 481 int32_t pos=aString.FindChar(0);
michael@0 482 if(kNotFound<pos) {
michael@0 483 if(aString.Length()-1!=pos) {
michael@0 484 }
michael@0 485 }
michael@0 486 }
michael@0 487
michael@0 488 void DoErrTest(nsCString& aString) {
michael@0 489 int32_t pos=aString.FindChar(0);
michael@0 490 if(kNotFound<pos) {
michael@0 491 if(aString.Length()-1!=pos) {
michael@0 492 }
michael@0 493 }
michael@0 494 }
michael@0 495 #endif
michael@0 496
michael@0 497 /**
michael@0 498 * Consume characters until you run into space, a '<', a '>', or a '/'.
michael@0 499 *
michael@0 500 * @param aString - receives new data from stream
michael@0 501 * @return error code
michael@0 502 */
michael@0 503 nsresult nsScanner::ReadTagIdentifier(nsScannerSharedSubstring& aString) {
michael@0 504
michael@0 505 if (!mSlidingBuffer) {
michael@0 506 return kEOF;
michael@0 507 }
michael@0 508
michael@0 509 char16_t theChar=0;
michael@0 510 nsresult result=Peek(theChar);
michael@0 511 nsScannerIterator current, end;
michael@0 512 bool found=false;
michael@0 513
michael@0 514 current = mCurrentPosition;
michael@0 515 end = mEndPosition;
michael@0 516
michael@0 517 // Loop until we find an illegal character. Everything is then appended
michael@0 518 // later.
michael@0 519 while(current != end && !found) {
michael@0 520 theChar=*current;
michael@0 521
michael@0 522 switch(theChar) {
michael@0 523 case '\n':
michael@0 524 case '\r':
michael@0 525 case ' ' :
michael@0 526 case '\t':
michael@0 527 case '\v':
michael@0 528 case '\f':
michael@0 529 case '<':
michael@0 530 case '>':
michael@0 531 case '/':
michael@0 532 found = true;
michael@0 533 break;
michael@0 534
michael@0 535 case '\0':
michael@0 536 ReplaceCharacter(current, sInvalid);
michael@0 537 break;
michael@0 538
michael@0 539 default:
michael@0 540 break;
michael@0 541 }
michael@0 542
michael@0 543 if (!found) {
michael@0 544 ++current;
michael@0 545 }
michael@0 546 }
michael@0 547
michael@0 548 // Don't bother appending nothing.
michael@0 549 if (current != mCurrentPosition) {
michael@0 550 AppendUnicodeTo(mCurrentPosition, current, aString);
michael@0 551 }
michael@0 552
michael@0 553 SetPosition(current);
michael@0 554 if (current == end) {
michael@0 555 result = kEOF;
michael@0 556 }
michael@0 557
michael@0 558 //DoErrTest(aString);
michael@0 559
michael@0 560 return result;
michael@0 561 }
michael@0 562
michael@0 563 /**
michael@0 564 * Consume characters until you run into a char that's not valid in an
michael@0 565 * entity name
michael@0 566 *
michael@0 567 * @param aString - receives new data from stream
michael@0 568 * @return error code
michael@0 569 */
michael@0 570 nsresult nsScanner::ReadEntityIdentifier(nsString& aString) {
michael@0 571
michael@0 572 if (!mSlidingBuffer) {
michael@0 573 return kEOF;
michael@0 574 }
michael@0 575
michael@0 576 char16_t theChar=0;
michael@0 577 nsresult result=Peek(theChar);
michael@0 578 nsScannerIterator origin, current, end;
michael@0 579 bool found=false;
michael@0 580
michael@0 581 origin = mCurrentPosition;
michael@0 582 current = mCurrentPosition;
michael@0 583 end = mEndPosition;
michael@0 584
michael@0 585 while(current != end) {
michael@0 586
michael@0 587 theChar=*current;
michael@0 588 if(theChar) {
michael@0 589 found=false;
michael@0 590 switch(theChar) {
michael@0 591 case '_':
michael@0 592 case '-':
michael@0 593 case '.':
michael@0 594 // Don't allow ':' in entity names. See bug 23791
michael@0 595 found = true;
michael@0 596 break;
michael@0 597 default:
michael@0 598 found = ('a'<=theChar && theChar<='z') ||
michael@0 599 ('A'<=theChar && theChar<='Z') ||
michael@0 600 ('0'<=theChar && theChar<='9');
michael@0 601 break;
michael@0 602 }
michael@0 603
michael@0 604 if(!found) {
michael@0 605 AppendUnicodeTo(mCurrentPosition, current, aString);
michael@0 606 break;
michael@0 607 }
michael@0 608 }
michael@0 609 ++current;
michael@0 610 }
michael@0 611
michael@0 612 SetPosition(current);
michael@0 613 if (current == end) {
michael@0 614 AppendUnicodeTo(origin, current, aString);
michael@0 615 return kEOF;
michael@0 616 }
michael@0 617
michael@0 618 //DoErrTest(aString);
michael@0 619
michael@0 620 return result;
michael@0 621 }
michael@0 622
michael@0 623 /**
michael@0 624 * Consume digits
michael@0 625 *
michael@0 626 * @param aString - should contain digits
michael@0 627 * @return error code
michael@0 628 */
michael@0 629 nsresult nsScanner::ReadNumber(nsString& aString,int32_t aBase) {
michael@0 630
michael@0 631 if (!mSlidingBuffer) {
michael@0 632 return kEOF;
michael@0 633 }
michael@0 634
michael@0 635 NS_ASSERTION(aBase == 10 || aBase == 16,"base value not supported");
michael@0 636
michael@0 637 char16_t theChar=0;
michael@0 638 nsresult result=Peek(theChar);
michael@0 639 nsScannerIterator origin, current, end;
michael@0 640
michael@0 641 origin = mCurrentPosition;
michael@0 642 current = origin;
michael@0 643 end = mEndPosition;
michael@0 644
michael@0 645 bool done = false;
michael@0 646 while(current != end) {
michael@0 647 theChar=*current;
michael@0 648 if(theChar) {
michael@0 649 done = (theChar < '0' || theChar > '9') &&
michael@0 650 ((aBase == 16)? (theChar < 'A' || theChar > 'F') &&
michael@0 651 (theChar < 'a' || theChar > 'f')
michael@0 652 :true);
michael@0 653 if(done) {
michael@0 654 AppendUnicodeTo(origin, current, aString);
michael@0 655 break;
michael@0 656 }
michael@0 657 }
michael@0 658 ++current;
michael@0 659 }
michael@0 660
michael@0 661 SetPosition(current);
michael@0 662 if (current == end) {
michael@0 663 AppendUnicodeTo(origin, current, aString);
michael@0 664 return kEOF;
michael@0 665 }
michael@0 666
michael@0 667 //DoErrTest(aString);
michael@0 668
michael@0 669 return result;
michael@0 670 }
michael@0 671
michael@0 672 /**
michael@0 673 * Consume characters until you find the terminal char
michael@0 674 *
michael@0 675 * @update gess 3/25/98
michael@0 676 * @param aString receives new data from stream
michael@0 677 * @param addTerminal tells us whether to append terminal to aString
michael@0 678 * @return error code
michael@0 679 */
michael@0 680 nsresult nsScanner::ReadWhitespace(nsScannerSharedSubstring& aString,
michael@0 681 int32_t& aNewlinesSkipped,
michael@0 682 bool& aHaveCR) {
michael@0 683
michael@0 684 aHaveCR = false;
michael@0 685
michael@0 686 if (!mSlidingBuffer) {
michael@0 687 return kEOF;
michael@0 688 }
michael@0 689
michael@0 690 char16_t theChar = 0;
michael@0 691 nsresult result = Peek(theChar);
michael@0 692
michael@0 693 if (NS_FAILED(result)) {
michael@0 694 return result;
michael@0 695 }
michael@0 696
michael@0 697 nsScannerIterator origin, current, end;
michael@0 698 bool done = false;
michael@0 699
michael@0 700 origin = mCurrentPosition;
michael@0 701 current = origin;
michael@0 702 end = mEndPosition;
michael@0 703
michael@0 704 bool haveCR = false;
michael@0 705
michael@0 706 while(!done && current != end) {
michael@0 707 switch(theChar) {
michael@0 708 case '\n':
michael@0 709 case '\r':
michael@0 710 {
michael@0 711 ++aNewlinesSkipped;
michael@0 712 char16_t thePrevChar = theChar;
michael@0 713 theChar = (++current != end) ? *current : '\0';
michael@0 714 if ((thePrevChar == '\r' && theChar == '\n') ||
michael@0 715 (thePrevChar == '\n' && theChar == '\r')) {
michael@0 716 theChar = (++current != end) ? *current : '\0'; // CRLF == LFCR => LF
michael@0 717 haveCR = true;
michael@0 718 } else if (thePrevChar == '\r') {
michael@0 719 // Lone CR becomes CRLF; callers should know to remove extra CRs
michael@0 720 AppendUnicodeTo(origin, current, aString);
michael@0 721 aString.writable().Append(char16_t('\n'));
michael@0 722 origin = current;
michael@0 723 haveCR = true;
michael@0 724 }
michael@0 725 }
michael@0 726 break;
michael@0 727 case ' ' :
michael@0 728 case '\t':
michael@0 729 theChar = (++current != end) ? *current : '\0';
michael@0 730 break;
michael@0 731 default:
michael@0 732 done = true;
michael@0 733 AppendUnicodeTo(origin, current, aString);
michael@0 734 break;
michael@0 735 }
michael@0 736 }
michael@0 737
michael@0 738 SetPosition(current);
michael@0 739 if (current == end) {
michael@0 740 AppendUnicodeTo(origin, current, aString);
michael@0 741 result = kEOF;
michael@0 742 }
michael@0 743
michael@0 744 aHaveCR = haveCR;
michael@0 745 return result;
michael@0 746 }
michael@0 747
michael@0 748 //XXXbz callers of this have to manage their lone '\r' themselves if they want
michael@0 749 //it to work. Good thing they're all in view-source and it deals.
michael@0 750 nsresult nsScanner::ReadWhitespace(nsScannerIterator& aStart,
michael@0 751 nsScannerIterator& aEnd,
michael@0 752 int32_t& aNewlinesSkipped) {
michael@0 753
michael@0 754 if (!mSlidingBuffer) {
michael@0 755 return kEOF;
michael@0 756 }
michael@0 757
michael@0 758 char16_t theChar = 0;
michael@0 759 nsresult result = Peek(theChar);
michael@0 760
michael@0 761 if (NS_FAILED(result)) {
michael@0 762 return result;
michael@0 763 }
michael@0 764
michael@0 765 nsScannerIterator origin, current, end;
michael@0 766 bool done = false;
michael@0 767
michael@0 768 origin = mCurrentPosition;
michael@0 769 current = origin;
michael@0 770 end = mEndPosition;
michael@0 771
michael@0 772 while(!done && current != end) {
michael@0 773 switch(theChar) {
michael@0 774 case '\n':
michael@0 775 case '\r': ++aNewlinesSkipped;
michael@0 776 case ' ' :
michael@0 777 case '\t':
michael@0 778 {
michael@0 779 char16_t thePrevChar = theChar;
michael@0 780 theChar = (++current != end) ? *current : '\0';
michael@0 781 if ((thePrevChar == '\r' && theChar == '\n') ||
michael@0 782 (thePrevChar == '\n' && theChar == '\r')) {
michael@0 783 theChar = (++current != end) ? *current : '\0'; // CRLF == LFCR => LF
michael@0 784 }
michael@0 785 }
michael@0 786 break;
michael@0 787 default:
michael@0 788 done = true;
michael@0 789 aStart = origin;
michael@0 790 aEnd = current;
michael@0 791 break;
michael@0 792 }
michael@0 793 }
michael@0 794
michael@0 795 SetPosition(current);
michael@0 796 if (current == end) {
michael@0 797 aStart = origin;
michael@0 798 aEnd = current;
michael@0 799 result = kEOF;
michael@0 800 }
michael@0 801
michael@0 802 return result;
michael@0 803 }
michael@0 804
michael@0 805 /**
michael@0 806 * Consume characters until you encounter one contained in given
michael@0 807 * input set.
michael@0 808 *
michael@0 809 * @update gess 3/25/98
michael@0 810 * @param aString will contain the result of this method
michael@0 811 * @param aTerminalSet is an ordered string that contains
michael@0 812 * the set of INVALID characters
michael@0 813 * @return error code
michael@0 814 */
michael@0 815 nsresult nsScanner::ReadUntil(nsAString& aString,
michael@0 816 const nsReadEndCondition& aEndCondition,
michael@0 817 bool addTerminal)
michael@0 818 {
michael@0 819 if (!mSlidingBuffer) {
michael@0 820 return kEOF;
michael@0 821 }
michael@0 822
michael@0 823 nsScannerIterator origin, current;
michael@0 824 const char16_t* setstart = aEndCondition.mChars;
michael@0 825 const char16_t* setcurrent;
michael@0 826
michael@0 827 origin = mCurrentPosition;
michael@0 828 current = origin;
michael@0 829
michael@0 830 char16_t theChar=0;
michael@0 831 nsresult result=Peek(theChar);
michael@0 832
michael@0 833 if (NS_FAILED(result)) {
michael@0 834 return result;
michael@0 835 }
michael@0 836
michael@0 837 while (current != mEndPosition) {
michael@0 838 theChar = *current;
michael@0 839 if (theChar == '\0') {
michael@0 840 ReplaceCharacter(current, sInvalid);
michael@0 841 theChar = sInvalid;
michael@0 842 }
michael@0 843
michael@0 844 // Filter out completely wrong characters
michael@0 845 // Check if all bits are in the required area
michael@0 846 if(!(theChar & aEndCondition.mFilter)) {
michael@0 847 // They were. Do a thorough check.
michael@0 848
michael@0 849 setcurrent = setstart;
michael@0 850 while (*setcurrent) {
michael@0 851 if (*setcurrent == theChar) {
michael@0 852 if(addTerminal)
michael@0 853 ++current;
michael@0 854 AppendUnicodeTo(origin, current, aString);
michael@0 855 SetPosition(current);
michael@0 856
michael@0 857 //DoErrTest(aString);
michael@0 858
michael@0 859 return NS_OK;
michael@0 860 }
michael@0 861 ++setcurrent;
michael@0 862 }
michael@0 863 }
michael@0 864
michael@0 865 ++current;
michael@0 866 }
michael@0 867
michael@0 868 // If we are here, we didn't find any terminator in the string and
michael@0 869 // current = mEndPosition
michael@0 870 SetPosition(current);
michael@0 871 AppendUnicodeTo(origin, current, aString);
michael@0 872 return kEOF;
michael@0 873 }
michael@0 874
michael@0 875 nsresult nsScanner::ReadUntil(nsScannerSharedSubstring& aString,
michael@0 876 const nsReadEndCondition& aEndCondition,
michael@0 877 bool addTerminal)
michael@0 878 {
michael@0 879 if (!mSlidingBuffer) {
michael@0 880 return kEOF;
michael@0 881 }
michael@0 882
michael@0 883 nsScannerIterator origin, current;
michael@0 884 const char16_t* setstart = aEndCondition.mChars;
michael@0 885 const char16_t* setcurrent;
michael@0 886
michael@0 887 origin = mCurrentPosition;
michael@0 888 current = origin;
michael@0 889
michael@0 890 char16_t theChar=0;
michael@0 891 nsresult result=Peek(theChar);
michael@0 892
michael@0 893 if (NS_FAILED(result)) {
michael@0 894 return result;
michael@0 895 }
michael@0 896
michael@0 897 while (current != mEndPosition) {
michael@0 898 theChar = *current;
michael@0 899 if (theChar == '\0') {
michael@0 900 ReplaceCharacter(current, sInvalid);
michael@0 901 theChar = sInvalid;
michael@0 902 }
michael@0 903
michael@0 904 // Filter out completely wrong characters
michael@0 905 // Check if all bits are in the required area
michael@0 906 if(!(theChar & aEndCondition.mFilter)) {
michael@0 907 // They were. Do a thorough check.
michael@0 908
michael@0 909 setcurrent = setstart;
michael@0 910 while (*setcurrent) {
michael@0 911 if (*setcurrent == theChar) {
michael@0 912 if(addTerminal)
michael@0 913 ++current;
michael@0 914 AppendUnicodeTo(origin, current, aString);
michael@0 915 SetPosition(current);
michael@0 916
michael@0 917 //DoErrTest(aString);
michael@0 918
michael@0 919 return NS_OK;
michael@0 920 }
michael@0 921 ++setcurrent;
michael@0 922 }
michael@0 923 }
michael@0 924
michael@0 925 ++current;
michael@0 926 }
michael@0 927
michael@0 928 // If we are here, we didn't find any terminator in the string and
michael@0 929 // current = mEndPosition
michael@0 930 SetPosition(current);
michael@0 931 AppendUnicodeTo(origin, current, aString);
michael@0 932 return kEOF;
michael@0 933 }
michael@0 934
michael@0 935 nsresult nsScanner::ReadUntil(nsScannerIterator& aStart,
michael@0 936 nsScannerIterator& aEnd,
michael@0 937 const nsReadEndCondition &aEndCondition,
michael@0 938 bool addTerminal)
michael@0 939 {
michael@0 940 if (!mSlidingBuffer) {
michael@0 941 return kEOF;
michael@0 942 }
michael@0 943
michael@0 944 nsScannerIterator origin, current;
michael@0 945 const char16_t* setstart = aEndCondition.mChars;
michael@0 946 const char16_t* setcurrent;
michael@0 947
michael@0 948 origin = mCurrentPosition;
michael@0 949 current = origin;
michael@0 950
michael@0 951 char16_t theChar=0;
michael@0 952 nsresult result=Peek(theChar);
michael@0 953
michael@0 954 if (NS_FAILED(result)) {
michael@0 955 aStart = aEnd = current;
michael@0 956 return result;
michael@0 957 }
michael@0 958
michael@0 959 while (current != mEndPosition) {
michael@0 960 theChar = *current;
michael@0 961 if (theChar == '\0') {
michael@0 962 ReplaceCharacter(current, sInvalid);
michael@0 963 theChar = sInvalid;
michael@0 964 }
michael@0 965
michael@0 966 // Filter out completely wrong characters
michael@0 967 // Check if all bits are in the required area
michael@0 968 if(!(theChar & aEndCondition.mFilter)) {
michael@0 969 // They were. Do a thorough check.
michael@0 970 setcurrent = setstart;
michael@0 971 while (*setcurrent) {
michael@0 972 if (*setcurrent == theChar) {
michael@0 973 if(addTerminal)
michael@0 974 ++current;
michael@0 975 aStart = origin;
michael@0 976 aEnd = current;
michael@0 977 SetPosition(current);
michael@0 978
michael@0 979 return NS_OK;
michael@0 980 }
michael@0 981 ++setcurrent;
michael@0 982 }
michael@0 983 }
michael@0 984
michael@0 985 ++current;
michael@0 986 }
michael@0 987
michael@0 988 // If we are here, we didn't find any terminator in the string and
michael@0 989 // current = mEndPosition
michael@0 990 SetPosition(current);
michael@0 991 aStart = origin;
michael@0 992 aEnd = current;
michael@0 993 return kEOF;
michael@0 994 }
michael@0 995
michael@0 996 /**
michael@0 997 * Consumes chars until you see the given terminalChar
michael@0 998 *
michael@0 999 * @update gess 3/25/98
michael@0 1000 * @param
michael@0 1001 * @return error code
michael@0 1002 */
michael@0 1003 nsresult nsScanner::ReadUntil(nsAString& aString,
michael@0 1004 char16_t aTerminalChar,
michael@0 1005 bool addTerminal)
michael@0 1006 {
michael@0 1007 if (!mSlidingBuffer) {
michael@0 1008 return kEOF;
michael@0 1009 }
michael@0 1010
michael@0 1011 nsScannerIterator origin, current;
michael@0 1012
michael@0 1013 origin = mCurrentPosition;
michael@0 1014 current = origin;
michael@0 1015
michael@0 1016 char16_t theChar;
michael@0 1017 nsresult result = Peek(theChar);
michael@0 1018
michael@0 1019 if (NS_FAILED(result)) {
michael@0 1020 return result;
michael@0 1021 }
michael@0 1022
michael@0 1023 while (current != mEndPosition) {
michael@0 1024 theChar = *current;
michael@0 1025 if (theChar == '\0') {
michael@0 1026 ReplaceCharacter(current, sInvalid);
michael@0 1027 theChar = sInvalid;
michael@0 1028 }
michael@0 1029
michael@0 1030 if (aTerminalChar == theChar) {
michael@0 1031 if(addTerminal)
michael@0 1032 ++current;
michael@0 1033 AppendUnicodeTo(origin, current, aString);
michael@0 1034 SetPosition(current);
michael@0 1035 return NS_OK;
michael@0 1036 }
michael@0 1037 ++current;
michael@0 1038 }
michael@0 1039
michael@0 1040 // If we are here, we didn't find any terminator in the string and
michael@0 1041 // current = mEndPosition
michael@0 1042 AppendUnicodeTo(origin, current, aString);
michael@0 1043 SetPosition(current);
michael@0 1044 return kEOF;
michael@0 1045
michael@0 1046 }
michael@0 1047
michael@0 1048 void nsScanner::BindSubstring(nsScannerSubstring& aSubstring, const nsScannerIterator& aStart, const nsScannerIterator& aEnd)
michael@0 1049 {
michael@0 1050 aSubstring.Rebind(*mSlidingBuffer, aStart, aEnd);
michael@0 1051 }
michael@0 1052
michael@0 1053 void nsScanner::CurrentPosition(nsScannerIterator& aPosition)
michael@0 1054 {
michael@0 1055 aPosition = mCurrentPosition;
michael@0 1056 }
michael@0 1057
michael@0 1058 void nsScanner::EndReading(nsScannerIterator& aPosition)
michael@0 1059 {
michael@0 1060 aPosition = mEndPosition;
michael@0 1061 }
michael@0 1062
michael@0 1063 void nsScanner::SetPosition(nsScannerIterator& aPosition, bool aTerminate, bool aReverse)
michael@0 1064 {
michael@0 1065 if (mSlidingBuffer) {
michael@0 1066 #ifdef DEBUG
michael@0 1067 uint32_t origRemaining = mCountRemaining;
michael@0 1068 #endif
michael@0 1069
michael@0 1070 if (aReverse) {
michael@0 1071 mCountRemaining += (Distance(aPosition, mCurrentPosition));
michael@0 1072 }
michael@0 1073 else {
michael@0 1074 mCountRemaining -= (Distance(mCurrentPosition, aPosition));
michael@0 1075 }
michael@0 1076
michael@0 1077 NS_ASSERTION((mCountRemaining >= origRemaining && aReverse) ||
michael@0 1078 (mCountRemaining <= origRemaining && !aReverse),
michael@0 1079 "Improper use of nsScanner::SetPosition. Make sure to set the"
michael@0 1080 " aReverse parameter correctly");
michael@0 1081
michael@0 1082 mCurrentPosition = aPosition;
michael@0 1083 if (aTerminate && (mCurrentPosition == mEndPosition)) {
michael@0 1084 mMarkPosition = mCurrentPosition;
michael@0 1085 mSlidingBuffer->DiscardPrefix(mCurrentPosition);
michael@0 1086 }
michael@0 1087 }
michael@0 1088 }
michael@0 1089
michael@0 1090 void nsScanner::ReplaceCharacter(nsScannerIterator& aPosition,
michael@0 1091 char16_t aChar)
michael@0 1092 {
michael@0 1093 if (mSlidingBuffer) {
michael@0 1094 mSlidingBuffer->ReplaceCharacter(aPosition, aChar);
michael@0 1095 }
michael@0 1096 }
michael@0 1097
michael@0 1098 bool nsScanner::AppendToBuffer(nsScannerString::Buffer* aBuf,
michael@0 1099 nsIRequest *aRequest,
michael@0 1100 int32_t aErrorPos)
michael@0 1101 {
michael@0 1102 uint32_t countRemaining = mCountRemaining;
michael@0 1103 if (!mSlidingBuffer) {
michael@0 1104 mSlidingBuffer = new nsScannerString(aBuf);
michael@0 1105 if (!mSlidingBuffer)
michael@0 1106 return false;
michael@0 1107 mSlidingBuffer->BeginReading(mCurrentPosition);
michael@0 1108 mMarkPosition = mCurrentPosition;
michael@0 1109 mSlidingBuffer->EndReading(mEndPosition);
michael@0 1110 mCountRemaining = aBuf->DataLength();
michael@0 1111 }
michael@0 1112 else {
michael@0 1113 mSlidingBuffer->AppendBuffer(aBuf);
michael@0 1114 if (mCurrentPosition == mEndPosition) {
michael@0 1115 mSlidingBuffer->BeginReading(mCurrentPosition);
michael@0 1116 }
michael@0 1117 mSlidingBuffer->EndReading(mEndPosition);
michael@0 1118 mCountRemaining += aBuf->DataLength();
michael@0 1119 }
michael@0 1120
michael@0 1121 if (aErrorPos != -1 && !mHasInvalidCharacter) {
michael@0 1122 mHasInvalidCharacter = true;
michael@0 1123 mFirstInvalidPosition = mCurrentPosition;
michael@0 1124 mFirstInvalidPosition.advance(countRemaining + aErrorPos);
michael@0 1125 }
michael@0 1126
michael@0 1127 if (mFirstNonWhitespacePosition == -1) {
michael@0 1128 nsScannerIterator iter(mCurrentPosition);
michael@0 1129 nsScannerIterator end(mEndPosition);
michael@0 1130
michael@0 1131 while (iter != end) {
michael@0 1132 if (!nsCRT::IsAsciiSpace(*iter)) {
michael@0 1133 mFirstNonWhitespacePosition = Distance(mCurrentPosition, iter);
michael@0 1134
michael@0 1135 break;
michael@0 1136 }
michael@0 1137
michael@0 1138 ++iter;
michael@0 1139 }
michael@0 1140 }
michael@0 1141 return true;
michael@0 1142 }
michael@0 1143
michael@0 1144 /**
michael@0 1145 * call this to copy bytes out of the scanner that have not yet been consumed
michael@0 1146 * by the tokenization process.
michael@0 1147 *
michael@0 1148 * @update gess 5/12/98
michael@0 1149 * @param aCopyBuffer is where the scanner buffer will be copied to
michael@0 1150 * @return nada
michael@0 1151 */
michael@0 1152 void nsScanner::CopyUnusedData(nsString& aCopyBuffer) {
michael@0 1153 if (!mSlidingBuffer) {
michael@0 1154 aCopyBuffer.Truncate();
michael@0 1155 return;
michael@0 1156 }
michael@0 1157
michael@0 1158 nsScannerIterator start, end;
michael@0 1159 start = mCurrentPosition;
michael@0 1160 end = mEndPosition;
michael@0 1161
michael@0 1162 CopyUnicodeTo(start, end, aCopyBuffer);
michael@0 1163 }
michael@0 1164
michael@0 1165 /**
michael@0 1166 * Retrieve the name of the file that the scanner is reading from.
michael@0 1167 * In some cases, it's just a given name, because the scanner isn't
michael@0 1168 * really reading from a file.
michael@0 1169 *
michael@0 1170 * @update gess 5/12/98
michael@0 1171 * @return
michael@0 1172 */
michael@0 1173 nsString& nsScanner::GetFilename(void) {
michael@0 1174 return mFilename;
michael@0 1175 }
michael@0 1176
michael@0 1177 /**
michael@0 1178 * Conduct self test. Actually, selftesting for this class
michael@0 1179 * occurs in the parser selftest.
michael@0 1180 *
michael@0 1181 * @update gess 3/25/98
michael@0 1182 * @param
michael@0 1183 * @return
michael@0 1184 */
michael@0 1185
michael@0 1186 void nsScanner::SelfTest(void) {
michael@0 1187 #ifdef _DEBUG
michael@0 1188 #endif
michael@0 1189 }
michael@0 1190
michael@0 1191 void nsScanner::OverrideReplacementCharacter(char16_t aReplacementCharacter)
michael@0 1192 {
michael@0 1193 mReplacementCharacter = aReplacementCharacter;
michael@0 1194
michael@0 1195 if (mHasInvalidCharacter) {
michael@0 1196 ReplaceCharacter(mFirstInvalidPosition, mReplacementCharacter);
michael@0 1197 }
michael@0 1198 }
michael@0 1199

mercurial