1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #define LOG_TAG "ExpatParser" 18 19 #include "JNIHelp.h" 20 #include "JniConstants.h" 21 #include "JniException.h" 22 #include "LocalArray.h" 23 #include "ScopedLocalRef.h" 24 #include "ScopedPrimitiveArray.h" 25 #include "ScopedStringChars.h" 26 #include "ScopedUtfChars.h" 27 #include "UniquePtr.h" 28 #include "jni.h" 29 #include "cutils/log.h" 30 #include "unicode/unistr.h" 31 32 #include <string.h> 33 #include <libexpat/expat.h> 34 35 #define BUCKET_COUNT 128 36 37 /** 38 * Wrapper around an interned string. 39 */ 40 struct InternedString { 41 InternedString() : interned(NULL), bytes(NULL) { 42 } 43 44 ~InternedString() { 45 delete[] bytes; 46 } 47 48 /** The interned string itself. */ 49 jstring interned; 50 51 /** UTF-8 equivalent of the interned string. */ 52 const char* bytes; 53 54 /** Hash code of the interned string. */ 55 int hash; 56 }; 57 58 /** 59 * Keeps track of strings between start and end events. 60 */ 61 class StringStack { 62 public: 63 StringStack() : array(new jstring[DEFAULT_CAPACITY]), capacity(DEFAULT_CAPACITY), size(0) { 64 } 65 66 ~StringStack() { 67 delete[] array; 68 } 69 70 void push(JNIEnv* env, jstring s) { 71 if (size == capacity) { 72 int newCapacity = capacity * 2; 73 jstring* newArray = new jstring[newCapacity]; 74 if (newArray == NULL) { 75 jniThrowOutOfMemoryError(env, NULL); 76 return; 77 } 78 memcpy(newArray, array, capacity * sizeof(jstring)); 79 80 array = newArray; 81 capacity = newCapacity; 82 } 83 84 array[size++] = s; 85 } 86 87 jstring pop() { 88 return (size == 0) ? NULL : array[--size]; 89 } 90 91 private: 92 enum { DEFAULT_CAPACITY = 10 }; 93 94 jstring* array; 95 int capacity; 96 int size; 97 }; 98 99 /** 100 * Data passed to parser handler method by the parser. 101 */ 102 struct ParsingContext { 103 ParsingContext(jobject object) : env(NULL), object(object), buffer(NULL), bufferSize(-1) { 104 for (int i = 0; i < BUCKET_COUNT; i++) { 105 internedStrings[i] = NULL; 106 } 107 } 108 109 // Warning: 'env' must be valid on entry. 110 ~ParsingContext() { 111 freeBuffer(); 112 113 // Free interned string cache. 114 for (int i = 0; i < BUCKET_COUNT; i++) { 115 if (internedStrings[i]) { 116 InternedString** bucket = internedStrings[i]; 117 InternedString* current; 118 while ((current = *(bucket++)) != NULL) { 119 // Free the interned string reference. 120 env->DeleteGlobalRef(current->interned); 121 122 // Free the bucket. 123 delete current; 124 } 125 126 // Free the buckets. 127 delete[] internedStrings[i]; 128 } 129 } 130 } 131 132 jcharArray ensureCapacity(int length) { 133 if (bufferSize < length) { 134 // Free the existing char[]. 135 freeBuffer(); 136 137 // Allocate a new char[]. 138 jcharArray javaBuffer = env->NewCharArray(length); 139 if (javaBuffer == NULL) return NULL; 140 141 // Create a global reference. 142 javaBuffer = (jcharArray) env->NewGlobalRef(javaBuffer); 143 if (javaBuffer == NULL) return NULL; 144 145 buffer = javaBuffer; 146 bufferSize = length; 147 } 148 return buffer; 149 } 150 151 private: 152 void freeBuffer() { 153 if (buffer != NULL) { 154 env->DeleteGlobalRef(buffer); 155 buffer = NULL; 156 bufferSize = -1; 157 } 158 } 159 160 public: 161 /** 162 * The JNI environment for the current thread. This should only be used 163 * to keep a reference to the env for use in Expat callbacks. 164 */ 165 JNIEnv* env; 166 167 /** The Java parser object. */ 168 jobject object; 169 170 /** Buffer for text events. */ 171 jcharArray buffer; 172 173 private: 174 /** The size of our buffer in jchars. */ 175 int bufferSize; 176 177 public: 178 /** Current attributes. */ 179 const char** attributes; 180 181 /** Number of attributes. */ 182 int attributeCount; 183 184 /** True if namespace support is enabled. */ 185 bool processNamespaces; 186 187 /** Keep track of names. */ 188 StringStack stringStack; 189 190 /** Cache of interned strings. */ 191 InternedString** internedStrings[BUCKET_COUNT]; 192 }; 193 194 static ParsingContext* toParsingContext(void* data) { 195 return reinterpret_cast<ParsingContext*>(data); 196 } 197 198 static ParsingContext* toParsingContext(XML_Parser parser) { 199 return reinterpret_cast<ParsingContext*>(XML_GetUserData(parser)); 200 } 201 202 static jmethodID commentMethod; 203 static jmethodID endCdataMethod; 204 static jmethodID endDtdMethod; 205 static jmethodID endElementMethod; 206 static jmethodID endNamespaceMethod; 207 static jmethodID handleExternalEntityMethod; 208 static jmethodID internMethod; 209 static jmethodID notationDeclMethod; 210 static jmethodID processingInstructionMethod; 211 static jmethodID startCdataMethod; 212 static jmethodID startDtdMethod; 213 static jmethodID startElementMethod; 214 static jmethodID startNamespaceMethod; 215 static jmethodID textMethod; 216 static jmethodID unparsedEntityDeclMethod; 217 static jstring emptyString; 218 219 /** 220 * Calculates a hash code for a null-terminated string. This is *not* equivalent 221 * to Java's String.hashCode(). This hashes the bytes while String.hashCode() 222 * hashes UTF-16 chars. 223 * 224 * @param s null-terminated string to hash 225 * @returns hash code 226 */ 227 static int hashString(const char* s) { 228 int hash = 0; 229 if (s) { 230 while (*s) { 231 hash = hash * 31 + *s++; 232 } 233 } 234 return hash; 235 } 236 237 /** 238 * Creates a new interned string wrapper. Looks up the interned string 239 * representing the given UTF-8 bytes. 240 * 241 * @param bytes null-terminated string to intern 242 * @param hash of bytes 243 * @returns wrapper of interned Java string 244 */ 245 static InternedString* newInternedString(JNIEnv* env, const char* bytes, int hash) { 246 // Allocate a new wrapper. 247 UniquePtr<InternedString> wrapper(new InternedString); 248 if (wrapper.get() == NULL) { 249 jniThrowOutOfMemoryError(env, NULL); 250 return NULL; 251 } 252 253 // Create a copy of the UTF-8 bytes. 254 // TODO: sometimes we already know the length. Reuse it if so. 255 char* copy = new char[strlen(bytes) + 1]; 256 if (copy == NULL) { 257 jniThrowOutOfMemoryError(env, NULL); 258 return NULL; 259 } 260 strcpy(copy, bytes); 261 wrapper->bytes = copy; 262 263 // Save the hash. 264 wrapper->hash = hash; 265 266 // To intern a string, we must first create a new string and then call 267 // intern() on it. We then keep a global reference to the interned string. 268 ScopedLocalRef<jstring> newString(env, env->NewStringUTF(bytes)); 269 if (env->ExceptionCheck()) { 270 return NULL; 271 } 272 273 // Call intern(). 274 ScopedLocalRef<jstring> interned(env, 275 reinterpret_cast<jstring>(env->CallObjectMethod(newString.get(), internMethod))); 276 if (env->ExceptionCheck()) { 277 return NULL; 278 } 279 280 // Create a global reference to the interned string. 281 wrapper->interned = (jstring) env->NewGlobalRef(interned.get()); 282 if (env->ExceptionCheck()) { 283 return NULL; 284 } 285 286 return wrapper.release(); 287 } 288 289 /** 290 * Allocates a new bucket with one entry. 291 * 292 * @param entry to store in the bucket 293 * @returns a reference to the bucket 294 */ 295 static InternedString** newInternedStringBucket(InternedString* entry) { 296 InternedString** bucket = new InternedString*[2]; 297 if (bucket != NULL) { 298 bucket[0] = entry; 299 bucket[1] = NULL; 300 } 301 return bucket; 302 } 303 304 /** 305 * Expands an interned string bucket and adds the given entry. Frees the 306 * provided bucket and returns a new one. 307 * 308 * @param existingBucket the bucket to replace 309 * @param entry to add to the bucket 310 * @returns a reference to the newly-allocated bucket containing the given entry 311 */ 312 static InternedString** expandInternedStringBucket( 313 InternedString** existingBucket, InternedString* entry) { 314 // Determine the size of the existing bucket. 315 int size = 0; 316 while (existingBucket[size]) size++; 317 318 // Allocate the new bucket with enough space for one more entry and 319 // a null terminator. 320 InternedString** newBucket = new InternedString*[size + 2]; 321 if (newBucket == NULL) return NULL; 322 323 memcpy(newBucket, existingBucket, size * sizeof(InternedString*)); 324 newBucket[size] = entry; 325 newBucket[size + 1] = NULL; 326 327 return newBucket; 328 } 329 330 /** 331 * Returns an interned string for the given UTF-8 string. 332 * 333 * @param bucket to search for s 334 * @param s null-terminated string to find 335 * @param hash of s 336 * @returns interned Java string equivalent of s or null if not found 337 */ 338 static jstring findInternedString(InternedString** bucket, const char* s, int hash) { 339 InternedString* current; 340 while ((current = *(bucket++)) != NULL) { 341 if (current->hash != hash) continue; 342 if (!strcmp(s, current->bytes)) return current->interned; 343 } 344 return NULL; 345 } 346 347 /** 348 * Returns an interned string for the given UTF-8 string. 349 * 350 * @param s null-terminated string to intern 351 * @returns interned Java string equivelent of s or NULL if s is null 352 */ 353 static jstring internString(JNIEnv* env, ParsingContext* parsingContext, const char* s) { 354 if (s == NULL) return NULL; 355 356 int hash = hashString(s); 357 int bucketIndex = hash & (BUCKET_COUNT - 1); 358 359 InternedString*** buckets = parsingContext->internedStrings; 360 InternedString** bucket = buckets[bucketIndex]; 361 InternedString* internedString; 362 363 if (bucket) { 364 // We have a bucket already. Look for the given string. 365 jstring found = findInternedString(bucket, s, hash); 366 if (found) { 367 // We found it! 368 return found; 369 } 370 371 // We didn't find it. :( 372 // Create a new entry. 373 internedString = newInternedString(env, s, hash); 374 if (internedString == NULL) return NULL; 375 376 // Expand the bucket. 377 bucket = expandInternedStringBucket(bucket, internedString); 378 if (bucket == NULL) { 379 jniThrowOutOfMemoryError(env, NULL); 380 return NULL; 381 } 382 383 buckets[bucketIndex] = bucket; 384 385 return internedString->interned; 386 } else { 387 // We don't even have a bucket yet. Create an entry. 388 internedString = newInternedString(env, s, hash); 389 if (internedString == NULL) return NULL; 390 391 // Create a new bucket with one entry. 392 bucket = newInternedStringBucket(internedString); 393 if (bucket == NULL) { 394 jniThrowOutOfMemoryError(env, NULL); 395 return NULL; 396 } 397 398 buckets[bucketIndex] = bucket; 399 400 return internedString->interned; 401 } 402 } 403 404 static void jniThrowExpatException(JNIEnv* env, XML_Error error) { 405 const char* message = XML_ErrorString(error); 406 jniThrowException(env, "org/apache/harmony/xml/ExpatException", message); 407 } 408 409 /** 410 * Copies UTF-8 characters into the buffer. Returns the number of Java chars 411 * which were buffered. 412 * 413 * @returns number of UTF-16 characters which were copied 414 */ 415 static size_t fillBuffer(ParsingContext* parsingContext, const char* utf8, int byteCount) { 416 JNIEnv* env = parsingContext->env; 417 418 // Grow buffer if necessary (the length in bytes is always >= the length in chars). 419 jcharArray javaChars = parsingContext->ensureCapacity(byteCount); 420 if (javaChars == NULL) return -1; 421 422 // Decode UTF-8 characters into our char[]. 423 ScopedCharArrayRW chars(env, javaChars); 424 if (chars.get() == NULL) { 425 return -1; 426 } 427 UErrorCode status = U_ZERO_ERROR; 428 UnicodeString utf16(UnicodeString::fromUTF8(StringPiece(utf8, byteCount))); 429 return utf16.extract(chars.get(), byteCount, status); 430 } 431 432 /** 433 * Buffers the given text and passes it to the given method. 434 * 435 * @param method to pass the characters and length to with signature 436 * (char[], int) 437 * @param data parsing context 438 * @param text to copy into the buffer 439 * @param length of text to copy (in bytes) 440 */ 441 static void bufferAndInvoke(jmethodID method, void* data, const char* text, size_t length) { 442 ParsingContext* parsingContext = toParsingContext(data); 443 JNIEnv* env = parsingContext->env; 444 445 // Bail out if a previously called handler threw an exception. 446 if (env->ExceptionCheck()) return; 447 448 // Buffer the element name. 449 size_t utf16length = fillBuffer(parsingContext, text, length); 450 451 // Invoke given method. 452 jobject javaParser = parsingContext->object; 453 jcharArray buffer = parsingContext->buffer; 454 env->CallVoidMethod(javaParser, method, buffer, utf16length); 455 } 456 457 static const char** toAttributes(jint attributePointer) { 458 return reinterpret_cast<const char**>(static_cast<uintptr_t>(attributePointer)); 459 } 460 461 /** 462 * The component parts of an attribute or element name. 463 */ 464 class ExpatElementName { 465 public: 466 ExpatElementName(JNIEnv* env, ParsingContext* parsingContext, jint attributePointer, jint index) { 467 const char** attributes = toAttributes(attributePointer); 468 const char* name = attributes[index * 2]; 469 init(env, parsingContext, name); 470 } 471 472 ExpatElementName(JNIEnv* env, ParsingContext* parsingContext, const char* s) { 473 init(env, parsingContext, s); 474 } 475 476 ~ExpatElementName() { 477 free(mCopy); 478 } 479 480 /** 481 * Returns the namespace URI, like "http://www.w3.org/1999/xhtml". 482 * Possibly empty. 483 */ 484 jstring uri() { 485 return internString(mEnv, mParsingContext, mUri); 486 } 487 488 /** 489 * Returns the element or attribute local name, like "h1". Never empty. When 490 * namespace processing is disabled, this may contain a prefix, yielding a 491 * local name like "html:h1". In such cases, the qName will always be empty. 492 */ 493 jstring localName() { 494 return internString(mEnv, mParsingContext, mLocalName); 495 } 496 497 /** 498 * Returns the namespace prefix, like "html". Possibly empty. 499 */ 500 jstring qName() { 501 if (*mPrefix == 0) { 502 return localName(); 503 } 504 505 // return prefix + ":" + localName 506 LocalArray<1024> qName(strlen(mPrefix) + 1 + strlen(mLocalName) + 1); 507 snprintf(&qName[0], qName.size(), "%s:%s", mPrefix, mLocalName); 508 return internString(mEnv, mParsingContext, &qName[0]); 509 } 510 511 /** 512 * Returns true if this expat name has the same URI and local name. 513 */ 514 bool matches(const char* uri, const char* localName) { 515 return strcmp(uri, mUri) == 0 && strcmp(localName, mLocalName) == 0; 516 } 517 518 /** 519 * Returns true if this expat name has the same qualified name. 520 */ 521 bool matchesQName(const char* qName) { 522 const char* lastColon = strrchr(qName, ':'); 523 524 // Compare local names only if either: 525 // - the input qualified name doesn't have a colon (like "h1") 526 // - this element doesn't have a prefix. Such is the case when it 527 // doesn't belong to a namespace, or when this parser's namespace 528 // processing is disabled. In the latter case, this element's local 529 // name may still contain a colon (like "html:h1"). 530 if (lastColon == NULL || *mPrefix == 0) { 531 return strcmp(qName, mLocalName) == 0; 532 } 533 534 // Otherwise compare both prefix and local name 535 size_t prefixLength = lastColon - qName; 536 return strlen(mPrefix) == prefixLength 537 && strncmp(qName, mPrefix, prefixLength) == 0 538 && strcmp(lastColon + 1, mLocalName) == 0; 539 } 540 541 private: 542 JNIEnv* mEnv; 543 ParsingContext* mParsingContext; 544 char* mCopy; 545 const char* mUri; 546 const char* mLocalName; 547 const char* mPrefix; 548 549 /** 550 * Decodes an Expat-encoded name of one of these three forms: 551 * "uri|localName|prefix" (example: "http://www.w3.org/1999/xhtml|h1|html") 552 * "uri|localName" (example: "http://www.w3.org/1999/xhtml|h1") 553 * "localName" (example: "h1") 554 */ 555 void init(JNIEnv* env, ParsingContext* parsingContext, const char* s) { 556 mEnv = env; 557 mParsingContext = parsingContext; 558 mCopy = strdup(s); 559 560 // split the input into up to 3 parts: a|b|c 561 char* context = NULL; 562 char* a = strtok_r(mCopy, "|", &context); 563 char* b = strtok_r(NULL, "|", &context); 564 char* c = strtok_r(NULL, "|", &context); 565 566 if (c != NULL) { // input of the form "uri|localName|prefix" 567 mUri = a; 568 mLocalName = b; 569 mPrefix = c; 570 } else if (b != NULL) { // input of the form "uri|localName" 571 mUri = a; 572 mLocalName = b; 573 mPrefix = ""; 574 } else { // input of the form "localName" 575 mLocalName = a; 576 mUri = ""; 577 mPrefix = ""; 578 } 579 } 580 581 // Disallow copy and assignment. 582 ExpatElementName(const ExpatElementName&); 583 void operator=(const ExpatElementName&); 584 }; 585 586 /** 587 * Called by Expat at the start of an element. Delegates to the same method 588 * on the Java parser. 589 * 590 * @param data parsing context 591 * @param elementName "uri|localName" or "localName" for the current element 592 * @param attributes alternating attribute names and values. Like element 593 * names, attribute names follow the format "uri|localName" or "localName". 594 */ 595 static void startElement(void* data, const char* elementName, const char** attributes) { 596 ParsingContext* parsingContext = toParsingContext(data); 597 JNIEnv* env = parsingContext->env; 598 599 // Bail out if a previously called handler threw an exception. 600 if (env->ExceptionCheck()) return; 601 602 // Count the number of attributes. 603 int count = 0; 604 while (attributes[count * 2]) count++; 605 606 // Make the attributes available for the duration of this call. 607 parsingContext->attributes = attributes; 608 parsingContext->attributeCount = count; 609 610 jobject javaParser = parsingContext->object; 611 612 ExpatElementName e(env, parsingContext, elementName); 613 jstring uri = parsingContext->processNamespaces ? e.uri() : emptyString; 614 jstring localName = parsingContext->processNamespaces ? e.localName() : emptyString; 615 jstring qName = e.qName(); 616 617 parsingContext->stringStack.push(env, qName); 618 parsingContext->stringStack.push(env, uri); 619 parsingContext->stringStack.push(env, localName); 620 621 env->CallVoidMethod(javaParser, startElementMethod, uri, localName, qName, attributes, count); 622 623 parsingContext->attributes = NULL; 624 parsingContext->attributeCount = -1; 625 } 626 627 /** 628 * Called by Expat at the end of an element. Delegates to the same method 629 * on the Java parser. 630 * 631 * @param data parsing context 632 * @param elementName "uri|localName" or "localName" for the current element; 633 * we assume that this matches the last data on our stack. 634 */ 635 static void endElement(void* data, const char* /*elementName*/) { 636 ParsingContext* parsingContext = toParsingContext(data); 637 JNIEnv* env = parsingContext->env; 638 639 // Bail out if a previously called handler threw an exception. 640 if (env->ExceptionCheck()) return; 641 642 jobject javaParser = parsingContext->object; 643 644 jstring localName = parsingContext->stringStack.pop(); 645 jstring uri = parsingContext->stringStack.pop(); 646 jstring qName = parsingContext->stringStack.pop(); 647 648 env->CallVoidMethod(javaParser, endElementMethod, uri, localName, qName); 649 } 650 651 /** 652 * Called by Expat when it encounters text. Delegates to the same method 653 * on the Java parser. This may be called mutiple times with incremental pieces 654 * of the same contiguous block of text. 655 * 656 * @param data parsing context 657 * @param characters buffer containing encountered text 658 * @param length number of characters in the buffer 659 */ 660 static void text(void* data, const char* characters, int length) { 661 bufferAndInvoke(textMethod, data, characters, length); 662 } 663 664 /** 665 * Called by Expat when it encounters a comment. Delegates to the same method 666 * on the Java parser. 667 668 * @param data parsing context 669 * @param comment 0-terminated 670 */ 671 static void comment(void* data, const char* comment) { 672 bufferAndInvoke(commentMethod, data, comment, strlen(comment)); 673 } 674 675 /** 676 * Called by Expat at the beginning of a namespace mapping. 677 * 678 * @param data parsing context 679 * @param prefix null-terminated namespace prefix used in the XML 680 * @param uri of the namespace 681 */ 682 static void startNamespace(void* data, const char* prefix, const char* uri) { 683 ParsingContext* parsingContext = toParsingContext(data); 684 JNIEnv* env = parsingContext->env; 685 686 // Bail out if a previously called handler threw an exception. 687 if (env->ExceptionCheck()) return; 688 689 jstring internedPrefix = emptyString; 690 if (prefix != NULL) { 691 internedPrefix = internString(env, parsingContext, prefix); 692 if (env->ExceptionCheck()) return; 693 } 694 695 jstring internedUri = emptyString; 696 if (uri != NULL) { 697 internedUri = internString(env, parsingContext, uri); 698 if (env->ExceptionCheck()) return; 699 } 700 701 parsingContext->stringStack.push(env, internedPrefix); 702 703 jobject javaParser = parsingContext->object; 704 env->CallVoidMethod(javaParser, startNamespaceMethod, internedPrefix, internedUri); 705 } 706 707 /** 708 * Called by Expat at the end of a namespace mapping. 709 * 710 * @param data parsing context 711 * @param prefix null-terminated namespace prefix used in the XML; 712 * we assume this is the same as the last prefix on the stack. 713 */ 714 static void endNamespace(void* data, const char* /*prefix*/) { 715 ParsingContext* parsingContext = toParsingContext(data); 716 JNIEnv* env = parsingContext->env; 717 718 // Bail out if a previously called handler threw an exception. 719 if (env->ExceptionCheck()) return; 720 721 jstring internedPrefix = parsingContext->stringStack.pop(); 722 723 jobject javaParser = parsingContext->object; 724 env->CallVoidMethod(javaParser, endNamespaceMethod, internedPrefix); 725 } 726 727 /** 728 * Called by Expat at the beginning of a CDATA section. 729 * 730 * @param data parsing context 731 */ 732 static void startCdata(void* data) { 733 ParsingContext* parsingContext = toParsingContext(data); 734 JNIEnv* env = parsingContext->env; 735 736 // Bail out if a previously called handler threw an exception. 737 if (env->ExceptionCheck()) return; 738 739 jobject javaParser = parsingContext->object; 740 env->CallVoidMethod(javaParser, startCdataMethod); 741 } 742 743 /** 744 * Called by Expat at the end of a CDATA section. 745 * 746 * @param data parsing context 747 */ 748 static void endCdata(void* data) { 749 ParsingContext* parsingContext = toParsingContext(data); 750 JNIEnv* env = parsingContext->env; 751 752 // Bail out if a previously called handler threw an exception. 753 if (env->ExceptionCheck()) return; 754 755 jobject javaParser = parsingContext->object; 756 env->CallVoidMethod(javaParser, endCdataMethod); 757 } 758 759 /** 760 * Called by Expat at the beginning of a DOCTYPE section. 761 * Expat gives us 'hasInternalSubset', but the Java API doesn't expect it, so we don't need it. 762 */ 763 static void startDtd(void* data, const char* name, 764 const char* systemId, const char* publicId, int /*hasInternalSubset*/) { 765 ParsingContext* parsingContext = toParsingContext(data); 766 JNIEnv* env = parsingContext->env; 767 768 // Bail out if a previously called handler threw an exception. 769 if (env->ExceptionCheck()) return; 770 771 jstring javaName = internString(env, parsingContext, name); 772 if (env->ExceptionCheck()) return; 773 774 jstring javaPublicId = internString(env, parsingContext, publicId); 775 if (env->ExceptionCheck()) return; 776 777 jstring javaSystemId = internString(env, parsingContext, systemId); 778 if (env->ExceptionCheck()) return; 779 780 jobject javaParser = parsingContext->object; 781 env->CallVoidMethod(javaParser, startDtdMethod, javaName, javaPublicId, 782 javaSystemId); 783 } 784 785 /** 786 * Called by Expat at the end of a DOCTYPE section. 787 * 788 * @param data parsing context 789 */ 790 static void endDtd(void* data) { 791 ParsingContext* parsingContext = toParsingContext(data); 792 JNIEnv* env = parsingContext->env; 793 794 // Bail out if a previously called handler threw an exception. 795 if (env->ExceptionCheck()) return; 796 797 jobject javaParser = parsingContext->object; 798 env->CallVoidMethod(javaParser, endDtdMethod); 799 } 800 801 /** 802 * Called by Expat when it encounters processing instructions. 803 * 804 * @param data parsing context 805 * @param target of the instruction 806 * @param instructionData 807 */ 808 static void processingInstruction(void* data, const char* target, const char* instructionData) { 809 ParsingContext* parsingContext = toParsingContext(data); 810 JNIEnv* env = parsingContext->env; 811 812 // Bail out if a previously called handler threw an exception. 813 if (env->ExceptionCheck()) return; 814 815 jstring javaTarget = internString(env, parsingContext, target); 816 if (env->ExceptionCheck()) return; 817 818 ScopedLocalRef<jstring> javaInstructionData(env, env->NewStringUTF(instructionData)); 819 if (env->ExceptionCheck()) return; 820 821 jobject javaParser = parsingContext->object; 822 env->CallVoidMethod(javaParser, processingInstructionMethod, javaTarget, javaInstructionData.get()); 823 } 824 825 /** 826 * Creates a new entity parser. 827 * 828 * @param object the Java ExpatParser instance 829 * @param parentParser pointer 830 * @param javaEncoding the character encoding name 831 * @param javaContext that was provided to handleExternalEntity 832 * @returns the pointer to the C Expat entity parser 833 */ 834 static jint ExpatParser_createEntityParser(JNIEnv* env, jobject, jint parentParser, jstring javaContext) { 835 ScopedUtfChars context(env, javaContext); 836 if (context.c_str() == NULL) { 837 return 0; 838 } 839 840 XML_Parser parent = (XML_Parser) parentParser; 841 XML_Parser entityParser = XML_ExternalEntityParserCreate(parent, context.c_str(), NULL); 842 if (entityParser == NULL) { 843 jniThrowOutOfMemoryError(env, NULL); 844 } 845 846 return (jint) entityParser; 847 } 848 849 /** 850 * Handles external entities. We ignore the "base" URI and keep track of it 851 * ourselves. 852 */ 853 static int handleExternalEntity(XML_Parser parser, const char* context, 854 const char*, const char* systemId, const char* publicId) { 855 ParsingContext* parsingContext = toParsingContext(parser); 856 jobject javaParser = parsingContext->object; 857 JNIEnv* env = parsingContext->env; 858 jobject object = parsingContext->object; 859 860 // Bail out if a previously called handler threw an exception. 861 if (env->ExceptionCheck()) { 862 return XML_STATUS_ERROR; 863 } 864 865 ScopedLocalRef<jstring> javaSystemId(env, env->NewStringUTF(systemId)); 866 if (env->ExceptionCheck()) { 867 return XML_STATUS_ERROR; 868 } 869 ScopedLocalRef<jstring> javaPublicId(env, env->NewStringUTF(publicId)); 870 if (env->ExceptionCheck()) { 871 return XML_STATUS_ERROR; 872 } 873 ScopedLocalRef<jstring> javaContext(env, env->NewStringUTF(context)); 874 if (env->ExceptionCheck()) { 875 return XML_STATUS_ERROR; 876 } 877 878 // Pass the wrapped parser and both strings to java. 879 env->CallVoidMethod(javaParser, handleExternalEntityMethod, javaContext.get(), 880 javaPublicId.get(), javaSystemId.get()); 881 882 /* 883 * Parsing the external entity leaves parsingContext->env and object set to 884 * NULL, so we need to restore both. 885 * 886 * TODO: consider restoring the original env and object instead of setting 887 * them to NULL in the append() functions. 888 */ 889 parsingContext->env = env; 890 parsingContext->object = object; 891 892 return env->ExceptionCheck() ? XML_STATUS_ERROR : XML_STATUS_OK; 893 } 894 895 /** 896 * Expat gives us 'base', but the Java API doesn't expect it, so we don't need it. 897 */ 898 static void unparsedEntityDecl(void* data, const char* name, const char* /*base*/, const char* systemId, const char* publicId, const char* notationName) { 899 ParsingContext* parsingContext = toParsingContext(data); 900 jobject javaParser = parsingContext->object; 901 JNIEnv* env = parsingContext->env; 902 903 // Bail out if a previously called handler threw an exception. 904 if (env->ExceptionCheck()) return; 905 906 ScopedLocalRef<jstring> javaName(env, env->NewStringUTF(name)); 907 if (env->ExceptionCheck()) return; 908 ScopedLocalRef<jstring> javaPublicId(env, env->NewStringUTF(publicId)); 909 if (env->ExceptionCheck()) return; 910 ScopedLocalRef<jstring> javaSystemId(env, env->NewStringUTF(systemId)); 911 if (env->ExceptionCheck()) return; 912 ScopedLocalRef<jstring> javaNotationName(env, env->NewStringUTF(notationName)); 913 if (env->ExceptionCheck()) return; 914 915 env->CallVoidMethod(javaParser, unparsedEntityDeclMethod, javaName.get(), javaPublicId.get(), javaSystemId.get(), javaNotationName.get()); 916 } 917 918 /** 919 * Expat gives us 'base', but the Java API doesn't expect it, so we don't need it. 920 */ 921 static void notationDecl(void* data, const char* name, const char* /*base*/, const char* systemId, const char* publicId) { 922 ParsingContext* parsingContext = toParsingContext(data); 923 jobject javaParser = parsingContext->object; 924 JNIEnv* env = parsingContext->env; 925 926 // Bail out if a previously called handler threw an exception. 927 if (env->ExceptionCheck()) return; 928 929 ScopedLocalRef<jstring> javaName(env, env->NewStringUTF(name)); 930 if (env->ExceptionCheck()) return; 931 ScopedLocalRef<jstring> javaPublicId(env, env->NewStringUTF(publicId)); 932 if (env->ExceptionCheck()) return; 933 ScopedLocalRef<jstring> javaSystemId(env, env->NewStringUTF(systemId)); 934 if (env->ExceptionCheck()) return; 935 936 env->CallVoidMethod(javaParser, notationDeclMethod, javaName.get(), javaPublicId.get(), javaSystemId.get()); 937 } 938 939 /** 940 * Creates a new Expat parser. Called from the Java ExpatParser constructor. 941 * 942 * @param object the Java ExpatParser instance 943 * @param javaEncoding the character encoding name 944 * @param processNamespaces true if the parser should handle namespaces 945 * @returns the pointer to the C Expat parser 946 */ 947 static jint ExpatParser_initialize(JNIEnv* env, jobject object, jstring javaEncoding, 948 jboolean processNamespaces) { 949 // Allocate parsing context. 950 UniquePtr<ParsingContext> context(new ParsingContext(object)); 951 if (context.get() == NULL) { 952 jniThrowOutOfMemoryError(env, NULL); 953 return 0; 954 } 955 956 context->processNamespaces = (bool) processNamespaces; 957 958 // Create a parser. 959 XML_Parser parser; 960 ScopedUtfChars encoding(env, javaEncoding); 961 if (encoding.c_str() == NULL) { 962 return 0; 963 } 964 if (processNamespaces) { 965 // Use '|' to separate URIs from local names. 966 parser = XML_ParserCreateNS(encoding.c_str(), '|'); 967 } else { 968 parser = XML_ParserCreate(encoding.c_str()); 969 } 970 971 if (parser != NULL) { 972 if (processNamespaces) { 973 XML_SetNamespaceDeclHandler(parser, startNamespace, endNamespace); 974 XML_SetReturnNSTriplet(parser, 1); 975 } 976 977 XML_SetCdataSectionHandler(parser, startCdata, endCdata); 978 XML_SetCharacterDataHandler(parser, text); 979 XML_SetCommentHandler(parser, comment); 980 XML_SetDoctypeDeclHandler(parser, startDtd, endDtd); 981 XML_SetElementHandler(parser, startElement, endElement); 982 XML_SetExternalEntityRefHandler(parser, handleExternalEntity); 983 XML_SetNotationDeclHandler(parser, notationDecl); 984 XML_SetProcessingInstructionHandler(parser, processingInstruction); 985 XML_SetUnparsedEntityDeclHandler(parser, unparsedEntityDecl); 986 XML_SetUserData(parser, context.release()); 987 } else { 988 jniThrowOutOfMemoryError(env, NULL); 989 return 0; 990 } 991 992 return (jint) parser; 993 } 994 995 /** 996 * Decodes the bytes as characters and parse the characters as XML. This 997 * performs character decoding using the charset specified at XML_Parser 998 * creation. For Java chars, that charset must be UTF-16 so that a Java char[] 999 * can be reinterpreted as a UTF-16 encoded byte[]. appendBytes, appendChars 1000 * and appendString all call through this method. 1001 */ 1002 static void append(JNIEnv* env, jobject object, jint pointer, 1003 const char* bytes, size_t byteOffset, size_t byteCount, jboolean isFinal) { 1004 XML_Parser parser = (XML_Parser) pointer; 1005 ParsingContext* context = toParsingContext(parser); 1006 context->env = env; 1007 context->object = object; 1008 if (!XML_Parse(parser, bytes + byteOffset, byteCount, isFinal) && !env->ExceptionCheck()) { 1009 jniThrowExpatException(env, XML_GetErrorCode(parser)); 1010 } 1011 context->object = NULL; 1012 context->env = NULL; 1013 } 1014 1015 static void ExpatParser_appendBytes(JNIEnv* env, jobject object, jint pointer, 1016 jbyteArray xml, jint byteOffset, jint byteCount) { 1017 ScopedByteArrayRO byteArray(env, xml); 1018 if (byteArray.get() == NULL) { 1019 return; 1020 } 1021 1022 const char* bytes = reinterpret_cast<const char*>(byteArray.get()); 1023 append(env, object, pointer, bytes, byteOffset, byteCount, XML_FALSE); 1024 } 1025 1026 static void ExpatParser_appendChars(JNIEnv* env, jobject object, jint pointer, 1027 jcharArray xml, jint charOffset, jint charCount) { 1028 ScopedCharArrayRO charArray(env, xml); 1029 if (charArray.get() == NULL) { 1030 return; 1031 } 1032 1033 const char* bytes = reinterpret_cast<const char*>(charArray.get()); 1034 size_t byteOffset = 2 * charOffset; 1035 size_t byteCount = 2 * charCount; 1036 append(env, object, pointer, bytes, byteOffset, byteCount, XML_FALSE); 1037 } 1038 1039 static void ExpatParser_appendString(JNIEnv* env, jobject object, jint pointer, jstring javaXml, jboolean isFinal) { 1040 ScopedStringChars xml(env, javaXml); 1041 if (xml.get() == NULL) { 1042 return; 1043 } 1044 const char* bytes = reinterpret_cast<const char*>(xml.get()); 1045 size_t byteCount = 2 * xml.size(); 1046 append(env, object, pointer, bytes, 0, byteCount, isFinal); 1047 } 1048 1049 /** 1050 * Releases parser only. 1051 * 1052 * @param object the Java ExpatParser instance 1053 * @param i pointer to the C expat parser 1054 */ 1055 static void ExpatParser_releaseParser(JNIEnv*, jobject, jint i) { 1056 XML_Parser parser = (XML_Parser) i; 1057 XML_ParserFree(parser); 1058 } 1059 1060 /** 1061 * Cleans up after the parser. Called at garbage collection time. 1062 * 1063 * @param object the Java ExpatParser instance 1064 * @param i pointer to the C expat parser 1065 */ 1066 static void ExpatParser_release(JNIEnv* env, jobject, jint i) { 1067 XML_Parser parser = (XML_Parser) i; 1068 1069 ParsingContext* context = toParsingContext(parser); 1070 context->env = env; 1071 delete context; 1072 1073 XML_ParserFree(parser); 1074 } 1075 1076 /** 1077 * Gets the current line. 1078 * 1079 * @param object the Java ExpatParser instance 1080 * @param pointer to the C expat parser 1081 * @returns current line number 1082 */ 1083 static int ExpatParser_line(JNIEnv*, jobject, jint pointer) { 1084 XML_Parser parser = (XML_Parser) pointer; 1085 return XML_GetCurrentLineNumber(parser); 1086 } 1087 1088 /** 1089 * Gets the current column. 1090 * 1091 * @param object the Java ExpatParser instance 1092 * @param pointer to the C expat parser 1093 * @returns current column number 1094 */ 1095 static int ExpatParser_column(JNIEnv*, jobject, jint pointer) { 1096 XML_Parser parser = (XML_Parser) pointer; 1097 return XML_GetCurrentColumnNumber(parser); 1098 } 1099 1100 /** 1101 * Gets the URI of the attribute at the given index. 1102 * 1103 * @param object Java ExpatParser instance 1104 * @param pointer to the C expat parser 1105 * @param attributePointer to the attribute array 1106 * @param index of the attribute 1107 * @returns interned Java string containing attribute's URI 1108 */ 1109 static jstring ExpatAttributes_getURI(JNIEnv* env, jobject, jint pointer, 1110 jint attributePointer, jint index) { 1111 XML_Parser parser = (XML_Parser) pointer; 1112 ParsingContext* context = toParsingContext(parser); 1113 return ExpatElementName(env, context, attributePointer, index).uri(); 1114 } 1115 1116 /** 1117 * Gets the local name of the attribute at the given index. 1118 * 1119 * @param object Java ExpatParser instance 1120 * @param pointer to the C expat parser 1121 * @param attributePointer to the attribute array 1122 * @param index of the attribute 1123 * @returns interned Java string containing attribute's local name 1124 */ 1125 static jstring ExpatAttributes_getLocalName(JNIEnv* env, jobject, jint pointer, 1126 jint attributePointer, jint index) { 1127 XML_Parser parser = (XML_Parser) pointer; 1128 ParsingContext* context = toParsingContext(parser); 1129 return ExpatElementName(env, context, attributePointer, index).localName(); 1130 } 1131 1132 /** 1133 * Gets the qualified name of the attribute at the given index. 1134 * 1135 * @param object Java ExpatParser instance 1136 * @param pointer to the C expat parser 1137 * @param attributePointer to the attribute array 1138 * @param index of the attribute 1139 * @returns interned Java string containing attribute's local name 1140 */ 1141 static jstring ExpatAttributes_getQName(JNIEnv* env, jobject, jint pointer, 1142 jint attributePointer, jint index) { 1143 XML_Parser parser = (XML_Parser) pointer; 1144 ParsingContext* context = toParsingContext(parser); 1145 return ExpatElementName(env, context, attributePointer, index).qName(); 1146 } 1147 1148 /** 1149 * Gets the value of the attribute at the given index. 1150 * 1151 * @param object Java ExpatParser instance 1152 * @param attributePointer to the attribute array 1153 * @param index of the attribute 1154 * @returns Java string containing attribute's value 1155 */ 1156 static jstring ExpatAttributes_getValueByIndex(JNIEnv* env, jobject, 1157 jint attributePointer, jint index) { 1158 const char** attributes = toAttributes(attributePointer); 1159 const char* value = attributes[(index * 2) + 1]; 1160 return env->NewStringUTF(value); 1161 } 1162 1163 /** 1164 * Gets the index of the attribute with the given qualified name. 1165 * 1166 * @param attributePointer to the attribute array 1167 * @param qName to look for 1168 * @returns index of attribute with the given uri and local name or -1 if not 1169 * found 1170 */ 1171 static jint ExpatAttributes_getIndexForQName(JNIEnv* env, jobject, 1172 jint attributePointer, jstring qName) { 1173 ScopedUtfChars qNameBytes(env, qName); 1174 if (qNameBytes.c_str() == NULL) { 1175 return -1; 1176 } 1177 1178 const char** attributes = toAttributes(attributePointer); 1179 int found = -1; 1180 for (int index = 0; attributes[index * 2]; ++index) { 1181 if (ExpatElementName(NULL, NULL, attributePointer, index).matchesQName(qNameBytes.c_str())) { 1182 found = index; 1183 break; 1184 } 1185 } 1186 1187 return found; 1188 } 1189 1190 /** 1191 * Gets the index of the attribute with the given URI and name. 1192 * 1193 * @param attributePointer to the attribute array 1194 * @param uri to look for 1195 * @param localName to look for 1196 * @returns index of attribute with the given uri and local name or -1 if not 1197 * found 1198 */ 1199 static jint ExpatAttributes_getIndex(JNIEnv* env, jobject, jint attributePointer, 1200 jstring uri, jstring localName) { 1201 ScopedUtfChars uriBytes(env, uri); 1202 if (uriBytes.c_str() == NULL) { 1203 return -1; 1204 } 1205 1206 ScopedUtfChars localNameBytes(env, localName); 1207 if (localNameBytes.c_str() == NULL) { 1208 return -1; 1209 } 1210 1211 const char** attributes = toAttributes(attributePointer); 1212 for (int index = 0; attributes[index * 2]; ++index) { 1213 if (ExpatElementName(NULL, NULL, attributePointer, index).matches(uriBytes.c_str(), 1214 localNameBytes.c_str())) { 1215 return index; 1216 } 1217 } 1218 return -1; 1219 } 1220 1221 /** 1222 * Gets the value of the attribute with the given qualified name. 1223 * 1224 * @param attributePointer to the attribute array 1225 * @param uri to look for 1226 * @param localName to look for 1227 * @returns value of attribute with the given uri and local name or NULL if not 1228 * found 1229 */ 1230 static jstring ExpatAttributes_getValueForQName(JNIEnv* env, jobject clazz, 1231 jint attributePointer, jstring qName) { 1232 jint index = ExpatAttributes_getIndexForQName(env, clazz, attributePointer, qName); 1233 return index == -1 ? NULL 1234 : ExpatAttributes_getValueByIndex(env, clazz, attributePointer, index); 1235 } 1236 1237 /** 1238 * Gets the value of the attribute with the given URI and name. 1239 * 1240 * @param attributePointer to the attribute array 1241 * @param uri to look for 1242 * @param localName to look for 1243 * @returns value of attribute with the given uri and local name or NULL if not 1244 * found 1245 */ 1246 static jstring ExpatAttributes_getValue(JNIEnv* env, jobject clazz, 1247 jint attributePointer, jstring uri, jstring localName) { 1248 jint index = ExpatAttributes_getIndex(env, clazz, attributePointer, uri, localName); 1249 return index == -1 ? NULL 1250 : ExpatAttributes_getValueByIndex(env, clazz, attributePointer, index); 1251 } 1252 1253 /** 1254 * Clones an array of strings. Uses one contiguous block of memory so as to 1255 * maximize performance. 1256 * 1257 * @param address char** to clone 1258 * @param count number of attributes 1259 */ 1260 static jint ExpatParser_cloneAttributes(JNIEnv* env, jobject, jint address, jint count) { 1261 const char** source = reinterpret_cast<const char**>(static_cast<uintptr_t>(address)); 1262 count *= 2; 1263 1264 // Figure out how big the buffer needs to be. 1265 int arraySize = (count + 1) * sizeof(char*); 1266 int totalSize = arraySize; 1267 int stringLengths[count]; 1268 for (int i = 0; i < count; i++) { 1269 int length = strlen(source[i]); 1270 stringLengths[i] = length; 1271 totalSize += length + 1; 1272 } 1273 1274 char* buffer = new char[totalSize]; 1275 if (buffer == NULL) { 1276 jniThrowOutOfMemoryError(env, NULL); 1277 return 0; 1278 } 1279 1280 // Array is at the beginning of the buffer. 1281 char** clonedArray = reinterpret_cast<char**>(buffer); 1282 clonedArray[count] = NULL; // null terminate 1283 1284 // String data follows immediately after. 1285 char* destinationString = buffer + arraySize; 1286 for (int i = 0; i < count; i++) { 1287 const char* sourceString = source[i]; 1288 int stringLength = stringLengths[i]; 1289 memcpy(destinationString, sourceString, stringLength + 1); 1290 clonedArray[i] = destinationString; 1291 destinationString += stringLength + 1; 1292 } 1293 1294 return static_cast<jint>(reinterpret_cast<uintptr_t>(buffer)); 1295 } 1296 1297 /** 1298 * Frees cloned attributes. 1299 */ 1300 static void ExpatAttributes_freeAttributes(JNIEnv*, jobject, jint pointer) { 1301 delete[] reinterpret_cast<char*>(static_cast<uintptr_t>(pointer)); 1302 } 1303 1304 /** 1305 * Called when we initialize our Java parser class. 1306 * 1307 * @param clazz Java ExpatParser class 1308 */ 1309 static void ExpatParser_staticInitialize(JNIEnv* env, jobject classObject, jstring empty) { 1310 jclass clazz = reinterpret_cast<jclass>(classObject); 1311 startElementMethod = env->GetMethodID(clazz, "startElement", 1312 "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;II)V"); 1313 if (startElementMethod == NULL) return; 1314 1315 endElementMethod = env->GetMethodID(clazz, "endElement", 1316 "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); 1317 if (endElementMethod == NULL) return; 1318 1319 textMethod = env->GetMethodID(clazz, "text", "([CI)V"); 1320 if (textMethod == NULL) return; 1321 1322 commentMethod = env->GetMethodID(clazz, "comment", "([CI)V"); 1323 if (commentMethod == NULL) return; 1324 1325 startCdataMethod = env->GetMethodID(clazz, "startCdata", "()V"); 1326 if (startCdataMethod == NULL) return; 1327 1328 endCdataMethod = env->GetMethodID(clazz, "endCdata", "()V"); 1329 if (endCdataMethod == NULL) return; 1330 1331 startDtdMethod = env->GetMethodID(clazz, "startDtd", 1332 "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); 1333 if (startDtdMethod == NULL) return; 1334 1335 endDtdMethod = env->GetMethodID(clazz, "endDtd", "()V"); 1336 if (endDtdMethod == NULL) return; 1337 1338 startNamespaceMethod = env->GetMethodID(clazz, "startNamespace", 1339 "(Ljava/lang/String;Ljava/lang/String;)V"); 1340 if (startNamespaceMethod == NULL) return; 1341 1342 endNamespaceMethod = env->GetMethodID(clazz, "endNamespace", 1343 "(Ljava/lang/String;)V"); 1344 if (endNamespaceMethod == NULL) return; 1345 1346 processingInstructionMethod = env->GetMethodID(clazz, 1347 "processingInstruction", "(Ljava/lang/String;Ljava/lang/String;)V"); 1348 if (processingInstructionMethod == NULL) return; 1349 1350 handleExternalEntityMethod = env->GetMethodID(clazz, 1351 "handleExternalEntity", 1352 "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); 1353 if (handleExternalEntityMethod == NULL) return; 1354 1355 notationDeclMethod = env->GetMethodID(clazz, "notationDecl", 1356 "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); 1357 if (notationDeclMethod == NULL) return; 1358 1359 unparsedEntityDeclMethod = env->GetMethodID(clazz, "unparsedEntityDecl", 1360 "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); 1361 if (unparsedEntityDeclMethod == NULL) return; 1362 1363 internMethod = env->GetMethodID(JniConstants::stringClass, "intern", "()Ljava/lang/String;"); 1364 if (internMethod == NULL) return; 1365 1366 // Reference to "". 1367 emptyString = (jstring) env->NewGlobalRef(empty); 1368 } 1369 1370 static JNINativeMethod parserMethods[] = { 1371 NATIVE_METHOD(ExpatParser, appendString, "(ILjava/lang/String;Z)V"), 1372 NATIVE_METHOD(ExpatParser, appendBytes, "(I[BII)V"), 1373 NATIVE_METHOD(ExpatParser, appendChars, "(I[CII)V"), 1374 NATIVE_METHOD(ExpatParser, cloneAttributes, "(II)I"), 1375 NATIVE_METHOD(ExpatParser, column, "(I)I"), 1376 NATIVE_METHOD(ExpatParser, createEntityParser, "(ILjava/lang/String;)I"), 1377 NATIVE_METHOD(ExpatParser, initialize, "(Ljava/lang/String;Z)I"), 1378 NATIVE_METHOD(ExpatParser, line, "(I)I"), 1379 NATIVE_METHOD(ExpatParser, release, "(I)V"), 1380 NATIVE_METHOD(ExpatParser, releaseParser, "(I)V"), 1381 NATIVE_METHOD(ExpatParser, staticInitialize, "(Ljava/lang/String;)V"), 1382 }; 1383 1384 static JNINativeMethod attributeMethods[] = { 1385 NATIVE_METHOD(ExpatAttributes, freeAttributes, "(I)V"), 1386 NATIVE_METHOD(ExpatAttributes, getIndexForQName, "(ILjava/lang/String;)I"), 1387 NATIVE_METHOD(ExpatAttributes, getIndex, "(ILjava/lang/String;Ljava/lang/String;)I"), 1388 NATIVE_METHOD(ExpatAttributes, getLocalName, "(III)Ljava/lang/String;"), 1389 NATIVE_METHOD(ExpatAttributes, getQName, "(III)Ljava/lang/String;"), 1390 NATIVE_METHOD(ExpatAttributes, getURI, "(III)Ljava/lang/String;"), 1391 NATIVE_METHOD(ExpatAttributes, getValueByIndex, "(II)Ljava/lang/String;"), 1392 NATIVE_METHOD(ExpatAttributes, getValueForQName, "(ILjava/lang/String;)Ljava/lang/String;"), 1393 NATIVE_METHOD(ExpatAttributes, getValue, "(ILjava/lang/String;Ljava/lang/String;)Ljava/lang/String;"), 1394 }; 1395 void register_org_apache_harmony_xml_ExpatParser(JNIEnv* env) { 1396 jniRegisterNativeMethods(env, "org/apache/harmony/xml/ExpatParser", parserMethods, NELEM(parserMethods)); 1397 jniRegisterNativeMethods(env, "org/apache/harmony/xml/ExpatAttributes", attributeMethods, NELEM(attributeMethods)); 1398 } 1399