const path = require('path');
const fs = require('fs');
const packageRoot = path.join(__dirname, '..');
function requireOptionalPackage(packageName) {
try {
return require(packageName);
} catch (error) {
if (error && error.code === 'MODULE_NOT_FOUND') {
return null;
}
throw error;
}
}
function resolveBindingPath() {
const candidates = [
path.join(packageRoot, 'build', 'Release', 'opencc.node'),
path.join(
packageRoot, 'prebuilds', `${process.platform}-${process.arch}`, 'opencc.node'
),
];
for (const candidate of candidates) {
if (fs.existsSync(candidate)) {
return candidate;
}
}
const scopedBinaryPackage = requireOptionalPackage(
`@opencc/opencc-${process.platform}-${process.arch}`
);
if (scopedBinaryPackage && scopedBinaryPackage.binaryPath) {
return scopedBinaryPackage.binaryPath;
}
throw new Error(
'Could not locate the opencc native addon (looked in build/Release, ' +
'prebuilds/, and @opencc/opencc-* scoped packages)'
);
}
const bindingPath = resolveBindingPath();
const binding = require(bindingPath);
const getAssetsPath = function (bindingPath) {
const packageAssetsPath = path.join(packageRoot, 'prebuilds', 'assets');
if (fs.existsSync(packageAssetsPath)) {
return packageAssetsPath;
}
const bindingDir = path.dirname(bindingPath);
const prebuildsDir = path.dirname(bindingDir);
if (path.basename(prebuildsDir) === 'prebuilds') {
const sharedAssetsPath = path.join(prebuildsDir, 'assets');
if (fs.existsSync(sharedAssetsPath)) {
return sharedAssetsPath;
}
}
return bindingDir;
};
function requireOptionalPeer(packageName) {
const searchPaths = [
process.cwd(),
path.join(__dirname, '..'),
__dirname,
];
for (const searchPath of searchPaths) {
try {
return require(require.resolve(packageName, { paths: [searchPath] }));
} catch (e) {
}
}
return null;
}
const jiebaInfo = requireOptionalPeer('opencc-jieba');
const assetsPath = getAssetsPath(bindingPath);
const isAbsolutePath = function (filePath) {
return path.isAbsolute(filePath) || /^[A-Za-z]:[\\/]/.test(filePath);
};
const getConfigPath = function (config) {
let configPath = config;
if (!isAbsolutePath(config)) {
configPath = path.join(assetsPath, config);
}
return configPath;
};
function patchDictPaths(dict, baseDir) {
if (!dict) return;
if (dict.type === 'group' && Array.isArray(dict.dicts)) {
for (const d of dict.dicts) {
patchDictPaths(d, baseDir);
}
} else if (dict.file && !path.isAbsolute(dict.file)) {
dict.file = path.join(baseDir, dict.file);
}
}
function filterTofuRiskDicts(dict, includeTofuRiskDictionaries) {
if (!dict) return null;
if (dict.may_output_tofu && !includeTofuRiskDictionaries) {
return null;
}
if (dict.type === 'group' && Array.isArray(dict.dicts)) {
dict.dicts = dict.dicts
.map((d) => filterTofuRiskDicts(d, includeTofuRiskDictionaries))
.filter(Boolean);
if (dict.dicts.length === 0) {
return null;
}
}
return dict;
}
function filterTofuRiskConversionChain(config, includeTofuRiskDictionaries) {
if (!Array.isArray(config.conversion_chain)) return;
config.conversion_chain = config.conversion_chain
.map((step) => {
if (!step || !step.dict) return step;
step.dict = filterTofuRiskDicts(step.dict, includeTofuRiskDictionaries);
return step.dict ? step : null;
})
.filter(Boolean);
}
function patchConfigPaths(config, jieba, mainAssetsDir) {
if (jieba && config.segmentation) {
config.segmentation.__plugin_library = jieba.pluginLibrary;
if (config.segmentation.resources) {
const res = config.segmentation.resources;
for (const key of Object.keys(res)) {
if (!path.isAbsolute(res[key])) {
res[key] = path.join(jieba.dataDir, res[key]);
}
}
}
}
if (Array.isArray(config.normalization)) {
for (const step of config.normalization) {
patchDictPaths(step.dict, mainAssetsDir);
}
}
if (Array.isArray(config.conversion_chain)) {
for (const step of config.conversion_chain) {
patchDictPaths(step.dict, mainAssetsDir);
}
}
}
function resolveJiebaConfigPath(config, jieba) {
if (!jieba || isAbsolutePath(config)) {
return null;
}
if (typeof jieba.resolveConfigPath === 'function') {
return jieba.resolveConfigPath(config);
}
const candidates = [config];
if (!config.endsWith('.json')) {
candidates.push(config + '.json');
}
for (const candidate of candidates) {
if (candidate.includes('/') || candidate.includes('\\')) {
continue;
}
const configPath = path.join(jieba.dataDir, candidate);
if (fs.existsSync(configPath)) {
return configPath;
}
}
return null;
}
const OpenCC = module.exports = function (config, options) {
if (!config) {
config = 's2t.json';
}
if (!options) {
options = {};
}
const includeTofuRiskDictionaries =
options.includeTofuRiskDictionaries !== false;
if (options.resourceZip) {
this.handler = new binding.Opencc(
config,
options.resourceZip,
includeTofuRiskDictionaries
);
return;
}
const jiebaConfigPath = resolveJiebaConfigPath(config, jiebaInfo);
if (jiebaConfigPath) {
const raw = parseJSON(fs.readFileSync(jiebaConfigPath, 'utf-8'));
filterTofuRiskConversionChain(raw, includeTofuRiskDictionaries);
patchConfigPaths(raw, jiebaInfo, assetsPath);
this.handler = new binding.Opencc(
JSON.stringify(raw),
jiebaInfo.dataDir + '/'
);
return;
}
config = getConfigPath(config);
if (!includeTofuRiskDictionaries) {
const raw = parseJSON(fs.readFileSync(config, 'utf-8'));
filterTofuRiskConversionChain(raw, includeTofuRiskDictionaries);
const configDir = path.dirname(config);
if (raw.segmentation && raw.segmentation.dict) {
patchDictPaths(raw.segmentation.dict, configDir);
}
if (Array.isArray(raw.conversion_chain)) {
for (const step of raw.conversion_chain) {
patchDictPaths(step.dict, configDir);
}
}
this.handler = new binding.Opencc(
JSON.stringify(raw),
configDir + path.sep
);
return;
}
this.handler = new binding.Opencc(config);
};
function parseJSON(str) {
const cleanStr = str.replace(/"(?:[^"\\]|\\.)*"|(\/\/.*|\/\*[\s\S]*?\*\/)|(,\s*(?=[\]}]))/g, (m, g1, g2) => (g1 || g2) ? "" : m);
return JSON.parse(cleanStr);
}
OpenCC.OpenCC = OpenCC;
OpenCC._parseJSON = parseJSON;
OpenCC._bindingPath = bindingPath;
OpenCC._assetsPath = assetsPath;
OpenCC.version = binding.Opencc.version();
OpenCC.generateDict = function (inputFileName, outputFileName,
formatFrom, formatTo) {
return binding.Opencc.generateDict(inputFileName, outputFileName,
formatFrom, formatTo);
}
OpenCC.prototype.convert = function (input, callback) {
return this.handler.convert(input.toString(), callback);
};
OpenCC.prototype.convertSync = function (input) {
return this.handler.convertSync(input.toString());
};
OpenCC.prototype.convertPromise = function (input) {
const self = this;
return new Promise(function (resolve, reject) {
self.handler.convert(input.toString(), function (err, text) {
if (err) reject(err);
else resolve(text);
});
});
};
OpenCC.prototype._createConverterStream = function () {
return new binding.OpenccStream(this.handler);
};