#include <napi.h>
#include <memory>
#include <string>
#include <string_view>
#include "src/Config.hpp"
#include "src/Converter.hpp"
#include "src/DictConverter.hpp"
#include "src/Exception.hpp"
#include "src/ResourceProvider.hpp"
using namespace opencc;
std::string ToUtf8String(const Napi::Value& val) {
return val.As<Napi::String>().Utf8Value();
}
class OpenccBinding : public Napi::ObjectWrap<OpenccBinding> {
class ConvertWorker : public Napi::AsyncWorker {
OpenccBinding* instance_;
std::string input_;
std::string output_;
public:
ConvertWorker(OpenccBinding* instance, const std::string& input,
const Napi::Function& callback)
: Napi::AsyncWorker(callback, "opencc:convert-async-cb"),
instance_(instance), input_(input) {
instance_->Ref();
}
~ConvertWorker() override {
instance_->Unref();
}
void Execute() override {
try {
output_ = instance_->Convert(input_);
} catch (opencc::Exception& e) {
SetError(e.what());
}
}
void OnOK() override {
Callback().Call({Env().Undefined(), Napi::String::New(Env(), output_)});
}
void OnError(const Napi::Error& e) override {
Callback().Call({Napi::String::New(Env(), e.Message()),
Napi::String::New(Env(), "")});
}
};
Config config_;
ConverterPtr converter_;
public:
explicit OpenccBinding(const Napi::CallbackInfo& info)
: Napi::ObjectWrap<OpenccBinding>(info), config_(), converter_() {
Napi::Env env = info.Env();
if (info.Length() >= 3 && info[0].IsString() && info[1].IsString() &&
info[2].IsBoolean()) {
const std::string configFile = ToUtf8String(info[0]);
const std::string resourceZipFile = ToUtf8String(info[1]);
ConfigLoadOptions options;
options.includeTofuRiskDictionaries =
info[2].As<Napi::Boolean>().Value();
try {
std::shared_ptr<ResourceProvider> provider(
new ZipResourceProvider(resourceZipFile));
converter_ = config_.NewFromFile(configFile, provider, options);
} catch (opencc::Exception& e) {
Napi::Error::New(env, e.what()).ThrowAsJavaScriptException();
}
return;
}
if (info.Length() >= 2 && info[0].IsString() && info[1].IsString()) {
const std::string json = ToUtf8String(info[0]);
const std::string configDir = ToUtf8String(info[1]);
try {
converter_ = config_.NewFromString(json, configDir);
} catch (opencc::Exception& e) {
Napi::Error::New(env, e.what()).ThrowAsJavaScriptException();
}
return;
}
std::string configFile = "s2t.json";
if (info.Length() >= 1) {
if (!info[0].IsString()) {
Napi::TypeError::New(env, "Wrong arguments")
.ThrowAsJavaScriptException();
return;
}
configFile = ToUtf8String(info[0]);
}
try {
converter_ = config_.NewFromFile(configFile);
} catch (opencc::Exception& e) {
Napi::Error::New(env, e.what()).ThrowAsJavaScriptException();
}
}
~OpenccBinding() override {}
std::string Convert(std::string_view input) {
return converter_->Convert(input);
}
ConverterPtr GetConverter() const { return converter_; }
static Napi::Value Version(const Napi::CallbackInfo& info) {
return Napi::String::New(info.Env(), OPENCC_VERSION);
}
Napi::Value Convert(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
if (info.Length() < 2 || !info[0].IsString() || !info[1].IsFunction()) {
Napi::TypeError::New(env, "Wrong arguments").ThrowAsJavaScriptException();
return env.Undefined();
}
ConvertWorker* worker =
new ConvertWorker(this, ToUtf8String(info[0]),
info[1].As<Napi::Function>());
worker->Queue();
return env.Undefined();
}
Napi::Value ConvertSync(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
if (info.Length() < 1 || !info[0].IsString()) {
Napi::TypeError::New(env, "Wrong arguments").ThrowAsJavaScriptException();
return env.Undefined();
}
const std::string input = ToUtf8String(info[0]);
std::string output;
try {
output = Convert(std::string_view(input));
} catch (opencc::Exception& e) {
Napi::Error::New(env, e.what()).ThrowAsJavaScriptException();
return env.Undefined();
}
return Napi::String::New(env, output);
}
static Napi::Value GenerateDict(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
if (info.Length() < 4 || !info[0].IsString() || !info[1].IsString() ||
!info[2].IsString() || !info[3].IsString()) {
Napi::TypeError::New(env, "Wrong arguments").ThrowAsJavaScriptException();
return env.Undefined();
}
const std::string inputFileName = ToUtf8String(info[0]);
const std::string outputFileName = ToUtf8String(info[1]);
const std::string formatFrom = ToUtf8String(info[2]);
const std::string formatTo = ToUtf8String(info[3]);
try {
opencc::ConvertDictionary(inputFileName, outputFileName, formatFrom,
formatTo);
} catch (opencc::Exception& e) {
Napi::Error::New(env, e.what()).ThrowAsJavaScriptException();
}
return env.Undefined();
}
static Napi::Object Init(Napi::Env env, Napi::Object exports) {
Napi::Function cons = DefineClass(
env, "Opencc",
{
StaticMethod("version", &OpenccBinding::Version),
StaticMethod("generateDict", &OpenccBinding::GenerateDict),
InstanceMethod("convert", &OpenccBinding::Convert),
InstanceMethod("convertSync", &OpenccBinding::ConvertSync),
});
exports.Set("Opencc", cons);
return exports;
}
};
class OpenccStreamBinding : public Napi::ObjectWrap<OpenccStreamBinding> {
std::unique_ptr<ConverterStream> stream_;
public:
explicit OpenccStreamBinding(const Napi::CallbackInfo& info)
: Napi::ObjectWrap<OpenccStreamBinding>(info), stream_() {
Napi::Env env = info.Env();
if (info.Length() < 1 || !info[0].IsObject()) {
Napi::TypeError::New(env, "Wrong arguments").ThrowAsJavaScriptException();
return;
}
OpenccBinding* owner =
Napi::ObjectWrap<OpenccBinding>::Unwrap(info[0].As<Napi::Object>());
stream_.reset(new ConverterStream(owner->GetConverter()));
}
Napi::Value ConvertChunk(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
if (info.Length() < 1 || (!info[0].IsBuffer() && !info[0].IsString())) {
Napi::TypeError::New(env, "Wrong arguments").ThrowAsJavaScriptException();
return env.Undefined();
}
try {
std::string output;
if (info[0].IsBuffer()) {
Napi::Buffer<char> input = info[0].As<Napi::Buffer<char>>();
output = stream_->ConvertChunk({input.Data(), input.Length()});
} else {
const std::string input = ToUtf8String(info[0]);
output = stream_->ConvertChunk(input);
}
return Napi::String::New(env, output);
} catch (opencc::Exception& e) {
Napi::Error::New(env, e.what()).ThrowAsJavaScriptException();
return env.Undefined();
}
}
Napi::Value Finish(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
if (info.Length() >= 1 && !info[0].IsBuffer() && !info[0].IsString()) {
Napi::TypeError::New(env, "Wrong arguments").ThrowAsJavaScriptException();
return env.Undefined();
}
try {
if (info.Length() >= 1 && info[0].IsBuffer()) {
Napi::Buffer<char> input = info[0].As<Napi::Buffer<char>>();
return Napi::String::New(
env, stream_->Finish({input.Data(), input.Length()}));
}
if (info.Length() >= 1) {
const std::string input = ToUtf8String(info[0]);
return Napi::String::New(env, stream_->Finish(input));
}
return Napi::String::New(env, stream_->Finish());
} catch (opencc::Exception& e) {
Napi::Error::New(env, e.what()).ThrowAsJavaScriptException();
return env.Undefined();
}
}
static Napi::Object Init(Napi::Env env, Napi::Object exports) {
Napi::Function cons = DefineClass(
env, "OpenccStream",
{
InstanceMethod("convertChunk", &OpenccStreamBinding::ConvertChunk),
InstanceMethod("finish", &OpenccStreamBinding::Finish),
});
exports.Set("OpenccStream", cons);
return exports;
}
};
Napi::Object InitAll(Napi::Env env, Napi::Object exports) {
OpenccBinding::Init(env, exports);
OpenccStreamBinding::Init(env, exports);
return exports;
}
NODE_API_MODULE(opencc, InitAll);