From 042d3a91d47ed0beef2c7181b33155776f72e3b2 Mon Sep 17 00:00:00 2001 From: liujia178 Date: Mon, 21 Apr 2025 15:54:25 +0800 Subject: [PATCH] Add ArkTs SDK StringDecoder Issue: https://gitee.com/openharmony/interface_sdk-js/issues/IC2RAH Signed-off-by: liujia178 --- sdk/BUILD.gn | 1 + sdk/api/@ohos.util.ets | 65 ++++++++++- sdk/native/api/ani_stringdecoder.cpp | 157 +++++++++++++++++++++++++++ sdk/native/api/ani_stringdecoder.h | 54 +++++++++ sdk/native/main.cpp | 93 ++++++++++++++++ 5 files changed, 369 insertions(+), 1 deletion(-) create mode 100644 sdk/native/api/ani_stringdecoder.cpp create mode 100644 sdk/native/api/ani_stringdecoder.h diff --git a/sdk/BUILD.gn b/sdk/BUILD.gn index e3e201d6..ff441ca5 100644 --- a/sdk/BUILD.gn +++ b/sdk/BUILD.gn @@ -66,6 +66,7 @@ generate_static_abc("commonsdk_arkts") { } ets_util_sources = [ + "$ets_util_path/sdk/native/api/ani_stringdecoder.cpp", "$ets_util_path/sdk/native/api/ani_textdecoder.cpp", "$ets_util_path/sdk/native/api/ani_textencoder.cpp", "$ets_util_path/sdk/native/api/ani_textencoder_helper.cpp", diff --git a/sdk/api/@ohos.util.ets b/sdk/api/@ohos.util.ets index 1d78c433..17c9d696 100644 --- a/sdk/api/@ohos.util.ets +++ b/sdk/api/@ohos.util.ets @@ -1040,4 +1040,67 @@ export namespace util { private static native doEncodeInfoUint8Array( input: string, inputEncoding: string, destArray: Uint8Array): EncodeIntoUint8ArrayInfo; } -} // namespace Util \ No newline at end of file + + export class StringDecoder { + static { loadLibrary("commonlib_ets_ani"); } + private encoding_: string = 'utf-8'; + private nativeDecoder_: long; + + constructor(encoding?: string) + { + if (!encoding) { + this.encoding_ = 'utf-8'; + } else { + let tempString: string = encoding.toLowerCase(); + let flag: boolean = StringDecoder.checkEncodingFormat(tempString); + if (!flag) { + throw new BusinessError(`Wrong encoding format, the current '${encoding}' format is not support.`); + } + this.encoding_ = tempString; + } + this.bindNativeStringDecoder(this.encoding_); + } + + write(chunk: string | Uint8Array): string + { + if (chunk instanceof string) { + return chunk; + } + return StringDecoder.doWrite(chunk); + } + + end(chunk?: string | Uint8Array): string + { + if (chunk && chunk instanceof string) { + return chunk; + } + return StringDecoder.doEnd(chunk); + } + + private static checkEncodingFormat(encoding: string): boolean + { + const knownEncodings: string[] = [ + "utf-8", "utf-16be", "utf-16le", "gbk", "gb2312", "gb18030", "ibm866", + "iso-8859-1", "iso-8859-2", "iso-8859-3", "iso-8859-4", "iso-8859-5", "iso-8859-6", + "iso-8859-7", "iso-8859-8", "iso-8859-8-i", "iso-8859-10", "iso-8859-13", + "iso-8859-14", "iso-8859-15", "koi8-r", "koi8-u", "macintosh", + "windows-874", "windows-1250", "windows-1251", "windows-1252", + "windows-1253", "windows-1254", "windows-1255", "windows-1256", + "windows-1257", "windows-1258", "big5", "euc-jp", "iso-2022-jp", + "shift_jis", "euc-kr", "x-mac-cyrillic" + ]; + for (const enc of knownEncodings) { + if (enc == encoding) { + return true; + } + } + return false; + } + + private native bindNativeStringDecoder(encoding: string): void; + + private static native doWrite(chunk: Uint8Array): string; + + private static native doEnd(chunk?: Uint8Array): string; + } +} // namespace Util diff --git a/sdk/native/api/ani_stringdecoder.cpp b/sdk/native/api/ani_stringdecoder.cpp new file mode 100644 index 00000000..c0a8d3e8 --- /dev/null +++ b/sdk/native/api/ani_stringdecoder.cpp @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ani_stringdecoder.h" +#include +#include +#include "tools/log.h" +#include "unicode/unistr.h" + +namespace OHOS::ETSUtil { +UConverter* UtilHelper::CreateConverter(const std::string& encStr_, UErrorCode& codeflag) +{ + UConverter *conv = ucnv_open(encStr_.c_str(), &codeflag); + if (U_FAILURE(codeflag)) { + HILOG_ERROR("Unable to create a UConverter object: %s\n", u_errorName(codeflag)); + return nullptr; + } + ucnv_setFromUCallBack(conv, UCNV_FROM_U_CALLBACK_SUBSTITUTE, NULL, NULL, NULL, &codeflag); + if (U_FAILURE(codeflag)) { + HILOG_ERROR("Unable to set the from Unicode callback function"); + ucnv_close(conv); + return nullptr; + } + + ucnv_setToUCallBack(conv, UCNV_TO_U_CALLBACK_SUBSTITUTE, NULL, NULL, NULL, &codeflag); + if (U_FAILURE(codeflag)) { + HILOG_ERROR("Unable to set the to Unicode callback function"); + ucnv_close(conv); + return nullptr; + } + return conv; +} + +std::string UtilHelper::ConvertToString(UChar *uchar) +{ + std::u16string tempStr16(uchar); + std::string tepStr = std::wstring_convert, char16_t> {}.to_bytes(tempStr16); + return tepStr; +} + +StringDecoder::StringDecoder(const std::string &encoding) +{ + UErrorCode codeflag = U_ZERO_ERROR; + conv_ = UtilHelper::CreateConverter(encoding, codeflag); +} + +ani_string StringDecoder::Write(ani_env *env, void *data, int32_t byteOffset, int32_t length, UBool flush) +{ + const char *source = static_cast(data); + size_t limit = static_cast(ucnv_getMinCharSize(conv_)) * length; + size_t len = limit * sizeof(UChar); + UChar *arr = nullptr; + if (limit > 0) { + arr = new UChar[limit + 1] { 0 }; + } else { + ThrowError(env, "Error obtaining minimum number of input bytes"); + return nullptr; + } + UChar *target = arr; + UErrorCode codeFlag = U_ZERO_ERROR; + ucnv_toUnicode(conv_, &target, target + len, &source, source + length, nullptr, flush, &codeFlag); + if (U_FAILURE(codeFlag)) { + FreedMemory(arr); + std::string err = "decoder error, "; + err += u_errorName(codeFlag); + ThrowError(env, err); + return nullptr; + } + pendingLen_ = ucnv_toUCountPending(conv_, &codeFlag); + pend_ = source + length - pendingLen_; + + ani_string resultStr {}; + size_t resultLen = target - arr; + if (env->String_NewUTF16(reinterpret_cast(arr), resultLen, &resultStr) != ANI_OK) { + HILOG_ERROR("StringDecoder:: create string error!"); + FreedMemory(arr); + return nullptr; + } + FreedMemory(arr); + return resultStr; +} + +ani_string StringDecoder::End(ani_env *env, void *data, int32_t byteOffset, int32_t length) +{ + return Write(env, data, byteOffset, length, true); +} + +ani_string StringDecoder::End(ani_env *env) +{ + ani_string resultStr {}; + if (pendingLen_ == 0) { + env->String_NewUTF8("", 0, &resultStr); + return resultStr; + } + UErrorCode errorCode = U_ZERO_ERROR; + UChar outputBuffer[pendingLen_]; + UChar *targetEnd = outputBuffer + pendingLen_; + UChar *target = outputBuffer; + const char *src = pend_; + const char *sourceEnd = pend_ + pendingLen_; + UBool flush = true; + ucnv_toUnicode(conv_, &target, targetEnd, &src, sourceEnd, nullptr, flush, &errorCode); + if (U_FAILURE(errorCode)) { + std::string err = "decoder error, "; + err += u_errorName(errorCode); + ThrowError(env, err); + return nullptr; + } + std::string tepStr = UtilHelper::ConvertToString(target, pendingLen_); + env->String_NewUTF8(tepStr.c_str(), tepStr.size(), &resultStr); + return resultStr; +} + +void StringDecoder::FreedMemory(UChar *&pData) +{ + if (pData != nullptr) { + delete[] pData; + pData = nullptr; + } +} + +ani_object StringDecoder::ThrowError(ani_env *env, std::string message) +{ + ani_string errString; + env->String_NewUTF8(message.c_str(), message.size(), &errString); + static const char *className = "L@ohos/util/util/BusinessError;"; + ani_class cls; + if (ANI_OK != env->FindClass(className, &cls)) { + HILOG_ERROR("StringDecoder:: Not found %{public}s", className); + return nullptr; + } + + ani_method errorCtor; + if (ANI_OK != env->Class_FindMethod(cls, "", "Lstd/core/String;:V", &errorCtor)) { + HILOG_ERROR("StringDecoder:: Class_FindMethod Failed"); + return nullptr; + } + + ani_object errorObj; + if (ANI_OK != env->Object_New(cls, errorCtor, &errorObj, errString)) { + HILOG_ERROR("StringDecoder:: Object_New Error Faild"); + } + return errorObj; +} +} // namespace OHOS::ETSUtil \ No newline at end of file diff --git a/sdk/native/api/ani_stringdecoder.h b/sdk/native/api/ani_stringdecoder.h new file mode 100644 index 00000000..47fbcb5e --- /dev/null +++ b/sdk/native/api/ani_stringdecoder.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UTIL_JS_STRINGDECODER_H +#define UTIL_JS_STRINGDECODER_H + +#include +#include +#include "unicode/ucnv.h" + +namespace OHOS::ETSUtil { +class UtilHelper { +public: + static UConverter* CreateConverter(const std::string& encStr_, UErrorCode& codeflag); + static std::string ConvertToString(UChar *uchar); +}; + +class StringDecoder { +public: + explicit StringDecoder(const std::string &encoding); + ~StringDecoder() { ucnv_close(conv_); } + ani_string Write(ani_env *env, void *data, int32_t byteOffset, int32_t length, UBool flush = false); + ani_string End(ani_env *env, void *data, int32_t byteOffset, int32_t length); + ani_string End(ani_env *env); + + static StringDecoder *unwrapp(ani_env *env, ani_object object) + { + ani_long stringDecoder; + if (ANI_OK != env->Object_GetFieldByName_Long(object, "nativeDecoder_", &stringDecoder)) { + return nullptr; + } + return reinterpret_cast(stringDecoder); + } +private: + ani_object ThrowError(ani_env *env, std::string message); + void FreedMemory(UChar *&pData); + const char* pend_ {}; + int pendingLen_ {}; + UConverter* conv_ {}; +}; +} +#endif // UTIL_JS_STRINGDECODER_H diff --git a/sdk/native/main.cpp b/sdk/native/main.cpp index 89522de1..eb49bfe1 100644 --- a/sdk/native/main.cpp +++ b/sdk/native/main.cpp @@ -18,6 +18,7 @@ #include #include "api/ani_textdecoder.h" #include "api/ani_textencoder.h" +#include "api/ani_stringdecoder.h" #include "api/ani_uuid.h" #include "ohos/init_data.h" @@ -38,6 +39,22 @@ static std::string ANIStringToStdString(ani_env *env, ani_string ani_str) return content; } +static ani_boolean ANIUtils_IsUndefined(ani_env *env, ani_object ani_obj) +{ + ani_boolean isUndefined = ANI_TRUE; + env->Reference_IsUndefined(ani_obj, &isUndefined); + return isUndefined; +} + +static ani_boolean ANIUtils_IsArrayBuffer(ani_env *env, ani_object ani_obj) +{ + ani_class cls {}; + env->FindClass("Lescompat/ArrayBuffer;", &cls); + ani_boolean isArrayBuffer = ANI_FALSE; + env->Object_InstanceOf(ani_obj, cls, &isArrayBuffer); + return isArrayBuffer; +} + struct ArrayBufferInfo { void* data; size_t length; @@ -217,6 +234,76 @@ static ani_status BindUtilHelper(ani_env *env) return ANI_OK; } +static void BindNativeStringDecoder(ani_env *env, ani_object object, ani_string ani_encoding, ani_int flags) +{ + std::string stringEncoding = ANIStringToStdString(env, ani_encoding); + auto nativeTextDecoder = new TextDecoder(stringEncoding, flags); + env->Object_SetFieldByName_Long(object, "nativeDecoder_", reinterpret_cast(nativeTextDecoder)); +} + +static ani_string DoWrite(ani_env *env, ani_object object, ani_object typedArray) +{ + void* data; + int32_t byteLength; + int32_t byteOffset; + GetUint8ArrayInfo(env, typedArray, data, byteLength, byteOffset); + auto stringDecoder = StringDecoder::unwrapp(env, object); + if (stringDecoder != nullptr) { + return stringDecoder->Write(env, data, byteOffset, byteLength); + } + return nullptr; +} + +static ani_string DoEnd(ani_env *env, ani_object object, ani_object typedArray) +{ + ani_boolean isUndefined = ANIUtils_IsUndefined(env, typedArray); + if (isUndefined == ANI_TRUE) { + auto stringDecoder = StringDecoder::unwrapp(env, object); + if (stringDecoder != nullptr) { + return stringDecoder->End(env); + } + return nullptr; + } else { + ani_boolean isTypeArray = ANIUtils_IsArrayBuffer(env, typedArray); + if (isTypeArray == ANI_FALSE) { + return nullptr; + } + void* data; + int32_t byteLength; + int32_t byteOffset; + GetUint8ArrayInfo(env, typedArray, data, byteLength, byteOffset); + auto stringDecoder = StringDecoder::unwrapp(env, object); + if (stringDecoder != nullptr) { + return stringDecoder->End(env, data, byteOffset, byteLength); + } + return nullptr; + } +} + +static ani_status BindStringDecoder(ani_env *env) +{ + ani_class cls; + const char *className = "L@ohos/util/util/StringDecoder;"; + if (ANI_OK != env->FindClass(className, &cls)) { + HILOG_ERROR("StringDecoder:: Not found %{public}s", className); + return ANI_ERROR; + } + std::array methods = { + ani_native_function { + "bindNativeStringDecoder", + "Lstd/core/String;:V", + reinterpret_cast(BindNativeStringDecoder) + }, + ani_native_function {"doWrite", "Lescompat/Uint8Array;:Lstd/core/String;", reinterpret_cast(DoWrite)}, + ani_native_function {"doEnd", "Lstd/core/Object;:Lstd/core/String;", reinterpret_cast(DoEnd)}, + }; + if (ANI_OK != env->Class_BindNativeMethods(cls, methods.data(), methods.size())) { + HILOG_ERROR("StringDecoder:: Cannot bind native methods to %{public}s", className); + return ANI_ERROR; + } + return ANI_OK; +} + extern "C" { ANI_EXPORT ani_status ANI_Constructor(ani_vm *vm, uint32_t *result) { @@ -242,6 +329,12 @@ ANI_EXPORT ani_status ANI_Constructor(ani_vm *vm, uint32_t *result) HILOG_ERROR("TextEncoder:: BindTextEncoder Failed"); return status; } + + status = BindStringDecoder(env); + if (status != ANI_OK) { + HILOG_ERROR("StringDecoder:: BindStringDecoder Failed"); + return status; + } *result = ANI_VERSION_1; return ANI_OK; } -- Gitee