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