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