6.6.1 - Alpha2 - 支持 Node.js 拼音模块及结巴分词
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
"released_date": "2024/12/09",
|
||||
"feature": [
|
||||
"pinyin 模块, 用于汉语拼音转换 (参阅 项目文档 > [汉语拼音](https://docs.autojs6.com/#/pinyin))",
|
||||
"pinyin4j 模块, 用于汉语拼音转换 (参阅 项目文档 > [汉语拼音](https://docs.autojs6.com/#/pinyin4j))",
|
||||
"UiObject#isSimilar 及 UiObjectCollection#isSimilar 方法, 用于确定控件或控件集合是否相似"
|
||||
],
|
||||
"fix": [
|
||||
@@ -15,9 +16,13 @@
|
||||
],
|
||||
"improvement": [
|
||||
"脚本项目识别在 project.json 损坏情况下尽可能还原关键信息",
|
||||
"打包单文件时自动生成的包名后缀支持将简体中文自动转换为拼音",
|
||||
"打包单文件时自动生成的包名后缀支持将简体中文转换为拼音 (支持多音字)",
|
||||
"UiSelector#findOnce 及 UiSelector#find 方法支持负数参数",
|
||||
"UI 元素及 className 相关选择器支持更多的包名前缀省略形式 (如 RecyclerView, Snackbar 等)"
|
||||
],
|
||||
"dependency": [
|
||||
"附加 Jieba Analysis 版本 1.0.3-SNAPSHOT (modified)",
|
||||
"升级 Gradle 版本 8.11.1 -> 8.12"
|
||||
]
|
||||
},
|
||||
"v6.6.0": {
|
||||
|
||||
@@ -198,9 +198,13 @@ dependencies /* Unclassified */ {
|
||||
implementation("com.github.getActivity:Toaster:12.6")
|
||||
implementation("com.github.getActivity:EasyWindow:10.3")
|
||||
|
||||
// pinyin4j
|
||||
// Pinyin4j
|
||||
implementation("com.belerweb:pinyin4j:2.5.0")
|
||||
|
||||
// Jieba Analysis (zh-CN: 结巴分词)
|
||||
// implementation("com.huaban:jieba-analysis:1.0.2")
|
||||
implementation(project(":jieba-analysis"))
|
||||
|
||||
}
|
||||
|
||||
dependencies /* MIME */ {
|
||||
@@ -741,6 +745,12 @@ extra {
|
||||
versions.handleIfNeeded(project, flavorNameApp, listOf(buildTypeDebug, buildTypeRelease))
|
||||
}
|
||||
|
||||
gradle.beforeProject {
|
||||
extensions.extraProperties["compileSdk"] = versions.sdkVersionCompile
|
||||
extensions.extraProperties["minSdk"] = versions.sdkVersionMin
|
||||
extensions.extraProperties["targetSdk"] = versions.sdkVersionTarget
|
||||
}
|
||||
|
||||
class Sign(filePath: String) {
|
||||
|
||||
var isValid = false
|
||||
@@ -828,6 +838,10 @@ class Versions(filePath: String) {
|
||||
javaVersionInfoSuffix += " [consistent]"
|
||||
}
|
||||
|
||||
gradle.beforeProject {
|
||||
extensions.extraProperties["javaVersion"] = niceVersionInt
|
||||
}
|
||||
|
||||
JavaVersion.toVersion(niceVersionInt)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.autojs.autojs.engine
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import org.autojs.autojs.core.ui.ViewExtras
|
||||
@@ -179,7 +180,16 @@ open class RhinoJavaScriptEngine(private val scriptRuntime: ScriptRuntime, priva
|
||||
initStandardObjects(context, false)
|
||||
}
|
||||
|
||||
fun enterContext(): Context = RhinoAndroidHelper(androidContext).enterContext().also { setupContext(it) }
|
||||
@SuppressLint("VisibleForTests")
|
||||
fun enterContext(): Context {
|
||||
val rhinoAndroidHelper = RhinoAndroidHelper(androidContext)
|
||||
val enterContext = try {
|
||||
rhinoAndroidHelper.enterContext()
|
||||
} catch (e: SecurityException) {
|
||||
rhinoAndroidHelper.contextFactory.enterContext()
|
||||
}
|
||||
return enterContext.also { setupContext(it) }
|
||||
}
|
||||
|
||||
fun setupContext(context: Context) = context.apply {
|
||||
if (this is AutoJsContext) {
|
||||
|
||||
@@ -72,6 +72,7 @@ import org.autojs.autojs.runtime.api.augment.ocr.OcrPaddle
|
||||
import org.autojs.autojs.runtime.api.augment.ocr.OcrRapid
|
||||
import org.autojs.autojs.runtime.api.augment.opencc.OpenCC
|
||||
import org.autojs.autojs.runtime.api.augment.pinyin.Pinyin
|
||||
import org.autojs.autojs.runtime.api.augment.pinyin4j.Pinyin4j
|
||||
import org.autojs.autojs.runtime.api.augment.plugins.Plugins
|
||||
import org.autojs.autojs.runtime.api.augment.recorder.Recorder
|
||||
import org.autojs.autojs.runtime.api.augment.s13n.S13n
|
||||
@@ -763,6 +764,7 @@ class ScriptRuntime private constructor(builder: Builder) {
|
||||
SQLite(this).augment(target, true)
|
||||
NanoID.augment(target, true)
|
||||
Pinyin.augment(target, true)
|
||||
Pinyin4j.augment(target, true)
|
||||
|
||||
augmentedApp.defineProp(Autojs::class.java.simpleName.lowercase(), augmentedAutojs)
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ class RootAutomatorNativeObject(scriptRuntime: ScriptRuntime, waitForReady: Any?
|
||||
|
||||
init {
|
||||
super.exportAsJSClass(MAX_PROTOTYPE_ID, this, false)
|
||||
defineProperty("__ra__", mRootAutomatorObject, DONTENUM or READONLY or PERMANENT)
|
||||
defineProperty("__ra__", mRootAutomatorObject, READONLY or DONTENUM or PERMANENT)
|
||||
}
|
||||
|
||||
override fun has(name: String?): Boolean {
|
||||
|
||||
@@ -0,0 +1,517 @@
|
||||
package org.autojs.autojs.runtime.api.augment.pinyin
|
||||
|
||||
object Dict {
|
||||
|
||||
/* 带声调字符. */
|
||||
val PHONETIC_SYMBOL = mapOf(
|
||||
"ā" to "a1",
|
||||
"á" to "a2",
|
||||
"ǎ" to "a3",
|
||||
"à" to "a4",
|
||||
"ē" to "e1",
|
||||
"é" to "e2",
|
||||
"ě" to "e3",
|
||||
"è" to "e4",
|
||||
"ō" to "o1",
|
||||
"ó" to "o2",
|
||||
"ǒ" to "o3",
|
||||
"ò" to "o4",
|
||||
"ī" to "i1",
|
||||
"í" to "i2",
|
||||
"ǐ" to "i3",
|
||||
"ì" to "i4",
|
||||
"ū" to "u1",
|
||||
"ú" to "u2",
|
||||
"ǔ" to "u3",
|
||||
"ù" to "u4",
|
||||
"ü" to "v0",
|
||||
"ǘ" to "v2",
|
||||
"ǚ" to "v3",
|
||||
"ǜ" to "v4",
|
||||
"ń" to "n2",
|
||||
"ň" to "n3",
|
||||
"" to "m2",
|
||||
)
|
||||
|
||||
/* 百家姓. */
|
||||
/* https://zh.wikipedia.org/wiki/%E7%99%BE%E5%AE%B6%E5%A7%93 */
|
||||
val SURNAME = mapOf(
|
||||
"赵" to listOf(listOf("zhào")),
|
||||
"钱" to listOf(listOf("qián")),
|
||||
"孙" to listOf(listOf("sūn")),
|
||||
"李" to listOf(listOf("lǐ")),
|
||||
"周" to listOf(listOf("zhōu")),
|
||||
"吴" to listOf(listOf("wú")),
|
||||
"郑" to listOf(listOf("zhèng")),
|
||||
"王" to listOf(listOf("wáng")),
|
||||
"冯" to listOf(listOf("féng")),
|
||||
"陈" to listOf(listOf("chén")),
|
||||
"褚" to listOf(listOf("chǔ")),
|
||||
"卫" to listOf(listOf("wèi")),
|
||||
"蒋" to listOf(listOf("jiǎng")),
|
||||
"沈" to listOf(listOf("shěn")),
|
||||
"韩" to listOf(listOf("hán")),
|
||||
"杨" to listOf(listOf("yáng")),
|
||||
"朱" to listOf(listOf("zhū")),
|
||||
"秦" to listOf(listOf("qín")),
|
||||
"尤" to listOf(listOf("yóu")),
|
||||
"许" to listOf(listOf("xǔ")),
|
||||
"何" to listOf(listOf("hé")),
|
||||
"吕" to listOf(listOf("lǚ")),
|
||||
"施" to listOf(listOf("shī")),
|
||||
"张" to listOf(listOf("zhāng")),
|
||||
"孔" to listOf(listOf("kǒng")),
|
||||
"曹" to listOf(listOf("cáo")),
|
||||
"严" to listOf(listOf("yán")),
|
||||
"华" to listOf(listOf("huà")),
|
||||
"金" to listOf(listOf("jīn")),
|
||||
"魏" to listOf(listOf("wèi")),
|
||||
"陶" to listOf(listOf("táo")),
|
||||
"姜" to listOf(listOf("jiāng")),
|
||||
"戚" to listOf(listOf("qī")),
|
||||
"谢" to listOf(listOf("xiè")),
|
||||
"邹" to listOf(listOf("zōu")),
|
||||
"喻" to listOf(listOf("yù")),
|
||||
"柏" to listOf(listOf("bǎi")),
|
||||
"水" to listOf(listOf("shuǐ")),
|
||||
"窦" to listOf(listOf("dòu")),
|
||||
"章" to listOf(listOf("zhāng")),
|
||||
"云" to listOf(listOf("yún")),
|
||||
"苏" to listOf(listOf("sū")),
|
||||
"潘" to listOf(listOf("pān")),
|
||||
"葛" to listOf(listOf("gě")),
|
||||
"奚" to listOf(listOf("xī")),
|
||||
"范" to listOf(listOf("fàn")),
|
||||
"彭" to listOf(listOf("péng")),
|
||||
"郎" to listOf(listOf("láng")),
|
||||
"鲁" to listOf(listOf("lǔ")),
|
||||
"韦" to listOf(listOf("wéi")),
|
||||
"昌" to listOf(listOf("chāng")),
|
||||
"马" to listOf(listOf("mǎ")),
|
||||
"苗" to listOf(listOf("miáo")),
|
||||
"凤" to listOf(listOf("fèng")),
|
||||
"花" to listOf(listOf("huā")),
|
||||
"方" to listOf(listOf("fāng")),
|
||||
"俞" to listOf(listOf("yú")),
|
||||
"任" to listOf(listOf("rèn")),
|
||||
"袁" to listOf(listOf("yuán")),
|
||||
"柳" to listOf(listOf("liǔ")),
|
||||
"酆" to listOf(listOf("fēng")),
|
||||
"鲍" to listOf(listOf("bào")),
|
||||
"史" to listOf(listOf("shǐ")),
|
||||
"唐" to listOf(listOf("táng")),
|
||||
"费" to listOf(listOf("fèi")),
|
||||
"廉" to listOf(listOf("lián")),
|
||||
"岑" to listOf(listOf("cén")),
|
||||
"薛" to listOf(listOf("xuē")),
|
||||
"雷" to listOf(listOf("léi")),
|
||||
"贺" to listOf(listOf("hè")),
|
||||
"倪" to listOf(listOf("ní")),
|
||||
"汤" to listOf(listOf("tāng")),
|
||||
"滕" to listOf(listOf("téng")),
|
||||
"殷" to listOf(listOf("yīn")),
|
||||
"罗" to listOf(listOf("luó")),
|
||||
"毕" to listOf(listOf("bì")),
|
||||
"郝" to listOf(listOf("hǎo")),
|
||||
"邬" to listOf(listOf("wū")),
|
||||
"安" to listOf(listOf("ān")),
|
||||
"常" to listOf(listOf("cháng")),
|
||||
"乐" to listOf(listOf("yuè")),
|
||||
"于" to listOf(listOf("yú")),
|
||||
"时" to listOf(listOf("shí")),
|
||||
"傅" to listOf(listOf("fù")),
|
||||
"皮" to listOf(listOf("pí")),
|
||||
"卞" to listOf(listOf("biàn")),
|
||||
"齐" to listOf(listOf("qí")),
|
||||
"康" to listOf(listOf("kāng")),
|
||||
"伍" to listOf(listOf("wǔ")),
|
||||
"余" to listOf(listOf("yú")),
|
||||
"元" to listOf(listOf("yuán")),
|
||||
"卜" to listOf(listOf("bǔ")),
|
||||
"顾" to listOf(listOf("gù")),
|
||||
"孟" to listOf(listOf("mèng")),
|
||||
"平" to listOf(listOf("píng")),
|
||||
"黄" to listOf(listOf("huáng")),
|
||||
"和" to listOf(listOf("hé")),
|
||||
"穆" to listOf(listOf("mù")),
|
||||
"萧" to listOf(listOf("xiāo")),
|
||||
"尹" to listOf(listOf("yǐn")),
|
||||
"姚" to listOf(listOf("yáo")),
|
||||
"邵" to listOf(listOf("shào")),
|
||||
"湛" to listOf(listOf("zhàn")),
|
||||
"汪" to listOf(listOf("wāng")),
|
||||
"祁" to listOf(listOf("qí")),
|
||||
"毛" to listOf(listOf("máo")),
|
||||
"禹" to listOf(listOf("yǔ")),
|
||||
"狄" to listOf(listOf("dí")),
|
||||
"米" to listOf(listOf("mǐ")),
|
||||
"贝" to listOf(listOf("bèi")),
|
||||
"明" to listOf(listOf("míng")),
|
||||
"臧" to listOf(listOf("zāng")),
|
||||
"计" to listOf(listOf("jì")),
|
||||
"伏" to listOf(listOf("fú")),
|
||||
"成" to listOf(listOf("chéng")),
|
||||
"戴" to listOf(listOf("dài")),
|
||||
"谈" to listOf(listOf("tán")),
|
||||
"宋" to listOf(listOf("sòng")),
|
||||
"茅" to listOf(listOf("máo")),
|
||||
"庞" to listOf(listOf("páng")),
|
||||
"熊" to listOf(listOf("xióng")),
|
||||
"纪" to listOf(listOf("jì")),
|
||||
"舒" to listOf(listOf("shū")),
|
||||
"屈" to listOf(listOf("qū")),
|
||||
"项" to listOf(listOf("xiàng")),
|
||||
"祝" to listOf(listOf("zhù")),
|
||||
"董" to listOf(listOf("dǒng")),
|
||||
"梁" to listOf(listOf("liáng")),
|
||||
"杜" to listOf(listOf("dù")),
|
||||
"阮" to listOf(listOf("ruǎn")),
|
||||
"蓝" to listOf(listOf("lán")),
|
||||
"闵" to listOf(listOf("mǐn")),
|
||||
"席" to listOf(listOf("xí")),
|
||||
"季" to listOf(listOf("jì")),
|
||||
"麻" to listOf(listOf("má")),
|
||||
"强" to listOf(listOf("qiáng")),
|
||||
"贾" to listOf(listOf("jiǎ")),
|
||||
"路" to listOf(listOf("lù")),
|
||||
"娄" to listOf(listOf("lóu")),
|
||||
"危" to listOf(listOf("wēi")),
|
||||
"江" to listOf(listOf("jiāng")),
|
||||
"童" to listOf(listOf("tóng")),
|
||||
"颜" to listOf(listOf("yán")),
|
||||
"郭" to listOf(listOf("guō")),
|
||||
"梅" to listOf(listOf("méi")),
|
||||
"盛" to listOf(listOf("shèng")),
|
||||
"林" to listOf(listOf("lín")),
|
||||
"刁" to listOf(listOf("diāo")),
|
||||
"钟" to listOf(listOf("zhōng")),
|
||||
"徐" to listOf(listOf("xú")),
|
||||
"邱" to listOf(listOf("qiū")),
|
||||
"骆" to listOf(listOf("luò")),
|
||||
"高" to listOf(listOf("gāo")),
|
||||
"夏" to listOf(listOf("xià")),
|
||||
"蔡" to listOf(listOf("cài")),
|
||||
"田" to listOf(listOf("tián")),
|
||||
"樊" to listOf(listOf("fán")),
|
||||
"胡" to listOf(listOf("hú")),
|
||||
"凌" to listOf(listOf("líng")),
|
||||
"霍" to listOf(listOf("huò")),
|
||||
"虞" to listOf(listOf("yú")),
|
||||
"万" to listOf(listOf("wàn")),
|
||||
"支" to listOf(listOf("zhī")),
|
||||
"柯" to listOf(listOf("kē")),
|
||||
"昝" to listOf(listOf("zǎn")),
|
||||
"管" to listOf(listOf("guǎn")),
|
||||
"卢" to listOf(listOf("lú")),
|
||||
"莫" to listOf(listOf("mò")),
|
||||
"经" to listOf(listOf("jīng")),
|
||||
"房" to listOf(listOf("fáng")),
|
||||
"裘" to listOf(listOf("qiú")),
|
||||
"缪" to listOf(listOf("miào")),
|
||||
"干" to listOf(listOf("gān")),
|
||||
"解" to listOf(listOf("xiè")),
|
||||
"应" to listOf(listOf("yīng")),
|
||||
"宗" to listOf(listOf("zōng")),
|
||||
"丁" to listOf(listOf("dīng")),
|
||||
"宣" to listOf(listOf("xuān")),
|
||||
"贲" to listOf(listOf("bēn")),
|
||||
"邓" to listOf(listOf("dèng")),
|
||||
"郁" to listOf(listOf("yù")),
|
||||
"单" to listOf(listOf("shàn")),
|
||||
"杭" to listOf(listOf("háng")),
|
||||
"洪" to listOf(listOf("hóng")),
|
||||
"包" to listOf(listOf("bāo")),
|
||||
"诸" to listOf(listOf("zhū")),
|
||||
"左" to listOf(listOf("zuǒ")),
|
||||
"石" to listOf(listOf("shí")),
|
||||
"崔" to listOf(listOf("cuī")),
|
||||
"吉" to listOf(listOf("jí")),
|
||||
"钮" to listOf(listOf("niǔ")),
|
||||
"龚" to listOf(listOf("gōng")),
|
||||
"程" to listOf(listOf("chéng")),
|
||||
"嵇" to listOf(listOf("jī")),
|
||||
"邢" to listOf(listOf("xíng")),
|
||||
"滑" to listOf(listOf("huá")),
|
||||
"裴" to listOf(listOf("péi")),
|
||||
"陆" to listOf(listOf("lù")),
|
||||
"荣" to listOf(listOf("róng")),
|
||||
"翁" to listOf(listOf("wēng")),
|
||||
"荀" to listOf(listOf("xún")),
|
||||
"羊" to listOf(listOf("yáng")),
|
||||
"於" to listOf(listOf("yū")),
|
||||
"惠" to listOf(listOf("huì")),
|
||||
"甄" to listOf(listOf("zhēn")),
|
||||
"曲" to listOf(listOf("qū")),
|
||||
"家" to listOf(listOf("jiā")),
|
||||
"封" to listOf(listOf("fēng")),
|
||||
"芮" to listOf(listOf("ruì")),
|
||||
"羿" to listOf(listOf("yì")),
|
||||
"储" to listOf(listOf("chǔ")),
|
||||
"靳" to listOf(listOf("jìn")),
|
||||
"汲" to listOf(listOf("jí")),
|
||||
"邴" to listOf(listOf("bǐng")),
|
||||
"糜" to listOf(listOf("mí")),
|
||||
"松" to listOf(listOf("sōng")),
|
||||
"井" to listOf(listOf("jǐng")),
|
||||
"段" to listOf(listOf("duàn")),
|
||||
"富" to listOf(listOf("fù")),
|
||||
"巫" to listOf(listOf("wū")),
|
||||
"乌" to listOf(listOf("wū")),
|
||||
"焦" to listOf(listOf("jiāo")),
|
||||
"巴" to listOf(listOf("bā")),
|
||||
"弓" to listOf(listOf("gōng")),
|
||||
"牧" to listOf(listOf("mù")),
|
||||
"隗" to listOf(listOf("kuí")),
|
||||
"山" to listOf(listOf("shān")),
|
||||
"谷" to listOf(listOf("gǔ")),
|
||||
"车" to listOf(listOf("chē")),
|
||||
"侯" to listOf(listOf("hóu")),
|
||||
"宓" to listOf(listOf("mì")),
|
||||
"蓬" to listOf(listOf("péng")),
|
||||
"全" to listOf(listOf("quán")),
|
||||
"郗" to listOf(listOf("xī")),
|
||||
"班" to listOf(listOf("bān")),
|
||||
"仰" to listOf(listOf("yǎng")),
|
||||
"秋" to listOf(listOf("qiū")),
|
||||
"仲" to listOf(listOf("zhòng")),
|
||||
"伊" to listOf(listOf("yī")),
|
||||
"宫" to listOf(listOf("gōng")),
|
||||
"宁" to listOf(listOf("nìng")),
|
||||
"仇" to listOf(listOf("qiú")),
|
||||
"栾" to listOf(listOf("luán")),
|
||||
"暴" to listOf(listOf("bào")),
|
||||
"甘" to listOf(listOf("gān")),
|
||||
"钭" to listOf(listOf("tǒu")),
|
||||
"厉" to listOf(listOf("lì")),
|
||||
"戎" to listOf(listOf("róng")),
|
||||
"祖" to listOf(listOf("zǔ")),
|
||||
"武" to listOf(listOf("wǔ")),
|
||||
"符" to listOf(listOf("fú")),
|
||||
"刘" to listOf(listOf("liú")),
|
||||
"景" to listOf(listOf("jǐng")),
|
||||
"詹" to listOf(listOf("zhān")),
|
||||
"束" to listOf(listOf("shù")),
|
||||
"龙" to listOf(listOf("lóng")),
|
||||
"叶" to listOf(listOf("yè")),
|
||||
"幸" to listOf(listOf("xìng")),
|
||||
"司" to listOf(listOf("sī")),
|
||||
"韶" to listOf(listOf("sháo")),
|
||||
"郜" to listOf(listOf("gào")),
|
||||
"黎" to listOf(listOf("lí")),
|
||||
"蓟" to listOf(listOf("jì")),
|
||||
"薄" to listOf(listOf("bó")),
|
||||
"印" to listOf(listOf("yìn")),
|
||||
"宿" to listOf(listOf("sù")),
|
||||
"白" to listOf(listOf("bái")),
|
||||
"怀" to listOf(listOf("huái")),
|
||||
"蒲" to listOf(listOf("pú")),
|
||||
"邰" to listOf(listOf("tái")),
|
||||
"从" to listOf(listOf("cóng")),
|
||||
"鄂" to listOf(listOf("è")),
|
||||
"索" to listOf(listOf("suǒ")),
|
||||
"咸" to listOf(listOf("xián")),
|
||||
"籍" to listOf(listOf("jí")),
|
||||
"赖" to listOf(listOf("lài")),
|
||||
"卓" to listOf(listOf("zhuó")),
|
||||
"蔺" to listOf(listOf("lìn")),
|
||||
"屠" to listOf(listOf("tú")),
|
||||
"蒙" to listOf(listOf("méng")),
|
||||
"池" to listOf(listOf("chí")),
|
||||
"乔" to listOf(listOf("qiáo")),
|
||||
"阴" to listOf(listOf("yīn")),
|
||||
"鬱" to listOf(listOf("yù")),
|
||||
"胥" to listOf(listOf("xū")),
|
||||
"能" to listOf(listOf("nài")),
|
||||
"苍" to listOf(listOf("cāng")),
|
||||
"双" to listOf(listOf("shuāng")),
|
||||
"闻" to listOf(listOf("wén")),
|
||||
"莘" to listOf(listOf("shēn")),
|
||||
"党" to listOf(listOf("dǎng")),
|
||||
"翟" to listOf(listOf("zhái")),
|
||||
"谭" to listOf(listOf("tán")),
|
||||
"贡" to listOf(listOf("gòng")),
|
||||
"劳" to listOf(listOf("láo")),
|
||||
"逄" to listOf(listOf("páng")),
|
||||
"姬" to listOf(listOf("jī")),
|
||||
"申" to listOf(listOf("shēn")),
|
||||
"扶" to listOf(listOf("fú")),
|
||||
"堵" to listOf(listOf("dǔ")),
|
||||
"冉" to listOf(listOf("rǎn")),
|
||||
"宰" to listOf(listOf("zǎi")),
|
||||
"郦" to listOf(listOf("lì")),
|
||||
"雍" to listOf(listOf("yōng")),
|
||||
"郤" to listOf(listOf("xì")),
|
||||
"璩" to listOf(listOf("qú")),
|
||||
"桑" to listOf(listOf("sāng")),
|
||||
"桂" to listOf(listOf("guì")),
|
||||
"濮" to listOf(listOf("pú")),
|
||||
"牛" to listOf(listOf("niú")),
|
||||
"寿" to listOf(listOf("shòu")),
|
||||
"通" to listOf(listOf("tōng")),
|
||||
"边" to listOf(listOf("biān")),
|
||||
"扈" to listOf(listOf("hù")),
|
||||
"燕" to listOf(listOf("yān")),
|
||||
"冀" to listOf(listOf("jì")),
|
||||
"郏" to listOf(listOf("jiá")),
|
||||
"浦" to listOf(listOf("pǔ")),
|
||||
"尚" to listOf(listOf("shàng")),
|
||||
"农" to listOf(listOf("nóng")),
|
||||
"温" to listOf(listOf("wēn")),
|
||||
"别" to listOf(listOf("bié")),
|
||||
"庄" to listOf(listOf("zhuāng")),
|
||||
"晏" to listOf(listOf("yàn")),
|
||||
"柴" to listOf(listOf("chái")),
|
||||
"瞿" to listOf(listOf("qú")),
|
||||
"阎" to listOf(listOf("yán")),
|
||||
"充" to listOf(listOf("chōng")),
|
||||
"慕" to listOf(listOf("mù")),
|
||||
"连" to listOf(listOf("lián")),
|
||||
"茹" to listOf(listOf("rú")),
|
||||
"习" to listOf(listOf("xí")),
|
||||
"宦" to listOf(listOf("huàn")),
|
||||
"艾" to listOf(listOf("ài")),
|
||||
"鱼" to listOf(listOf("yú")),
|
||||
"容" to listOf(listOf("róng")),
|
||||
"向" to listOf(listOf("xiàng")),
|
||||
"古" to listOf(listOf("gǔ")),
|
||||
"易" to listOf(listOf("yì")),
|
||||
"慎" to listOf(listOf("shèn")),
|
||||
"戈" to listOf(listOf("gē")),
|
||||
"廖" to listOf(listOf("liào")),
|
||||
"庾" to listOf(listOf("yǔ")),
|
||||
"终" to listOf(listOf("zhōng")),
|
||||
"暨" to listOf(listOf("jì")),
|
||||
"居" to listOf(listOf("jū")),
|
||||
"衡" to listOf(listOf("héng")),
|
||||
"步" to listOf(listOf("bù")),
|
||||
"都" to listOf(listOf("dū")),
|
||||
"耿" to listOf(listOf("gěng")),
|
||||
"满" to listOf(listOf("mǎn")),
|
||||
"弘" to listOf(listOf("hóng")),
|
||||
"匡" to listOf(listOf("kuāng")),
|
||||
"国" to listOf(listOf("guó")),
|
||||
"文" to listOf(listOf("wén")),
|
||||
"寇" to listOf(listOf("kòu")),
|
||||
"广" to listOf(listOf("guǎng")),
|
||||
"禄" to listOf(listOf("lù")),
|
||||
"阙" to listOf(listOf("quē")),
|
||||
"东" to listOf(listOf("dōng")),
|
||||
"欧" to listOf(listOf("ōu")),
|
||||
"殳" to listOf(listOf("shū")),
|
||||
"沃" to listOf(listOf("wò")),
|
||||
"利" to listOf(listOf("lì")),
|
||||
"蔚" to listOf(listOf("wèi")),
|
||||
"越" to listOf(listOf("yuè")),
|
||||
"夔" to listOf(listOf("kuí")),
|
||||
"隆" to listOf(listOf("lóng")),
|
||||
"师" to listOf(listOf("shī")),
|
||||
"巩" to listOf(listOf("gǒng")),
|
||||
"厍" to listOf(listOf("shè")),
|
||||
"聂" to listOf(listOf("niè")),
|
||||
"晁" to listOf(listOf("cháo")),
|
||||
"勾" to listOf(listOf("gōu")),
|
||||
"敖" to listOf(listOf("áo")),
|
||||
"融" to listOf(listOf("róng")),
|
||||
"冷" to listOf(listOf("lěng")),
|
||||
"訾" to listOf(listOf("zǐ")),
|
||||
"辛" to listOf(listOf("xīn")),
|
||||
"阚" to listOf(listOf("kàn")),
|
||||
"那" to listOf(listOf("nā")),
|
||||
"简" to listOf(listOf("jiǎn")),
|
||||
"饶" to listOf(listOf("ráo")),
|
||||
"空" to listOf(listOf("kōng")),
|
||||
"曾" to listOf(listOf("zēng")),
|
||||
"母" to listOf(listOf("mǔ")),
|
||||
"沙" to listOf(listOf("shā")),
|
||||
"乜" to listOf(listOf("niè")),
|
||||
"养" to listOf(listOf("yǎng")),
|
||||
"鞠" to listOf(listOf("jū")),
|
||||
"须" to listOf(listOf("xū")),
|
||||
"丰" to listOf(listOf("fēng")),
|
||||
"巢" to listOf(listOf("cháo")),
|
||||
"关" to listOf(listOf("guān")),
|
||||
"蒯" to listOf(listOf("kuǎi")),
|
||||
"相" to listOf(listOf("xiàng")),
|
||||
"查" to listOf(listOf("zhā")),
|
||||
"后" to listOf(listOf("hòu")),
|
||||
"荆" to listOf(listOf("jīng")),
|
||||
"红" to listOf(listOf("hóng")),
|
||||
"游" to listOf(listOf("yóu")),
|
||||
"竺" to listOf(listOf("zhú")),
|
||||
"权" to listOf(listOf("quán")),
|
||||
"逯" to listOf(listOf("lù")),
|
||||
"盖" to listOf(listOf("gài")),
|
||||
"益" to listOf(listOf("yì")),
|
||||
"桓" to listOf(listOf("huán")),
|
||||
"公" to listOf(listOf("gōng")),
|
||||
"牟" to listOf(listOf("móu")),
|
||||
"哈" to listOf(listOf("hǎ")),
|
||||
"言" to listOf(listOf("yán")),
|
||||
"福" to listOf(listOf("fú")),
|
||||
)
|
||||
|
||||
/* 复姓. */
|
||||
val COMPOUND_SURNAME = mapOf(
|
||||
"万俟" to listOf(listOf("mò"), listOf("qí")),
|
||||
"上官" to listOf(listOf("shàng"), listOf("guān")),
|
||||
"东方" to listOf(listOf("dōng"), listOf("fāng")),
|
||||
"东郭" to listOf(listOf("dōng"), listOf("guō")),
|
||||
"东门" to listOf(listOf("dōng"), listOf("mén")),
|
||||
"乐正" to listOf(listOf("yuè"), listOf("zhèng")),
|
||||
"亓官" to listOf(listOf("qí"), listOf("guān")),
|
||||
"仉督" to listOf(listOf("zhǎng"), listOf("dū")),
|
||||
"令狐" to listOf(listOf("líng"), listOf("hú")),
|
||||
"仲孙" to listOf(listOf("zhòng"), listOf("sūn")),
|
||||
"公冶" to listOf(listOf("gōng"), listOf("yě")),
|
||||
"公孙" to listOf(listOf("gōng"), listOf("sūn")),
|
||||
"公羊" to listOf(listOf("gōng"), listOf("yáng")),
|
||||
"公良" to listOf(listOf("gōng"), listOf("liáng")),
|
||||
"公西" to listOf(listOf("gōng"), listOf("xī")),
|
||||
"单于" to listOf(listOf("chán"), listOf("yú")),
|
||||
"南宫" to listOf(listOf("nán"), listOf("gōng")),
|
||||
"南门" to listOf(listOf("nán"), listOf("mén")),
|
||||
"司寇" to listOf(listOf("sī"), listOf("kòu")),
|
||||
"司徒" to listOf(listOf("sī"), listOf("tú")),
|
||||
"司空" to listOf(listOf("sī"), listOf("kōng")),
|
||||
"司马" to listOf(listOf("sī"), listOf("mǎ")),
|
||||
"呼延" to listOf(listOf("hū"), listOf("yán")),
|
||||
"壤驷" to listOf(listOf("rǎng"), listOf("sì")),
|
||||
"夏侯" to listOf(listOf("xià"), listOf("hóu")),
|
||||
"太叔" to listOf(listOf("tài"), listOf("shū")),
|
||||
"夹谷" to listOf(listOf("jiá"), listOf("gǔ")),
|
||||
"子车" to listOf(listOf("zǐ"), listOf("jū")),
|
||||
"宇文" to listOf(listOf("yǔ"), listOf("wén")),
|
||||
"宗政" to listOf(listOf("zōng"), listOf("zhèng")),
|
||||
"宰父" to listOf(listOf("zǎi"), listOf("fǔ")),
|
||||
"尉迟" to listOf(listOf("yù"), listOf("chí")),
|
||||
"左丘" to listOf(listOf("zuǒ"), listOf("qiū")),
|
||||
"巫马" to listOf(listOf("wū"), listOf("mǎ")),
|
||||
"慕容" to listOf(listOf("mù"), listOf("róng")),
|
||||
"拓跋" to listOf(listOf("tuò"), listOf("bá")),
|
||||
"梁丘" to listOf(listOf("liáng"), listOf("qiū")),
|
||||
"榖梁" to listOf(listOf("gǔ"), listOf("liáng")),
|
||||
"欧阳" to listOf(listOf("ōu"), listOf("yáng")),
|
||||
"段干" to listOf(listOf("duàn"), listOf("gān")),
|
||||
"淳于" to listOf(listOf("chún"), listOf("yú")),
|
||||
"漆雕" to listOf(listOf("qī"), listOf("diāo")),
|
||||
"澹台" to listOf(listOf("tán"), listOf("tái")),
|
||||
"濮阳" to listOf(listOf("pú"), listOf("yáng")),
|
||||
"申屠" to listOf(listOf("shēn"), listOf("tú")),
|
||||
"百里" to listOf(listOf("bǎi"), listOf("lǐ")),
|
||||
"皇甫" to listOf(listOf("huáng"), listOf("pǔ")),
|
||||
"端木" to listOf(listOf("duān"), listOf("mù")),
|
||||
"第五" to listOf(listOf("dì"), listOf("wǔ")),
|
||||
"羊舌" to listOf(listOf("yáng"), listOf("shé")),
|
||||
"西门" to listOf(listOf("xī"), listOf("mén")),
|
||||
"诸葛" to listOf(listOf("zhū"), listOf("gě")),
|
||||
"赫连" to listOf(listOf("hè"), listOf("lián")),
|
||||
"轩辕" to listOf(listOf("xuān"), listOf("yuán")),
|
||||
"钟离" to listOf(listOf("zhōng"), listOf("lí")),
|
||||
"长孙" to listOf(listOf("zhǎng"), listOf("sūn")),
|
||||
"闻人" to listOf(listOf("wén"), listOf("rén")),
|
||||
"闾丘" to listOf(listOf("lǘ"), listOf("qiū")),
|
||||
"颛孙" to listOf(listOf("zhuān"), listOf("sūn")),
|
||||
"鲜于" to listOf(listOf("xiān"), listOf("yú")),
|
||||
)
|
||||
|
||||
}
|
||||
@@ -1,178 +1,263 @@
|
||||
package org.autojs.autojs.runtime.api.augment.pinyin
|
||||
|
||||
import net.sourceforge.pinyin4j.PinyinHelper
|
||||
import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType
|
||||
import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType.LOWERCASE
|
||||
import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType.UPPERCASE
|
||||
import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat
|
||||
import net.sourceforge.pinyin4j.format.HanyuPinyinToneType
|
||||
import net.sourceforge.pinyin4j.format.HanyuPinyinToneType.WITHOUT_TONE
|
||||
import net.sourceforge.pinyin4j.format.HanyuPinyinToneType.WITH_TONE_MARK
|
||||
import net.sourceforge.pinyin4j.format.HanyuPinyinToneType.WITH_TONE_NUMBER
|
||||
import net.sourceforge.pinyin4j.format.HanyuPinyinVCharType
|
||||
import net.sourceforge.pinyin4j.format.HanyuPinyinVCharType.WITH_U_AND_COLON
|
||||
import net.sourceforge.pinyin4j.format.HanyuPinyinVCharType.WITH_U_UNICODE
|
||||
import net.sourceforge.pinyin4j.format.HanyuPinyinVCharType.WITH_V
|
||||
import com.huaban.analysis.jieba.CharsDictionaryDatabase
|
||||
import com.huaban.analysis.jieba.JiebaSegmenter
|
||||
import com.huaban.analysis.jieba.PhrasesDictionaryDatabase
|
||||
import org.autojs.autojs.annotation.RhinoSingletonFunctionInterface
|
||||
import org.autojs.autojs.extension.ArrayExtensions.toNativeArray
|
||||
import org.autojs.autojs.extension.ScriptableExtensions.defineProp
|
||||
import org.autojs.autojs.extension.ScriptableObjectExtensions.inquire
|
||||
import org.autojs.autojs.runtime.api.augment.Augmentable
|
||||
import org.autojs.autojs.runtime.api.augment.Invokable
|
||||
import org.autojs.autojs.runtime.api.augment.pinyin.PinyinCore.DefaultOptions
|
||||
import org.autojs.autojs.runtime.api.augment.pinyin.PinyinCore.PinyinMode
|
||||
import org.autojs.autojs.runtime.api.augment.pinyin.PinyinCore.PinyinStyle
|
||||
import org.autojs.autojs.runtime.api.augment.pinyin.PinyinCore.parseStyle
|
||||
import org.autojs.autojs.util.RhinoUtils.coerceBoolean
|
||||
import org.autojs.autojs.util.RhinoUtils.coerceIntNumber
|
||||
import org.autojs.autojs.util.RhinoUtils.coerceString
|
||||
import org.autojs.autojs.util.RhinoUtils.coerceStringUppercase
|
||||
import org.autojs.autojs.util.RhinoUtils.newNativeObject
|
||||
import org.autojs.autojs.util.RhinoUtils.withRhinoContext
|
||||
import org.mozilla.javascript.BoundFunction
|
||||
import org.mozilla.javascript.Context
|
||||
import org.mozilla.javascript.NativeArray
|
||||
import org.mozilla.javascript.NativeJavaMethod
|
||||
import org.mozilla.javascript.NativeObject
|
||||
import org.mozilla.javascript.Scriptable
|
||||
import org.mozilla.javascript.ScriptableObject.DONTENUM
|
||||
import org.mozilla.javascript.ScriptableObject.PERMANENT
|
||||
import org.mozilla.javascript.ScriptableObject.READONLY
|
||||
|
||||
object Pinyin : Augmentable(), Invokable {
|
||||
|
||||
private const val DEFAULT_SEPARATOR = ""
|
||||
private val charsDictDatabase by lazy { CharsDictionaryDatabase.getInstance(globalContext).database }
|
||||
private val phrasesDictDatabase by lazy { PhrasesDictionaryDatabase.getInstance(globalContext).database }
|
||||
|
||||
private const val DEFAULT_CASE_TYPE_STRING = "LOWERCASE"
|
||||
private val DEFAULT_CASE_TYPE = LOWERCASE
|
||||
|
||||
private const val DEFAULT_TONE_TYPE_STRING = "WITHOUT_TONE"
|
||||
private val DEFAULT_TONE_TYPE = WITHOUT_TONE
|
||||
|
||||
private const val DEFAULT_V_CHAR_TYPE_STRING = "WITH_V"
|
||||
private val DEFAULT_V_CHAR_TYPE = WITH_V
|
||||
|
||||
private val pinyinToneMap = mapOf(
|
||||
// 单韵母
|
||||
"a1" to "ā", "a2" to "á", "a3" to "ǎ", "a4" to "à", "a0" to "a",
|
||||
"o1" to "ō", "o2" to "ó", "o3" to "ǒ", "o4" to "ò", "o0" to "o",
|
||||
"e1" to "ē", "e2" to "é", "e3" to "ě", "e4" to "è", "e0" to "e",
|
||||
"i1" to "ī", "i2" to "í", "i3" to "ǐ", "i4" to "ì", "i0" to "i",
|
||||
"u1" to "ū", "u2" to "ú", "u3" to "ǔ", "u4" to "ù", "u0" to "u",
|
||||
"ü1" to "ǖ", "ü2" to "ǘ", "ü3" to "ǚ", "ü4" to "ǜ", "ü0" to "ü",
|
||||
"v1" to "ǖ", "v2" to "ǘ", "v3" to "ǚ", "v4" to "ǜ", "v0" to "ü",
|
||||
"u:1" to "ǖ", "u:2" to "ǘ", "u:3" to "ǚ", "u:4" to "ǜ", "u:0" to "ü",
|
||||
|
||||
// 复韵母 - 普通
|
||||
"ai1" to "āi", "ai2" to "ái", "ai3" to "ǎi", "ai4" to "ài", "ai0" to "ai",
|
||||
"ei1" to "ēi", "ei2" to "éi", "ei3" to "ěi", "ei4" to "èi", "ei0" to "ei",
|
||||
"ui1" to "uī", "ui2" to "uí", "ui3" to "uǐ", "ui4" to "uì", "ui0" to "ui",
|
||||
"ao1" to "āo", "ao2" to "áo", "ao3" to "ǎo", "ao4" to "ào", "ao0" to "ao",
|
||||
"ou1" to "ōu", "ou2" to "óu", "ou3" to "ǒu", "ou4" to "òu", "ou0" to "ou",
|
||||
"iu1" to "iū", "iu2" to "iú", "iu3" to "iǔ", "iu4" to "iù", "iu0" to "iu",
|
||||
"ie1" to "iē", "ie2" to "ié", "ie3" to "iě", "ie4" to "iè", "ie0" to "ie",
|
||||
"üe1" to "üē", "üe2" to "üé", "üe3" to "üě", "üe4" to "üè", "üe0" to "üe",
|
||||
"u:e1" to "üē", "u:e2" to "üé", "u:e3" to "üě", "u:e4" to "üè", "u:e0" to "üe",
|
||||
"ve1" to "üē", "ve2" to "üé", "ve3" to "üě", "ve4" to "üè", "ve0" to "üe",
|
||||
|
||||
// 复韵母 - 特殊
|
||||
"er1" to "ēr", "er2" to "ér", "er3" to "ěr", "er4" to "èr", "er0" to "er",
|
||||
|
||||
// 复韵母 - 前鼻韵母
|
||||
"an1" to "ān", "an2" to "án", "an3" to "ǎn", "an4" to "àn", "an0" to "an",
|
||||
"en1" to "ēn", "en2" to "én", "en3" to "ěn", "en4" to "èn", "en0" to "en",
|
||||
"in1" to "īn", "in2" to "ín", "in3" to "ǐn", "in4" to "ìn", "in0" to "in",
|
||||
"un1" to "ūn", "un2" to "ún", "un3" to "ǔn", "un4" to "ùn", "un0" to "un",
|
||||
"ün1" to "ǖn", "ün2" to "ǘn", "ün3" to "ǚn", "ün4" to "ǜn", "ün0" to "ün",
|
||||
"u:n1" to "ǖn", "u:n2" to "ǘn", "u:n3" to "ǚn", "u:n4" to "ǜn", "u:n0" to "ün",
|
||||
"vn1" to "ǖn", "vn2" to "ǘn", "vn3" to "ǚn", "vn4" to "ǜn", "vn0" to "ün",
|
||||
|
||||
// 复韵母 - 后鼻韵母
|
||||
"ang1" to "āng", "ang2" to "áng", "ang3" to "ǎng", "ang4" to "àng", "ang0" to "ang",
|
||||
"eng1" to "ēng", "eng2" to "éng", "eng3" to "ěng", "eng4" to "èng", "eng0" to "eng",
|
||||
"ing1" to "īng", "ing2" to "íng", "ing3" to "ǐng", "ing4" to "ìng", "ing0" to "ing",
|
||||
"ong1" to "ōng", "ong2" to "óng", "ong3" to "ǒng", "ong4" to "òng", "ong0" to "ong"
|
||||
@Suppress("SpellCheckingInspection")
|
||||
override val selfAssignmentProperties = listOf(
|
||||
"STYLE_NORMAL" to PinyinStyle.NORMAL,
|
||||
"STYLE_TONE" to PinyinStyle.TONE,
|
||||
"STYLE_TONE2" to PinyinStyle.TONE2,
|
||||
"STYLE_TO3NE" to PinyinStyle.TO3NE,
|
||||
"STYLE_INITIALS" to PinyinStyle.INITIALS,
|
||||
"STYLE_FIRST_LETTER" to PinyinStyle.FIRST_LETTER,
|
||||
"MODE_NORMAL" to PinyinMode.NORMAL,
|
||||
"MODE_SURNAME" to PinyinMode.SURNAME,
|
||||
"MODE_PLACENAME" to PinyinMode.PLACE_NAME,
|
||||
"MODE_PLACE_NAME" to PinyinMode.PLACE_NAME,
|
||||
)
|
||||
|
||||
override val selfAssignmentFunctions = listOf(
|
||||
::`as`.name,
|
||||
::of.name,
|
||||
::convert.name,
|
||||
::simple.name,
|
||||
::compare.name,
|
||||
::compact.name,
|
||||
::fromCodePoint.name,
|
||||
::fromPhrase.name,
|
||||
)
|
||||
|
||||
@RhinoSingletonFunctionInterface
|
||||
override fun invoke(args: Array<out Any?>): String = ensureArgumentsLengthInRange(args, 1..2) { argList ->
|
||||
of(argList)
|
||||
override fun invoke(args: Array<out Any?>): NativeArray = ensureArgumentsLengthInRange(args, 1..2) { argList ->
|
||||
convert(argList)
|
||||
}
|
||||
|
||||
@RhinoSingletonFunctionInterface
|
||||
fun of(args: Array<out Any?>): String = ensureArgumentsLengthInRange(args, 1..2) { argList ->
|
||||
val (string, options) = argList
|
||||
|
||||
val str = coerceString(string, "").takeIf { it.isNotEmpty() } ?: return@ensureArgumentsLengthInRange ""
|
||||
var separator = DEFAULT_SEPARATOR
|
||||
val opt = when (options) {
|
||||
is String -> newNativeObject().also { separator = options }
|
||||
is NativeObject -> options
|
||||
else -> newNativeObject()
|
||||
}
|
||||
|
||||
separator = opt.inquire(listOf("separator", "sep"), ::coerceString, separator)
|
||||
|
||||
val caseType = when (val case = opt.inquire(listOf("case", "caseType"))) {
|
||||
is HanyuPinyinCaseType -> case
|
||||
else -> when (coerceStringUppercase(case, DEFAULT_CASE_TYPE_STRING)) {
|
||||
"LOWERCASE", "LOW", "L", "0" -> LOWERCASE
|
||||
"UPPERCASE", "UP", "U", "1" -> UPPERCASE
|
||||
else -> throw IllegalArgumentException("Invalid case type: ${Context.toString(case)}")
|
||||
fun convert(args: Array<out Any?>): NativeArray = ensureArgumentsLengthInRange(args, 1..2) { argList ->
|
||||
val (hansArg, options) = argList
|
||||
val hans = coerceString(hansArg, "")
|
||||
val opt = options as? NativeObject ?: newNativeObject()
|
||||
convertRhino(hans, opt).map { it.toNativeArray() }.toNativeArray().also { result ->
|
||||
val funcName = "compact"
|
||||
withRhinoContext { cx ->
|
||||
val thisObj = Context.javaToJS(Util, result) as Scriptable
|
||||
val boundFunction = BoundFunction(
|
||||
/* cx = */ cx,
|
||||
/* scope = */ result,
|
||||
/* targetFunction = */ NativeJavaMethod(Util::class.java.methods.find { it.name == funcName }, funcName),
|
||||
/* boundThis = */ thisObj,
|
||||
/* boundArgs = */ arrayOf(result),
|
||||
)
|
||||
result.defineProp(funcName, boundFunction, READONLY or DONTENUM or PERMANENT)
|
||||
}
|
||||
}
|
||||
val toneType = when (val tone = opt.inquire(listOf("tone", "toneType"))) {
|
||||
is HanyuPinyinToneType -> tone
|
||||
else -> when (coerceStringUppercase(tone, DEFAULT_TONE_TYPE_STRING)) {
|
||||
"WITH_TONE_NUMBER", "WITH_NUMBER", "NUMBER", "NUM" -> WITH_TONE_NUMBER
|
||||
"WITHOUT_TONE", "NO_TONE", "NO", "FALSE", "0" -> WITHOUT_TONE
|
||||
"WITH_TONE_MARK", "WITH_MARK", "MARK", "TRUE", "1" -> WITH_TONE_MARK
|
||||
else -> throw IllegalArgumentException("Invalid tone type: ${Context.toString(tone)}")
|
||||
}
|
||||
}
|
||||
val vCharType = when (val vChar = opt.inquire(listOf("v", "vChar", "vCharType"))) {
|
||||
is HanyuPinyinVCharType -> vChar
|
||||
else -> when (coerceStringUppercase(vChar, if (toneType == WITH_TONE_MARK) "WITH_U_UNICODE" else DEFAULT_V_CHAR_TYPE_STRING)) {
|
||||
"WITH_U_AND_COLON", "U_AND_COLON", "U_COLON", "U:" -> WITH_U_AND_COLON
|
||||
"WITH_V", "V" -> WITH_V
|
||||
"WITH_U_UNICODE", "U_UNICODE", "UNICODE", "Ü" -> WITH_U_UNICODE
|
||||
else -> throw IllegalArgumentException("Invalid v char type: ${Context.toString(vChar)}")
|
||||
}
|
||||
}
|
||||
ofRhino(str, separator, caseType, toneType, vCharType)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun ofRhino(
|
||||
str: String,
|
||||
separator: String = DEFAULT_SEPARATOR,
|
||||
caseType: HanyuPinyinCaseType = DEFAULT_CASE_TYPE,
|
||||
toneType: HanyuPinyinToneType = DEFAULT_TONE_TYPE,
|
||||
vCharType: HanyuPinyinVCharType = DEFAULT_V_CHAR_TYPE,
|
||||
): String {
|
||||
val format = HanyuPinyinOutputFormat().also {
|
||||
it.caseType = caseType
|
||||
it.toneType = toneType
|
||||
it.vCharType = vCharType
|
||||
}
|
||||
|
||||
val pinyinResult = StringBuilder()
|
||||
|
||||
str.forEach { ch ->
|
||||
val pinyin = when (ch) {
|
||||
in '\u4E00'..'\u9FA5' -> {
|
||||
PinyinHelper.toHanyuPinyinStringArray(ch, format)?.firstOrNull() ?: ""
|
||||
fun convertRhino(hans: String, opt: NativeObject): List<List<String>> = when {
|
||||
hans.isEmpty() -> listOf()
|
||||
else -> {
|
||||
val phrases: List<String> = when {
|
||||
opt.inquire("segment", ::coerceBoolean, DefaultOptions.SEGMENT) -> {
|
||||
segment(hans)
|
||||
}
|
||||
else -> ch.toString()
|
||||
else -> groupChineseAndNonChinese(hans)
|
||||
}
|
||||
pinyinResult.append(pinyin).append(separator)
|
||||
}
|
||||
var nonHans = ""
|
||||
|
||||
return pinyinResult.toString().trim()
|
||||
when (PinyinMode.SURNAME.value) {
|
||||
opt.inquire("mode", { o, def -> PinyinCore.parseMode(o) ?: def }, DefaultOptions.MODE.value) -> {
|
||||
PinyinCore.surnamePinyin(hans, opt)
|
||||
}
|
||||
else -> {
|
||||
val pinyinList = mutableListOf<List<String>>()
|
||||
for (words in phrases) {
|
||||
val firstCharCode = words.codePointAt(0)
|
||||
if (fromCodePointInternal(firstCharCode) != null) {
|
||||
if (nonHans.isNotEmpty()) {
|
||||
pinyinList.add(listOf(nonHans))
|
||||
nonHans = ""
|
||||
}
|
||||
|
||||
val newPinyinList = when (words.length) {
|
||||
1 -> PinyinCore.convert(words, opt)
|
||||
else -> phrasePinyin(words, opt)
|
||||
}
|
||||
|
||||
if (opt.inquire("group", ::coerceBoolean, false)) {
|
||||
pinyinList.add(groupPhrases(newPinyinList))
|
||||
} else {
|
||||
pinyinList.addAll(newPinyinList)
|
||||
}
|
||||
} else {
|
||||
nonHans += words
|
||||
}
|
||||
}
|
||||
if (nonHans.isNotEmpty()) {
|
||||
pinyinList.add(listOf(nonHans))
|
||||
}
|
||||
pinyinList
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RhinoSingletonFunctionInterface
|
||||
fun `as`(args: Array<out Any?>): String = ensureArgumentsOnlyOne(args) { o ->
|
||||
convertToTonePinyin(coerceString(o, ""))
|
||||
fun compare(args: Array<out Any?>): String = ensureArgumentsLengthInRange(args, 1..2) { argList ->
|
||||
""
|
||||
}
|
||||
|
||||
private fun convertToTonePinyin(input: String): String {
|
||||
val regex = Regex("(üe|u:e|ve|ue|ang|eng|ing|ong|ai|ei|ui|ao|ou|iu|ie|er|an|en|in|un|ün|u:n|vn|[aoeiuüv])([0-4])")
|
||||
return regex.replace(input) { matchResult ->
|
||||
val pinyinBase = matchResult.groupValues[1]
|
||||
val toneNumber = matchResult.groupValues[2]
|
||||
pinyinToneMap[pinyinBase + toneNumber] ?: matchResult.value
|
||||
@RhinoSingletonFunctionInterface
|
||||
fun compact(args: Array<out Any?>): String = ensureArgumentsLengthInRange(args, 1..2) { argList ->
|
||||
""
|
||||
}
|
||||
|
||||
@RhinoSingletonFunctionInterface
|
||||
fun simple(args: Array<out Any?>): String = ensureArgumentsLengthInRange(args, 1..3) { argList ->
|
||||
val (str, enableNumericTone, enableSegment) = argList
|
||||
simpleRhino(coerceString(str, ""), coerceBoolean(enableNumericTone, false), coerceBoolean(enableSegment, false))
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun simpleRhino(s: String, enableNumericTone: Boolean = false, enableSegment: Boolean = false): String {
|
||||
return convertRhino(s, newNativeObject().also {
|
||||
it.defineProp("style", if (enableNumericTone) PinyinStyle.TONE2 else PinyinStyle.NORMAL)
|
||||
it.defineProp("segment", enableSegment)
|
||||
}).map { it.firstOrNull() ?: emptyList<String>() }.joinToString("")
|
||||
}
|
||||
|
||||
@RhinoSingletonFunctionInterface
|
||||
fun fromCodePoint(args: Array<out Any?>): String? = ensureArgumentsOnlyOne(args) { o ->
|
||||
fromCodePointInternal(coerceIntNumber(o))
|
||||
}
|
||||
|
||||
@RhinoSingletonFunctionInterface
|
||||
fun fromPhrase(args: Array<out Any?>): NativeArray = ensureArgumentsOnlyOne(args) { o ->
|
||||
fromPhraseInternal(coerceString(o)).map { it.toNativeArray() }.toNativeArray()
|
||||
}
|
||||
|
||||
internal fun fromCodePointInternal(codePoint: Int): String? = charsDictDatabase.let { db ->
|
||||
db.query(
|
||||
"Dict",
|
||||
arrayOf("Pinyin"),
|
||||
"CodePoint = ?",
|
||||
arrayOf(codePoint.toString()),
|
||||
null,
|
||||
null,
|
||||
null
|
||||
).use { cursor ->
|
||||
when {
|
||||
cursor.moveToFirst() -> cursor.getString(cursor.getColumnIndexOrThrow("Pinyin"))
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun fromPhraseInternal(phrase: String): List<List<String>> = phrasesDictDatabase.let { db ->
|
||||
val cursor = db.query(
|
||||
"PhrasesDict",
|
||||
arrayOf("Pinyin", "OrderIndex"),
|
||||
"Word = ?",
|
||||
arrayOf(phrase),
|
||||
null,
|
||||
null,
|
||||
"OrderIndex ASC",
|
||||
)
|
||||
|
||||
val result = mutableListOf<MutableList<String>>()
|
||||
cursor.use {
|
||||
while (it.moveToNext()) {
|
||||
val pinyin = it.getString(it.getColumnIndexOrThrow("Pinyin"))
|
||||
val orderIndex = it.getInt(it.getColumnIndexOrThrow("OrderIndex"))
|
||||
while (result.size < orderIndex) {
|
||||
result.add(mutableListOf())
|
||||
}
|
||||
result[orderIndex - 1].add(pinyin)
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
private fun segment(hans: String): List<String> {
|
||||
return JiebaSegmenter(globalContext).cutSmall(hans, 4)
|
||||
}
|
||||
|
||||
private fun phrasePinyin(phrase: String, options: NativeObject): List<List<String>> {
|
||||
val phrasePinyinList = fromPhraseInternal(phrase)
|
||||
return when {
|
||||
phrasePinyinList.isNotEmpty() -> {
|
||||
val style = options.inquire("style", { o, def -> parseStyle(o) ?: def }, DefaultOptions.STYLE.value)
|
||||
phrasePinyinList.map { item ->
|
||||
if (options.inquire("heteronym", ::coerceBoolean, DefaultOptions.HETERONYM)) {
|
||||
item.map { pyItem -> PinyinCore.toFixed(pyItem, style) }.toMutableList()
|
||||
} else {
|
||||
mutableListOf(PinyinCore.toFixed(item[0], style))
|
||||
}
|
||||
}.toMutableList()
|
||||
}
|
||||
else -> PinyinCore.convert(phrase, options)
|
||||
}
|
||||
}
|
||||
|
||||
private fun groupPhrases(phrases: List<List<String>>): List<String> {
|
||||
return when (phrases.size) {
|
||||
1 -> phrases[0]
|
||||
else -> Util.combo(phrases)
|
||||
}
|
||||
}
|
||||
|
||||
private fun groupChineseAndNonChinese(input: String): List<String> {
|
||||
if (input.isEmpty()) return emptyList()
|
||||
|
||||
val result = mutableListOf<String>()
|
||||
val chineseRegex = "\\p{IsHan}".toRegex() // 匹配中文字符的正则
|
||||
var tempGroup = StringBuilder()
|
||||
var isLastChinese = chineseRegex.matches(input[0].toString()) // 判断第一个字符是否中文
|
||||
|
||||
for (char in input) {
|
||||
val isChinese = chineseRegex.matches(char.toString())
|
||||
// 如果字符类型(中文或非中文)发生切换,保存当前分组
|
||||
if (isChinese != isLastChinese) {
|
||||
result.add(tempGroup.toString()) // 添加当前分组
|
||||
tempGroup = StringBuilder() // 重置分组
|
||||
}
|
||||
tempGroup.append(char) // 将字符添加到当前分组
|
||||
isLastChinese = isChinese // 更新当前字符类型
|
||||
}
|
||||
// 添加最后一个分组
|
||||
if (tempGroup.isNotEmpty()) {
|
||||
result.add(tempGroup.toString())
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
package org.autojs.autojs.runtime.api.augment.pinyin
|
||||
|
||||
import org.autojs.autojs.extension.AnyExtensions.isJsNullish
|
||||
import org.autojs.autojs.extension.AnyExtensions.jsUnwrapped
|
||||
import org.autojs.autojs.extension.ScriptableObjectExtensions.inquire
|
||||
import org.autojs.autojs.util.RhinoUtils.coerceBoolean
|
||||
import org.autojs.autojs.util.RhinoUtils.coerceString
|
||||
import org.autojs.autojs.util.RhinoUtils.newNativeObject
|
||||
import org.mozilla.javascript.NativeObject
|
||||
import java.util.regex.Pattern
|
||||
|
||||
object PinyinCore {
|
||||
|
||||
private val SURNAME = Dict.SURNAME
|
||||
private val COMPOUND_SURNAME = Dict.COMPOUND_SURNAME
|
||||
|
||||
// 声母表
|
||||
private val INITIALS = arrayOf(
|
||||
"b", "p", "m", "f", "d", "t", "n", "l",
|
||||
"g", "k", "h", "j", "q", "x",
|
||||
"zh", "ch", "sh", "r", "z", "c", "s",
|
||||
)
|
||||
|
||||
// 韵母表
|
||||
private val FINALS = arrayOf(
|
||||
"a", "o", "e", "i", "u", "v",
|
||||
"ai", "ei", "ui", "ao", "ou", "iu", "ie", "ve", "er",
|
||||
"an", "en", "in", "un", "vn",
|
||||
"ang", "eng", "ing", "ong",
|
||||
)
|
||||
|
||||
// 带声调字符
|
||||
private val PHONETIC_SYMBOL = Dict.PHONETIC_SYMBOL
|
||||
private val RE_PHONETIC_SYMBOL = Pattern.compile("([${PHONETIC_SYMBOL.keys.joinToString("")}])").toRegex()
|
||||
private val RE_TONE2 = Regex("([aeoiuvnm])([0-4])$")
|
||||
|
||||
/**
|
||||
* @param hansArg 要转为拼音的目标字符串 (汉字).
|
||||
* @param options 可选, 用于指定拼音风格, 是否启用多音字.
|
||||
* @return 返回的拼音列表.
|
||||
*/
|
||||
fun convert(hansArg: String, options: NativeObject = newNativeObject()): MutableList<List<String>> {
|
||||
val pinyinList = mutableListOf<List<String>>()
|
||||
val hans = coerceString(hansArg, "").trim()
|
||||
|
||||
if (hans.isNotEmpty()) {
|
||||
var nonHans = ""
|
||||
|
||||
when (PinyinMode.SURNAME.value) {
|
||||
options.inquire("mode", { o, def -> parseMode(o) ?: def }, DefaultOptions.MODE.value) -> {
|
||||
pinyinList.addAll(surnamePinyin(hans, options))
|
||||
}
|
||||
else -> hans.forEach { char ->
|
||||
when {
|
||||
Pinyin.fromCodePointInternal(char.code) != null -> {
|
||||
// 拼音转换
|
||||
if (nonHans.isNotEmpty()) {
|
||||
pinyinList.add(listOf(nonHans))
|
||||
nonHans = ""
|
||||
}
|
||||
pinyinList.add(singlePinyin(char.toString(), options))
|
||||
}
|
||||
else -> nonHans += char
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nonHans.isNotEmpty()) pinyinList.add(listOf(nonHans))
|
||||
}
|
||||
|
||||
return pinyinList
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较两个汉字转成拼音后的排序顺序, 可以用作默认的拼音排序算法.
|
||||
*
|
||||
* @param hanA 汉字字符串 A.
|
||||
* @param hanB 汉字字符串 B.
|
||||
* @return -1, 0 或 1.
|
||||
*/
|
||||
fun compare(hanA: String, hanB: String): Int {
|
||||
return coerceString(convert(hanA)).compareTo(coerceString(convert(hanB)))
|
||||
}
|
||||
|
||||
fun compact(arr: List<List<String>>) = Util.compact(arr)
|
||||
|
||||
/**
|
||||
* 单字拼音转换.
|
||||
*
|
||||
* @param han 单个汉字.
|
||||
* @param options 选项.
|
||||
* @return 返回拼音列表, 多音字会有多个拼音项.
|
||||
*/
|
||||
private fun singlePinyin(han: String, options: NativeObject): List<String> {
|
||||
require(han.isNotEmpty()) { "Argument han must not be empty" }
|
||||
val hanCode = han.first().code
|
||||
val consulted = Pinyin.fromCodePointInternal(hanCode)
|
||||
val pinyinList = consulted?.split(",") ?: return listOf(han.first().toString())
|
||||
val style = options.inquire("style", { o, def -> parseStyle(o) ?: def }, DefaultOptions.STYLE.value)
|
||||
return when {
|
||||
options.inquire("heteronym", ::coerceBoolean, DefaultOptions.HETERONYM) -> {
|
||||
pinyinList.map { toFixed(it, style) }.distinct()
|
||||
}
|
||||
else -> listOf(toFixed(pinyinList.first(), style))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 单姓处理, 将单个姓氏转换为拼音.
|
||||
*
|
||||
* @param hans 姓氏字符串.
|
||||
* @param options 转换选项 (包括风格、多音字选项等).
|
||||
* @return 转换后的拼音列表.
|
||||
*/
|
||||
private fun singleSurname(hans: String, options: NativeObject): List<List<String>> {
|
||||
val result = mutableListOf<List<String>>()
|
||||
|
||||
// 遍历所有汉字
|
||||
hans.forEach { char ->
|
||||
val word = char.toString()
|
||||
when {
|
||||
// 如果是已知的姓氏, 从预定义数据中获取拼音, 并格式化
|
||||
SURNAME.containsKey(word) -> SURNAME[word]?.map { toFixedList(it, options) }?.let { surnamePinyin ->
|
||||
result.addAll(surnamePinyin)
|
||||
}
|
||||
// 如果是未知姓氏, 则按一般规则转换为拼音
|
||||
else -> result.add(singlePinyin(word, options))
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 姓名转换为拼音.
|
||||
*/
|
||||
fun surnamePinyin(hans: String, options: NativeObject): List<List<String>> {
|
||||
return compoundSurname(hans, options)
|
||||
}
|
||||
|
||||
/**
|
||||
* 复姓处理, 将复姓转换为拼音.
|
||||
*
|
||||
* @param hans 包含复姓的字符串.
|
||||
* @param options 转换选项 (包含拼音风格等).
|
||||
* @return 转换后的拼音列表.
|
||||
*/
|
||||
private fun compoundSurname(hans: String, options: NativeObject): List<List<String>> {
|
||||
val len = hans.length
|
||||
var prefixIndex = 0
|
||||
val result = mutableListOf<List<String>>()
|
||||
|
||||
var i = 0
|
||||
while (i < len) {
|
||||
// 获取两个连续字符, 检测是否是复姓
|
||||
val twoWords = hans.substring(i, (i + 2).coerceAtMost(len))
|
||||
|
||||
if (!COMPOUND_SURNAME.containsKey(twoWords)) {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
// 如果前缀内容存在 (非复姓部分), 处理非复姓部分
|
||||
if (prefixIndex < i) {
|
||||
val nonCompoundPart = hans.substring(prefixIndex, i)
|
||||
result.addAll(singleSurname(nonCompoundPart, options)) // 调用单姓处理函数
|
||||
}
|
||||
|
||||
// 处理复姓部分, 并格式化为指定风格
|
||||
COMPOUND_SURNAME[twoWords]?.map { toFixedList(it, options) }?.let { compoundPart ->
|
||||
result.addAll(compoundPart)
|
||||
}
|
||||
|
||||
// 跳过复姓的两个字
|
||||
i += 2
|
||||
prefixIndex = i
|
||||
}
|
||||
|
||||
// 处理复姓最后剩下的部分 (即非复姓部分)
|
||||
if (prefixIndex < len) {
|
||||
val remaining = hans.substring(prefixIndex, len)
|
||||
result.addAll(singleSurname(remaining, options))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化拼音风格.
|
||||
*
|
||||
* @param pinyin TONE 风格的拼音.
|
||||
* @param style 目标转换的拼音风格.
|
||||
* @return 转换后的拼音.
|
||||
*/
|
||||
internal fun toFixed(pinyin: String, style: Int): String = when (style) {
|
||||
PinyinStyle.NORMAL.value -> pinyin.replace(RE_PHONETIC_SYMBOL) { matchResult ->
|
||||
PHONETIC_SYMBOL[matchResult.groupValues[1]]?.replace(RE_TONE2, "$1") ?: matchResult.value
|
||||
}
|
||||
PinyinStyle.INITIALS.value -> initials(pinyin)
|
||||
PinyinStyle.FIRST_LETTER.value -> pinyin.first().toString()
|
||||
PinyinStyle.TONE.value -> pinyin
|
||||
PinyinStyle.TONE2.value -> {
|
||||
var tone = ""
|
||||
val py = pinyin.replace(RE_PHONETIC_SYMBOL) { matchResult ->
|
||||
PHONETIC_SYMBOL[matchResult.groupValues[1]]?.let { s ->
|
||||
s.replace(RE_TONE2, "$2").also { tone = it }
|
||||
s.replace(RE_TONE2, "$1")
|
||||
} ?: matchResult.value
|
||||
}
|
||||
py + tone
|
||||
}
|
||||
PinyinStyle.TO3NE.value -> pinyin.replace(RE_PHONETIC_SYMBOL) { matchResult ->
|
||||
PHONETIC_SYMBOL[matchResult.groupValues[1]] ?: matchResult.value
|
||||
}
|
||||
else -> pinyin
|
||||
}
|
||||
|
||||
private fun toFixedList(items: List<String>, options: NativeObject): List<String> {
|
||||
val style = options.inquire("style", { o, def -> parseStyle(o) ?: def }, DefaultOptions.STYLE.value)
|
||||
return items.map { toFixed(it, style) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化拼音为声母 (Initials) 形式.
|
||||
*/
|
||||
private fun initials(pinyin: String) = INITIALS.find { pinyin.startsWith(it) } ?: ""
|
||||
|
||||
internal fun parseMode(o: Any): Int? {
|
||||
val obj = o.jsUnwrapped()
|
||||
return when {
|
||||
obj.isJsNullish() -> null
|
||||
obj is PinyinMode -> obj.value
|
||||
obj is Int -> {
|
||||
require(PinyinMode.entries.any { it.value == obj }) { "Invalid mode constant: $obj" }
|
||||
obj
|
||||
}
|
||||
else -> coerceString(obj, "").uppercase().let { name ->
|
||||
PinyinMode.entries.find { it.name == name }?.value ?: throw IllegalArgumentException("Invalid mode name: $obj")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun parseStyle(o: Any): Int? {
|
||||
val obj = o.jsUnwrapped()
|
||||
return when {
|
||||
obj.isJsNullish() -> null
|
||||
obj is PinyinStyle -> obj.value
|
||||
obj is Int -> {
|
||||
require(PinyinStyle.entries.any { it.value == obj }) { "Invalid style constant: $obj" }
|
||||
obj
|
||||
}
|
||||
else -> coerceString(obj, "").uppercase().let { name ->
|
||||
PinyinStyle.entries.find { it.name == name }?.value ?: throw IllegalArgumentException("Invalid style name: $obj")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class PinyinStyle(val value: Int) {
|
||||
NORMAL(0), // 普通风格, 不带声调
|
||||
TONE(1), // 标准风格, 声调在韵母上
|
||||
TONE2(2), // 数字形式标记声调
|
||||
TO3NE(5), // 数字方式, 声母后加声调
|
||||
INITIALS(3), // 仅保留声母
|
||||
FIRST_LETTER(4), // 仅保留首字母
|
||||
}
|
||||
|
||||
@Suppress("SpellCheckingInspection")
|
||||
enum class PinyinMode(val value: Int) {
|
||||
NORMAL(0), // 普通模式
|
||||
SURNAME(1), // 姓氏模式
|
||||
PLACE_NAME(2), // 地名模式
|
||||
PLACENAME(2), // 地名模式 (兼容)
|
||||
}
|
||||
|
||||
object DefaultOptions {
|
||||
val MODE = PinyinMode.NORMAL // 默认模式
|
||||
val STYLE = PinyinStyle.TONE // 默认风格
|
||||
const val SEGMENT: Boolean = false // 是否分词
|
||||
const val HETERONYM: Boolean = false // 是否允许多音字
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package org.autojs.autojs.runtime.api.augment.pinyin
|
||||
|
||||
import org.autojs.autojs.util.RhinoUtils.coerceString
|
||||
|
||||
/**
|
||||
* 工具类 Util, 用于实现拼音转换时辅助功能.
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
object Util {
|
||||
|
||||
/**
|
||||
* 合并二维字符串数组为一维字符串数组.
|
||||
*
|
||||
* @param arr 二维数组, 如 [["zhāo", "cháo"], ["yáng"], ["dōng"], ["shēng"]]
|
||||
* @return 合并后的字符串数组, 一维结果, 如 ["zhāoyáng", "cháoyáng", "dōng", "shēng"]
|
||||
*/
|
||||
fun combo(arr: List<List<String>>): List<String> = when {
|
||||
arr.size <= 1 -> arr.firstOrNull() ?: emptyList()
|
||||
else -> {
|
||||
var result = combo2array(arr[0], arr[1])
|
||||
for (i in 2 until arr.size) {
|
||||
result = combo2array(result, arr[i])
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 组合两个拼音数组为一维字符串数组.
|
||||
*
|
||||
* @param a1 第一个拼音数组, 如 ["zhāo", "cháo"]
|
||||
* @param a2 第二个拼音数组, 如 ["yáng"]
|
||||
* @return 组合后的一维字符串数组, 如 ["zhāoyáng", "cháoyáng"]
|
||||
*/
|
||||
private fun combo2array(a1: List<String>, a2: List<String>): List<String> {
|
||||
return when {
|
||||
a1.isEmpty() -> a2
|
||||
a2.isEmpty() -> a1
|
||||
else -> a1.flatMap { item1 -> a2.map { item2 -> item1 + item2 } }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并多个拼音数组, 形成一个新的二维数组.
|
||||
* 每两个拼音元素形成一个元组数组.
|
||||
*
|
||||
* @param arr 二维数组(每组拼音数组)
|
||||
* @return 合并后的二维拼音数组, 如 [[hai, qian], [huan, qian]]
|
||||
*/
|
||||
fun compact(arr: List<List<String>>): List<List<String>> = when {
|
||||
arr.isEmpty() -> emptyList()
|
||||
arr.size == 1 -> arr.map { listOf(it).flatten() }
|
||||
else -> {
|
||||
var result = compact2array(arr[0], arr[1])
|
||||
for (i in 2 until arr.size) {
|
||||
result = compact2array(result, arr[i])
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 组合两个拼音数组, 形成一个新的二维数组.
|
||||
*
|
||||
* @param a1 第一个拼音数组 eg: ["hai", "huan"] / [["hai"], ["huan"]]
|
||||
* @param a2 第二个拼音数组 eg: ["qian"]
|
||||
* @return 组合后的二维数组, 如 [["hai", "qian"], ["huan", "qian"]]
|
||||
*/
|
||||
private fun compact2array(a1: List<Any>, a2: List<String>): List<List<String>> = when {
|
||||
a1.isEmpty() -> listOf(a2)
|
||||
a2.isEmpty() -> a1.map { item1 ->
|
||||
when (item1) {
|
||||
is List<*> -> item1.map { coerceString(it) }
|
||||
else -> listOf(item1.toString())
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
val result = mutableListOf<List<String>>()
|
||||
a1.forEach { item1 ->
|
||||
val list1 = when (item1) {
|
||||
is List<*> -> item1.map { coerceString(it) }
|
||||
else -> listOf(coerceString(item1))
|
||||
}
|
||||
a2.mapTo(result) { list1 + it }
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
package org.autojs.autojs.runtime.api.augment.pinyin4j
|
||||
|
||||
import net.sourceforge.pinyin4j.PinyinHelper
|
||||
import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType
|
||||
import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType.LOWERCASE
|
||||
import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType.UPPERCASE
|
||||
import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat
|
||||
import net.sourceforge.pinyin4j.format.HanyuPinyinToneType
|
||||
import net.sourceforge.pinyin4j.format.HanyuPinyinToneType.WITHOUT_TONE
|
||||
import net.sourceforge.pinyin4j.format.HanyuPinyinToneType.WITH_TONE_MARK
|
||||
import net.sourceforge.pinyin4j.format.HanyuPinyinToneType.WITH_TONE_NUMBER
|
||||
import net.sourceforge.pinyin4j.format.HanyuPinyinVCharType
|
||||
import net.sourceforge.pinyin4j.format.HanyuPinyinVCharType.WITH_U_AND_COLON
|
||||
import net.sourceforge.pinyin4j.format.HanyuPinyinVCharType.WITH_U_UNICODE
|
||||
import net.sourceforge.pinyin4j.format.HanyuPinyinVCharType.WITH_V
|
||||
import org.autojs.autojs.annotation.RhinoSingletonFunctionInterface
|
||||
import org.autojs.autojs.extension.ScriptableObjectExtensions.inquire
|
||||
import org.autojs.autojs.runtime.api.augment.Augmentable
|
||||
import org.autojs.autojs.runtime.api.augment.Invokable
|
||||
import org.autojs.autojs.util.RhinoUtils.coerceString
|
||||
import org.autojs.autojs.util.RhinoUtils.coerceStringUppercase
|
||||
import org.autojs.autojs.util.RhinoUtils.newNativeObject
|
||||
import org.mozilla.javascript.Context
|
||||
import org.mozilla.javascript.NativeObject
|
||||
|
||||
object Pinyin4j : Augmentable(), Invokable {
|
||||
|
||||
private const val DEFAULT_SEPARATOR = ""
|
||||
|
||||
private const val DEFAULT_CASE_TYPE_STRING = "LOWERCASE"
|
||||
private val DEFAULT_CASE_TYPE = LOWERCASE
|
||||
|
||||
private const val DEFAULT_TONE_TYPE_STRING = "WITHOUT_TONE"
|
||||
private val DEFAULT_TONE_TYPE = WITHOUT_TONE
|
||||
|
||||
private const val DEFAULT_V_CHAR_TYPE_STRING = "WITH_V"
|
||||
private val DEFAULT_V_CHAR_TYPE = WITH_V
|
||||
|
||||
private val pinyinToneMap = mapOf(
|
||||
// 单韵母
|
||||
"a1" to "ā", "a2" to "á", "a3" to "ǎ", "a4" to "à", "a0" to "a",
|
||||
"o1" to "ō", "o2" to "ó", "o3" to "ǒ", "o4" to "ò", "o0" to "o",
|
||||
"e1" to "ē", "e2" to "é", "e3" to "ě", "e4" to "è", "e0" to "e",
|
||||
"i1" to "ī", "i2" to "í", "i3" to "ǐ", "i4" to "ì", "i0" to "i",
|
||||
"u1" to "ū", "u2" to "ú", "u3" to "ǔ", "u4" to "ù", "u0" to "u",
|
||||
"ü1" to "ǖ", "ü2" to "ǘ", "ü3" to "ǚ", "ü4" to "ǜ", "ü0" to "ü",
|
||||
"v1" to "ǖ", "v2" to "ǘ", "v3" to "ǚ", "v4" to "ǜ", "v0" to "ü",
|
||||
"u:1" to "ǖ", "u:2" to "ǘ", "u:3" to "ǚ", "u:4" to "ǜ", "u:0" to "ü",
|
||||
|
||||
// 复韵母 - 普通
|
||||
"ai1" to "āi", "ai2" to "ái", "ai3" to "ǎi", "ai4" to "ài", "ai0" to "ai",
|
||||
"ei1" to "ēi", "ei2" to "éi", "ei3" to "ěi", "ei4" to "èi", "ei0" to "ei",
|
||||
"ui1" to "uī", "ui2" to "uí", "ui3" to "uǐ", "ui4" to "uì", "ui0" to "ui",
|
||||
"ao1" to "āo", "ao2" to "áo", "ao3" to "ǎo", "ao4" to "ào", "ao0" to "ao",
|
||||
"ou1" to "ōu", "ou2" to "óu", "ou3" to "ǒu", "ou4" to "òu", "ou0" to "ou",
|
||||
"iu1" to "iū", "iu2" to "iú", "iu3" to "iǔ", "iu4" to "iù", "iu0" to "iu",
|
||||
"ie1" to "iē", "ie2" to "ié", "ie3" to "iě", "ie4" to "iè", "ie0" to "ie",
|
||||
"üe1" to "üē", "üe2" to "üé", "üe3" to "üě", "üe4" to "üè", "üe0" to "üe",
|
||||
"u:e1" to "üē", "u:e2" to "üé", "u:e3" to "üě", "u:e4" to "üè", "u:e0" to "üe",
|
||||
"ve1" to "üē", "ve2" to "üé", "ve3" to "üě", "ve4" to "üè", "ve0" to "üe",
|
||||
|
||||
// 复韵母 - 特殊
|
||||
"er1" to "ēr", "er2" to "ér", "er3" to "ěr", "er4" to "èr", "er0" to "er",
|
||||
|
||||
// 复韵母 - 前鼻韵母
|
||||
"an1" to "ān", "an2" to "án", "an3" to "ǎn", "an4" to "àn", "an0" to "an",
|
||||
"en1" to "ēn", "en2" to "én", "en3" to "ěn", "en4" to "èn", "en0" to "en",
|
||||
"in1" to "īn", "in2" to "ín", "in3" to "ǐn", "in4" to "ìn", "in0" to "in",
|
||||
"un1" to "ūn", "un2" to "ún", "un3" to "ǔn", "un4" to "ùn", "un0" to "un",
|
||||
"ün1" to "ǖn", "ün2" to "ǘn", "ün3" to "ǚn", "ün4" to "ǜn", "ün0" to "ün",
|
||||
"u:n1" to "ǖn", "u:n2" to "ǘn", "u:n3" to "ǚn", "u:n4" to "ǜn", "u:n0" to "ün",
|
||||
"vn1" to "ǖn", "vn2" to "ǘn", "vn3" to "ǚn", "vn4" to "ǜn", "vn0" to "ün",
|
||||
|
||||
// 复韵母 - 后鼻韵母
|
||||
"ang1" to "āng", "ang2" to "áng", "ang3" to "ǎng", "ang4" to "àng", "ang0" to "ang",
|
||||
"eng1" to "ēng", "eng2" to "éng", "eng3" to "ěng", "eng4" to "èng", "eng0" to "eng",
|
||||
"ing1" to "īng", "ing2" to "íng", "ing3" to "ǐng", "ing4" to "ìng", "ing0" to "ing",
|
||||
"ong1" to "ōng", "ong2" to "óng", "ong3" to "ǒng", "ong4" to "òng", "ong0" to "ong"
|
||||
)
|
||||
|
||||
override val selfAssignmentFunctions = listOf(
|
||||
::`as`.name,
|
||||
::of.name,
|
||||
)
|
||||
|
||||
@RhinoSingletonFunctionInterface
|
||||
override fun invoke(args: Array<out Any?>): String = ensureArgumentsLengthInRange(args, 1..2) { argList ->
|
||||
of(argList)
|
||||
}
|
||||
|
||||
@RhinoSingletonFunctionInterface
|
||||
fun of(args: Array<out Any?>): String = ensureArgumentsLengthInRange(args, 1..2) { argList ->
|
||||
val (string, options) = argList
|
||||
|
||||
val str = coerceString(string, "").takeIf { it.isNotEmpty() } ?: return@ensureArgumentsLengthInRange ""
|
||||
var separator = DEFAULT_SEPARATOR
|
||||
val opt = when (options) {
|
||||
is String -> newNativeObject().also { separator = options }
|
||||
is NativeObject -> options
|
||||
else -> newNativeObject()
|
||||
}
|
||||
|
||||
separator = opt.inquire(listOf("separator", "sep"), ::coerceString, separator)
|
||||
|
||||
val caseType = when (val case = opt.inquire(listOf("case", "caseType"))) {
|
||||
is HanyuPinyinCaseType -> case
|
||||
else -> when (coerceStringUppercase(case, DEFAULT_CASE_TYPE_STRING)) {
|
||||
"LOWERCASE", "LOW", "L", "0" -> LOWERCASE
|
||||
"UPPERCASE", "UP", "U", "1" -> UPPERCASE
|
||||
else -> throw IllegalArgumentException("Invalid case type: ${Context.toString(case)}")
|
||||
}
|
||||
}
|
||||
val toneType = when (val tone = opt.inquire(listOf("tone", "toneType"))) {
|
||||
is HanyuPinyinToneType -> tone
|
||||
else -> when (coerceStringUppercase(tone, DEFAULT_TONE_TYPE_STRING)) {
|
||||
"WITH_TONE_NUMBER", "WITH_NUMBER", "NUMBER", "NUM" -> WITH_TONE_NUMBER
|
||||
"WITHOUT_TONE", "NO_TONE", "NO", "FALSE", "0" -> WITHOUT_TONE
|
||||
"WITH_TONE_MARK", "WITH_MARK", "MARK", "TRUE", "1" -> WITH_TONE_MARK
|
||||
else -> throw IllegalArgumentException("Invalid tone type: ${Context.toString(tone)}")
|
||||
}
|
||||
}
|
||||
val vCharType = when (val vChar = opt.inquire(listOf("v", "vChar", "vCharType"))) {
|
||||
is HanyuPinyinVCharType -> vChar
|
||||
else -> when (coerceStringUppercase(vChar, if (toneType == WITH_TONE_MARK) "WITH_U_UNICODE" else DEFAULT_V_CHAR_TYPE_STRING)) {
|
||||
"WITH_U_AND_COLON", "U_AND_COLON", "U_COLON", "U:" -> WITH_U_AND_COLON
|
||||
"WITH_V", "V" -> WITH_V
|
||||
"WITH_U_UNICODE", "U_UNICODE", "UNICODE", "Ü" -> WITH_U_UNICODE
|
||||
else -> throw IllegalArgumentException("Invalid v char type: ${Context.toString(vChar)}")
|
||||
}
|
||||
}
|
||||
ofRhino(str, separator, caseType, toneType, vCharType)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun ofRhino(
|
||||
str: String,
|
||||
separator: String = DEFAULT_SEPARATOR,
|
||||
caseType: HanyuPinyinCaseType = DEFAULT_CASE_TYPE,
|
||||
toneType: HanyuPinyinToneType = DEFAULT_TONE_TYPE,
|
||||
vCharType: HanyuPinyinVCharType = DEFAULT_V_CHAR_TYPE,
|
||||
): String {
|
||||
val format = HanyuPinyinOutputFormat().also {
|
||||
it.caseType = caseType
|
||||
it.toneType = toneType
|
||||
it.vCharType = vCharType
|
||||
}
|
||||
|
||||
val pinyinResult = StringBuilder()
|
||||
|
||||
str.forEach { ch ->
|
||||
val pinyin4j = when (ch) {
|
||||
in '\u4E00'..'\u9FA5' -> {
|
||||
PinyinHelper.toHanyuPinyinStringArray(ch, format)?.firstOrNull() ?: ""
|
||||
}
|
||||
else -> ch.toString()
|
||||
}
|
||||
pinyinResult.append(pinyin4j).append(separator)
|
||||
}
|
||||
|
||||
return pinyinResult.toString().trim()
|
||||
}
|
||||
|
||||
@RhinoSingletonFunctionInterface
|
||||
fun `as`(args: Array<out Any?>): String = ensureArgumentsOnlyOne(args) { o ->
|
||||
convertToTonePinyin(coerceString(o, ""))
|
||||
}
|
||||
|
||||
private fun convertToTonePinyin(input: String): String {
|
||||
val regex = Regex("(üe|u:e|ve|ue|ang|eng|ing|ong|ai|ei|ui|ao|ou|iu|ie|er|an|en|in|un|ün|u:n|vn|[aoeiuüv])([0-4])")
|
||||
return regex.replace(input) { matchResult ->
|
||||
val pinyinBase = matchResult.groupValues[1]
|
||||
val toneNumber = matchResult.groupValues[2]
|
||||
pinyinToneMap[pinyinBase + toneNumber] ?: matchResult.value
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -161,7 +161,7 @@ class UI(private val scriptRuntime: ScriptRuntime) : AugmentableProxy(scriptRunt
|
||||
val key = coerceString(keyArg)
|
||||
value?.let { mProperties[key] = it } ?: mProperties.remove(key)
|
||||
}, NOT_CONSTRUCTABLE))
|
||||
}, PERMANENT or READONLY)
|
||||
}, READONLY or PERMANENT)
|
||||
}
|
||||
|
||||
@Suppress("SameReturnValue")
|
||||
@@ -599,11 +599,11 @@ class UI(private val scriptRuntime: ScriptRuntime) : AugmentableProxy(scriptRunt
|
||||
private fun initWidgetConstructor(ctor: BaseFunction) {
|
||||
val prototype = ctor.prop("prototype") as? ScriptableObject ?: newNativeObject()
|
||||
assignWidgetProperties(prototype)
|
||||
ctor.defineProp("prototype", prototype, PERMANENT or DONTENUM)
|
||||
ctor.defineProp("prototype", prototype, DONTENUM or PERMANENT)
|
||||
}
|
||||
|
||||
private fun assignWidgetProperties(o: ScriptableObject) {
|
||||
o.defineProperty("__attrs__", newNativeObject(), PERMANENT or DONTENUM)
|
||||
o.defineProperty("__attrs__", newNativeObject(), DONTENUM or PERMANENT)
|
||||
o.defineFunctionProperties(
|
||||
arrayOf(
|
||||
"renderInternal",
|
||||
|
||||
@@ -277,7 +277,7 @@ object Util : Augmentable() {
|
||||
else -> withRhinoContext { context ->
|
||||
val tmp = object : BaseFunction() {
|
||||
override fun call(cx: Context, scope: Scriptable, thisObj: Scriptable?, args: Array<out Any?>) = newNativeObject().also {
|
||||
it.defineProperty("constructor", d, READONLY or PERMANENT or DONTENUM)
|
||||
it.defineProperty("constructor", d, READONLY or DONTENUM or PERMANENT)
|
||||
}
|
||||
}
|
||||
tmp.construct(context, ImporterTopLevel(context), arrayOf()).also { instance ->
|
||||
@@ -718,7 +718,7 @@ object Util : Augmentable() {
|
||||
it.defineFunctionProperties(
|
||||
arrayOf("toString"),
|
||||
RegularFunction::class.java,
|
||||
READONLY or PERMANENT or DONTENUM,
|
||||
READONLY or DONTENUM or PERMANENT,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,9 @@ import androidx.fragment.app.Fragment
|
||||
import androidx.viewpager.widget.ViewPager
|
||||
import androidx.viewpager.widget.ViewPager.SimpleOnPageChangeListener
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import com.huaban.analysis.jieba.CharsDictionaryDatabase
|
||||
import com.huaban.analysis.jieba.PhrasesDictionaryDatabase
|
||||
import com.huaban.analysis.jieba.WordDictionaryDatabase
|
||||
import org.autojs.autojs.AutoJs
|
||||
import org.autojs.autojs.app.FragmentPagerAdapterBuilder
|
||||
import org.autojs.autojs.app.FragmentPagerAdapterBuilder.StoredFragmentPagerAdapter
|
||||
@@ -230,12 +233,20 @@ class MainActivity : BaseActivity(), DelegateHost, HostActivity {
|
||||
|
||||
fun exitCompletely() {
|
||||
FloatyWindowManger.hideCircularMenuAndSaveState()
|
||||
|
||||
stopService(Intent(this, FloatyService::class.java))
|
||||
stopService(Intent(this, ScreenCapturerForegroundService::class.java))
|
||||
|
||||
AutoJs.instance.scriptEngineService.stopAll()
|
||||
|
||||
WordDictionaryDatabase.getInstance(this).close()
|
||||
CharsDictionaryDatabase.getInstance(this).close()
|
||||
PhrasesDictionaryDatabase.getInstance(this).close()
|
||||
|
||||
mA11yTool.stopService(false)
|
||||
mForeGroundService.stopIfNeeded()
|
||||
WrappedShizuku.onDestroy()
|
||||
|
||||
Process.killProcess(Process.myPid())
|
||||
}
|
||||
|
||||
|
||||
@@ -234,8 +234,8 @@ public class TaskListRecyclerView extends ThemeColorRecyclerView {
|
||||
mTask = task;
|
||||
firstCharIconBinding.firstChar
|
||||
.setIcon(AutoFileSource.ENGINE.equals(mTask.getEngineName()) ? FileUtils.TYPE.AUTO.icon : FileUtils.TYPE.JAVASCRIPT.icon)
|
||||
// .setIconTextThemeColor()
|
||||
// .setStrokeThemeColor()
|
||||
.setIconTextThemeColor()
|
||||
.setStrokeThemeColor()
|
||||
.setFillTransparent();
|
||||
itemBinding.name.setText(task.getName());
|
||||
itemBinding.taskListFilePath.setText(task.getDesc());
|
||||
|
||||
@@ -56,7 +56,6 @@ import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -115,8 +114,11 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
|
||||
EditText mOutputPath;
|
||||
EditText mAppName;
|
||||
EditText mPackageName;
|
||||
TextInputLayout mPackageNameParent;
|
||||
EditText mVersionName;
|
||||
TextInputLayout mVersionNameParent;
|
||||
EditText mVersionCode;
|
||||
TextInputLayout mVersionCodeParent;
|
||||
ImageView mIcon;
|
||||
LinearLayout mAppConfig;
|
||||
|
||||
@@ -181,11 +183,15 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
|
||||
}
|
||||
return false;
|
||||
});
|
||||
mPackageNameParent = binding.packageNameParent;
|
||||
|
||||
mVersionName = binding.versionName;
|
||||
mVersionNameParent = binding.versionNameParent;
|
||||
mVersionCode = binding.versionCode;
|
||||
mVersionCodeParent = binding.versionCodeParent;
|
||||
|
||||
mIcon = binding.appIcon;
|
||||
mIcon.setVisibility(View.INVISIBLE);
|
||||
mIcon.setOnClickListener(v -> selectIcon());
|
||||
|
||||
mAppConfig = binding.appConfig;
|
||||
@@ -261,11 +267,11 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
|
||||
NotAskAgainDialog.Builder builder = new NotAskAgainDialog.Builder(context, key);
|
||||
builder.title(R.string.text_prompt);
|
||||
builder.content(getString(R.string.text_unable_to_build_apk_as_autojs6_does_not_include_selected_abi, abiText) + "\n\n" +
|
||||
getString(R.string.text_the_following_solutions_can_be_referred_to) + ":\n\n" +
|
||||
"- " + getString(R.string.text_download_and_install_autojs6_including_above_abi, abiText) + "\n" +
|
||||
"- " + getString(R.string.text_download_and_install_autojs6_including_all_abis) + " [" + getString(R.string.text_recommended) + "]\n\n" +
|
||||
getString(R.string.text_download_link_for_autojs6) + ":\n" +
|
||||
getString(R.string.uri_autojs6_download_link));
|
||||
getString(R.string.text_the_following_solutions_can_be_referred_to) + ":\n\n" +
|
||||
"- " + getString(R.string.text_download_and_install_autojs6_including_above_abi, abiText) + "\n" +
|
||||
"- " + getString(R.string.text_download_and_install_autojs6_including_all_abis) + " [" + getString(R.string.text_recommended) + "]\n\n" +
|
||||
getString(R.string.text_download_link_for_autojs6) + ":\n" +
|
||||
getString(R.string.uri_autojs6_download_link));
|
||||
builder.positiveText(R.string.dialog_button_dismiss);
|
||||
MaterialDialog dialog = builder.show();
|
||||
if (dialog != null) {
|
||||
@@ -360,6 +366,7 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
|
||||
return list.stream().anyMatch(item -> item.equalsIgnoreCase(s));
|
||||
}
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
private void setupWithSourceFile(ScriptFile file) {
|
||||
String dir = file.getParent();
|
||||
if (dir != null && dir.startsWith(getFilesDir().getPath())) {
|
||||
@@ -368,20 +375,42 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
|
||||
mOutputPath.setText(dir);
|
||||
mAppName.setText(file.getSimplifiedName());
|
||||
|
||||
String packageName = getString(R.string.format_default_package_name, generatePackageNameSuffix(file));
|
||||
mPackageName.setText(packageName);
|
||||
Observable.fromCallable(() -> {
|
||||
String packageNameSuffix = generatePackageNameSuffix(file);
|
||||
return getString(R.string.format_default_package_name, packageNameSuffix);
|
||||
})
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(packageName -> {
|
||||
mPackageName.setText(packageName);
|
||||
mPackageNameParent.setHint(R.string.text_package_name);
|
||||
|
||||
SimpleVersionInfo nextVersionInfo = AppUtils.generateNextVersionInfo(packageName);
|
||||
if (nextVersionInfo != null) {
|
||||
mVersionName.setText(nextVersionInfo.versionName);
|
||||
mVersionCode.setText(nextVersionInfo.versionCodeString);
|
||||
}
|
||||
SimpleVersionInfo nextVersionInfo = AppUtils.generateNextVersionInfo(packageName);
|
||||
if (nextVersionInfo != null) {
|
||||
mVersionName.setText(nextVersionInfo.versionName);
|
||||
mVersionCode.setText(nextVersionInfo.versionCodeString);
|
||||
} else {
|
||||
mVersionName.setText(R.string.default_build_apk_version_name);
|
||||
mVersionCode.setText(R.string.default_build_apk_version_code);
|
||||
}
|
||||
mVersionNameParent.setHint(R.string.text_version_name);
|
||||
mVersionCodeParent.setHint(R.string.text_version_code);
|
||||
|
||||
Drawable iconDrawable = AppUtils.getInstalledAppIcon(packageName);
|
||||
if (iconDrawable != null) {
|
||||
mIcon.setImageDrawable(iconDrawable);
|
||||
mIsDefaultIcon = false;
|
||||
}
|
||||
Drawable iconDrawable = AppUtils.getInstalledAppIcon(packageName);
|
||||
if (iconDrawable != null) {
|
||||
mIcon.setImageDrawable(iconDrawable);
|
||||
mIsDefaultIcon = false;
|
||||
}
|
||||
mIcon.setVisibility(View.VISIBLE);
|
||||
}, throwable -> {
|
||||
mPackageName.setText(getString(R.string.format_default_package_name, file.getSimplifiedName().toLowerCase(Language.getPrefLanguage().getLocale())));
|
||||
mVersionName.setText(R.string.default_build_apk_version_name);
|
||||
mVersionCode.setText(R.string.default_build_apk_version_code);
|
||||
mPackageNameParent.setHint(R.string.text_package_name);
|
||||
mVersionNameParent.setHint(R.string.text_version_name);
|
||||
mVersionCodeParent.setHint(R.string.text_version_code);
|
||||
mIcon.setVisibility(View.VISIBLE);
|
||||
});
|
||||
|
||||
setSource(file);
|
||||
}
|
||||
@@ -389,7 +418,8 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
|
||||
private static String generatePackageNameSuffix(ScriptFile file) {
|
||||
String name = file.getSimplifiedName();
|
||||
if (name.matches(".*[\\u4e00-\\u9fff].*")) {
|
||||
name = Pinyin.ofRhino(name);
|
||||
// name = Pinyin4j.ofRhino(name);
|
||||
name = Pinyin.INSTANCE.simpleRhino(name, false, true);
|
||||
}
|
||||
name = name.replaceAll("\\W+", "_");
|
||||
if (name.matches("^\\d.*")) {
|
||||
@@ -461,11 +491,11 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
|
||||
return checkNotEmpty(mOutputPath);
|
||||
}
|
||||
return checkNotEmpty(mSourcePath)
|
||||
& checkNotEmpty(mOutputPath)
|
||||
& checkNotEmpty(mAppName)
|
||||
& checkNotEmpty(mVersionCode)
|
||||
& checkNotEmpty(mVersionName)
|
||||
& checkPackageNameValid(mPackageName);
|
||||
& checkNotEmpty(mOutputPath)
|
||||
& checkNotEmpty(mAppName)
|
||||
& checkNotEmpty(mVersionCode)
|
||||
& checkNotEmpty(mVersionName)
|
||||
& checkPackageNameValid(mPackageName);
|
||||
}
|
||||
|
||||
private boolean checkAbis() {
|
||||
@@ -482,9 +512,8 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
|
||||
|
||||
private boolean checkPackageNameValid(EditText editText) {
|
||||
Editable text = editText.getText();
|
||||
String hint = Objects.requireNonNull(((TextInputLayout) editText.getParent().getParent()).getHint()).toString();
|
||||
if (TextUtils.isEmpty(text)) {
|
||||
editText.setError(hint + getString(R.string.text_should_not_be_empty));
|
||||
editText.setError(getString(R.string.text_should_not_be_empty));
|
||||
return false;
|
||||
}
|
||||
if (!REGEX_PACKAGE_NAME.matcher(text).matches()) {
|
||||
@@ -495,15 +524,11 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
|
||||
}
|
||||
|
||||
private boolean checkNotEmpty(EditText editText) {
|
||||
if (!TextUtils.isEmpty(editText.getText()) || !editText.isShown())
|
||||
return true;
|
||||
// TODO by Stardust on Dec 8, 2017.
|
||||
// ! More beautiful ways?
|
||||
// ! zh-CN (translated by SuperMonster003 on Jul 29, 2024):
|
||||
// ! 更优雅的方式?
|
||||
String hint = Objects.requireNonNull(((TextInputLayout) editText.getParent().getParent()).getHint()).toString();
|
||||
editText.setError(hint + getString(R.string.text_should_not_be_empty));
|
||||
return false;
|
||||
if (TextUtils.isEmpty(editText.getText()) && editText.isShown()) {
|
||||
editText.setError(getString(R.string.text_should_not_be_empty));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void showHintDialogIfNeeded() {
|
||||
@@ -586,18 +611,18 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
|
||||
private boolean isWideCharacter(char c) {
|
||||
Character.UnicodeBlock block = Character.UnicodeBlock.of(c);
|
||||
return block == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS ||
|
||||
block == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A ||
|
||||
block == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B ||
|
||||
block == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS ||
|
||||
block == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS_SUPPLEMENT ||
|
||||
block == Character.UnicodeBlock.HIRAGANA ||
|
||||
block == Character.UnicodeBlock.KATAKANA ||
|
||||
block == Character.UnicodeBlock.HANGUL_SYLLABLES ||
|
||||
block == Character.UnicodeBlock.HANGUL_JAMO ||
|
||||
block == Character.UnicodeBlock.HANGUL_COMPATIBILITY_JAMO ||
|
||||
block == Character.UnicodeBlock.ENCLOSED_CJK_LETTERS_AND_MONTHS ||
|
||||
block == Character.UnicodeBlock.CJK_COMPATIBILITY_FORMS ||
|
||||
block == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS;
|
||||
block == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A ||
|
||||
block == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B ||
|
||||
block == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS ||
|
||||
block == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS_SUPPLEMENT ||
|
||||
block == Character.UnicodeBlock.HIRAGANA ||
|
||||
block == Character.UnicodeBlock.KATAKANA ||
|
||||
block == Character.UnicodeBlock.HANGUL_SYLLABLES ||
|
||||
block == Character.UnicodeBlock.HANGUL_JAMO ||
|
||||
block == Character.UnicodeBlock.HANGUL_COMPATIBILITY_JAMO ||
|
||||
block == Character.UnicodeBlock.ENCLOSED_CJK_LETTERS_AND_MONTHS ||
|
||||
block == Character.UnicodeBlock.CJK_COMPATIBILITY_FORMS ||
|
||||
block == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS;
|
||||
}
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
@@ -156,10 +157,11 @@
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/package_name_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:hint="@string/text_package_name"
|
||||
android:hint="@string/text_package_name_with_computing"
|
||||
app:expandedHintEnabled="false">
|
||||
|
||||
<!-- @Hint by SuperMonster003 on Nov 18, 2024. -->
|
||||
@@ -188,10 +190,11 @@
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/version_name_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:hint="@string/text_version_name"
|
||||
android:hint="@string/text_version_name_with_computing"
|
||||
app:expandedHintEnabled="false">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatEditText
|
||||
@@ -199,15 +202,16 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="text"
|
||||
android:text="1.0.0" />
|
||||
tools:text="1.0.0" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/version_code_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:hint="@string/text_version_code"
|
||||
android:hint="@string/text_version_code_with_computing"
|
||||
app:expandedHintEnabled="false">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatEditText
|
||||
@@ -215,7 +219,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="number"
|
||||
android:text="1" />
|
||||
tools:text="1" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
|
||||
@@ -811,5 +811,8 @@
|
||||
<string name="text_write_secure_settings_description">إعدادات النظام الآمنة ، التي تحتوي على تفضيلات النظام التي يمكن أن تقرأها التطبيقات ولكن لا يُسمح لها بالكتابة.\nهذه هي لتفضيلات يجب على المستخدم تعديلها بشكل صريح من خلال واجهة المستخدم لتطبيق النظام.\nمع إذن إعدادات النظام الآمن ، يمكن للتطبيقات العادية تعديل الإعدادات الآمنة مباشرة (مثل خدمة إمكانية الوصول).</string>
|
||||
<string name="text_write_system_settings">كتابة إعدادات النظام</string>
|
||||
<string name="theme_color_default">تقصير</string>
|
||||
<string name="text_package_name_with_computing" tools:ignore="TypographyEllipsis">اسم الحزمة (جارٍ الحساب...)</string>
|
||||
<string name="text_version_name_with_computing" tools:ignore="TypographyEllipsis">اسم الإصدار (جارٍ الحساب...)</string>
|
||||
<string name="text_version_code_with_computing" tools:ignore="TypographyEllipsis">رمز الإصدار (جارٍ الحساب...)</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -807,5 +807,8 @@
|
||||
<string name="text_write_secure_settings_description">Secure system settings, containing system preferences that applications can read but are not allowed to write.\nThese are for preferences that the user must explicitly modify through the UI of a system app.\nWith secure system settings permission, normal applications can directly modify the secure settings (such as accessibility service).</string>
|
||||
<string name="text_write_system_settings">Write system settings</string>
|
||||
<string name="theme_color_default">Default</string>
|
||||
<string name="text_package_name_with_computing" tools:ignore="TypographyEllipsis">Package name (computing...)</string>
|
||||
<string name="text_version_name_with_computing" tools:ignore="TypographyEllipsis">Version name (computing...)</string>
|
||||
<string name="text_version_code_with_computing" tools:ignore="TypographyEllipsis">Version code (computing...)</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -810,5 +810,8 @@
|
||||
<string name="text_write_secure_settings_description">Ajustes de seguridad del sistema, que contienen preferencias del sistema que las aplicaciones pueden leer pero no pueden escribir.\nSe trata de preferencias que el usuario debe modificar explícitamente a través de la interfaz de usuario de una aplicación del sistema.\nCon el permiso de configuración segura del sistema, las aplicaciones normales pueden modificar directamente la configuración segura (como el servicio de accesibilidad).</string>
|
||||
<string name="text_write_system_settings">Escribir la configuración del sistema</string>
|
||||
<string name="theme_color_default">Por defecto</string>
|
||||
<string name="text_package_name_with_computing" tools:ignore="TypographyEllipsis">Nombre del paquete (Calculando...)</string>
|
||||
<string name="text_version_name_with_computing" tools:ignore="TypographyEllipsis">Nombre de la versión (Calculando...)</string>
|
||||
<string name="text_version_code_with_computing" tools:ignore="TypographyEllipsis">Código de la versión (Calculando...)</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -810,5 +810,8 @@
|
||||
<string name="text_write_secure_settings_description">Paramètres de sécurité du système, contenant les préférences du système que les applications peuvent lire mais ne sont pas autorisées à écrire.\nIl s\'agit des préférences que l\'utilisateur doit explicitement modifier par le biais de l\'interface utilisateur d\'une application système.\nAvec l\'autorisation de paramètres de sécurité du système, les applications normales peuvent directement modifier les paramètres de sécurité (comme le service d\'accessibilité).</string>
|
||||
<string name="text_write_system_settings">Écrire les paramètres système</string>
|
||||
<string name="theme_color_default">Default</string>
|
||||
|
||||
<string name="text_package_name_with_computing" tools:ignore="TypographyEllipsis">Nom du paquet (en cours de calcul...)</string>
|
||||
<string name="text_version_name_with_computing" tools:ignore="TypographyEllipsis">Nom de la version (en cours de calcul...)</string>
|
||||
<string name="text_version_code_with_computing" tools:ignore="TypographyEllipsis">Code de version (en cours de calcul...)</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -810,5 +810,8 @@
|
||||
<string name="text_write_secure_settings_description">アプリケーションが読み取ることはできるが, 書き込むことはできないシステム環境設定を含む, 安全なシステム設定です.\nこれは, ユーザーがシステムアプリのUIを通じて明示的に変更する必要がある環境設定のためのものです.\nセキュアなシステム設定を許可すると, 通常のアプリケーションはセキュアな設定 (アクセシビリティサービスなど) を直接変更できるようになります</string>
|
||||
<string name="text_write_system_settings">システム設定の書き込み</string>
|
||||
<string name="theme_color_default">デフォルト</string>
|
||||
<string name="text_package_name_with_computing" tools:ignore="TypographyEllipsis">パッケージ名 (計算中...)</string>
|
||||
<string name="text_version_name_with_computing" tools:ignore="TypographyEllipsis">バージョン名 (計算中...)</string>
|
||||
<string name="text_version_code_with_computing" tools:ignore="TypographyEllipsis">バージョンコード (計算中...)</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -811,5 +811,8 @@
|
||||
<string name="text_write_secure_settings_description">애플리케이션이 읽을 수 있지만 쓸 수없는 시스템 환경 설정을 포함하는 보안 시스템 설정.\n이들은 사용자가 시스템 앱의 UI를 통해 명시 적으로 수정 해야하는 선호도입니다.\n보안 시스템 설정 권한을 사용하면 일반 애플리케이션이 보안 설정 (예: 접근성 서비스)을 직접 수정할 수 있습니다.</string>
|
||||
<string name="text_write_system_settings">시스템 설정을 작성하십시오</string>
|
||||
<string name="theme_color_default">기본</string>
|
||||
<string name="text_package_name_with_computing" tools:ignore="TypographyEllipsis">패키지 이름 (계산 중...)</string>
|
||||
<string name="text_version_name_with_computing" tools:ignore="TypographyEllipsis">버전 이름 (계산 중...)</string>
|
||||
<string name="text_version_code_with_computing" tools:ignore="TypographyEllipsis">버전 코드 (계산 중...)</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -810,5 +810,8 @@
|
||||
<string name="text_write_secure_settings_description">Настройки безопасности системы, содержащие системные предпочтения, которые приложения могут читать, но не имеют права записывать.\nОни предназначены для параметров, которые пользователь должен явно изменить через пользовательский интерфейс системного приложения.\nПри наличии разрешения на безопасные системные настройки обычные приложения могут напрямую изменять безопасные настройки (например, служба доступности).</string>
|
||||
<string name="text_write_system_settings">Запись системных настроек</string>
|
||||
<string name="theme_color_default">По умолчанию</string>
|
||||
<string name="text_package_name_with_computing" tools:ignore="TypographyEllipsis">Имя пакета (Вычисление...)</string>
|
||||
<string name="text_version_name_with_computing" tools:ignore="TypographyEllipsis">Название версии (Вычисление...)</string>
|
||||
<string name="text_version_code_with_computing" tools:ignore="TypographyEllipsis">Код версии (Вычисление...)</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -809,5 +809,8 @@
|
||||
<string name="text_write_secure_settings_description">安全設置包含應用程序可讀但不可寫入的設置選項, 這些選項只能由 UI 或系統級別應用修改.\n被授予 \"修改安全設置權限\" 後, 普通應用可直接修改上述安全設置 (例如無障礙服務).</string>
|
||||
<string name="text_write_system_settings">修改系統設置</string>
|
||||
<string name="theme_color_default">默認</string>
|
||||
<string name="text_package_name_with_computing" tools:ignore="TypographyEllipsis">包名 (計算中...)</string>
|
||||
<string name="text_version_name_with_computing" tools:ignore="TypographyEllipsis">版本名稱 (計算中...)</string>
|
||||
<string name="text_version_code_with_computing" tools:ignore="TypographyEllipsis">版本號 (計算中...)</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -809,5 +809,8 @@
|
||||
<string name="text_write_secure_settings_description">安全設定包含應用程式可讀但不可寫入的設定選項, 這些選項只能由 UI 或系統級別應用修改.\n被授予 \"修改安全設定許可權\" 後, 普通應用可直接修改上述安全設定 (例如無障礙服務).</string>
|
||||
<string name="text_write_system_settings">修改系統設定</string>
|
||||
<string name="theme_color_default">預設</string>
|
||||
<string name="text_package_name_with_computing" tools:ignore="TypographyEllipsis">包名 (計算中...)</string>
|
||||
<string name="text_version_name_with_computing" tools:ignore="TypographyEllipsis">版本名稱 (計算中...)</string>
|
||||
<string name="text_version_code_with_computing" tools:ignore="TypographyEllipsis">版本號 (計算中...)</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -805,5 +805,8 @@
|
||||
<string name="text_write_secure_settings_description">安全设置包含应用程序可读但不可写入的设置选项, 这些选项只能由 UI 或系统级别应用修改.\n被授予 \"修改安全设置权限\" 后, 普通应用可直接修改上述安全设置 (例如无障碍服务).</string>
|
||||
<string name="text_write_system_settings">修改系统设置</string>
|
||||
<string name="theme_color_default">默认</string>
|
||||
<string name="text_package_name_with_computing" tools:ignore="TypographyEllipsis">包名 (计算中...)</string>
|
||||
<string name="text_version_name_with_computing" tools:ignore="TypographyEllipsis">版本名称 (计算中...)</string>
|
||||
<string name="text_version_code_with_computing" tools:ignore="TypographyEllipsis">版本号 (计算中...)</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -1030,5 +1030,10 @@
|
||||
<string name="value_app_language_english" translatable="false">EN</string>
|
||||
<string name="value_app_language_follow_system" translatable="false">Auto</string>
|
||||
<string name="value_app_language_simplified_chinese" translatable="false">ZH_CN</string>
|
||||
<string name="text_package_name_with_computing" tools:ignore="TypographyEllipsis">Package name (computing...)</string>
|
||||
<string name="text_version_name_with_computing" tools:ignore="TypographyEllipsis">Version name (computing...)</string>
|
||||
<string name="text_version_code_with_computing" tools:ignore="TypographyEllipsis">Version code (computing...)</string>
|
||||
<string name="default_build_apk_version_name" translatable="false">1.0.0</string>
|
||||
<string name="default_build_apk_version_code" translatable="false">1</string>
|
||||
|
||||
</resources>
|
||||
|
||||
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
||||
#Fri Nov 29 20:40:57 CST 2024
|
||||
#Mon Dec 23 16:08:32 CST 2024
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
191
jieba-analysis/LICENSE
Normal file
191
jieba-analysis/LICENSE
Normal file
@@ -0,0 +1,191 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, and
|
||||
distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright
|
||||
owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all other entities
|
||||
that control, are controlled by, or are under common control with that entity.
|
||||
For the purposes of this definition, "control" means (i) the power, direct or
|
||||
indirect, to cause the direction or management of such entity, whether by
|
||||
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity exercising
|
||||
permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications, including
|
||||
but not limited to software source code, documentation source, and configuration
|
||||
files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical transformation or
|
||||
translation of a Source form, including but not limited to compiled object code,
|
||||
generated documentation, and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or Object form, made
|
||||
available under the License, as indicated by a copyright notice that is included
|
||||
in or attached to the work (an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object form, that
|
||||
is based on (or derived from) the Work and for which the editorial revisions,
|
||||
annotations, elaborations, or other modifications represent, as a whole, an
|
||||
original work of authorship. For the purposes of this License, Derivative Works
|
||||
shall not include works that remain separable from, or merely link (or bind by
|
||||
name) to the interfaces of, the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including the original version
|
||||
of the Work and any modifications or additions to that Work or Derivative Works
|
||||
thereof, that is intentionally submitted to Licensor for inclusion in the Work
|
||||
by the copyright owner or by an individual or Legal Entity authorized to submit
|
||||
on behalf of the copyright owner. For the purposes of this definition,
|
||||
"submitted" means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems, and
|
||||
issue tracking systems that are managed by, or on behalf of, the Licensor for
|
||||
the purpose of discussing and improving the Work, but excluding communication
|
||||
that is conspicuously marked or otherwise designated in writing by the copyright
|
||||
owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
|
||||
of whom a Contribution has been received by Licensor and subsequently
|
||||
incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License.
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the Work and such
|
||||
Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License.
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable (except as stated in this section) patent license to make, have
|
||||
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
|
||||
such license applies only to those patent claims licensable by such Contributor
|
||||
that are necessarily infringed by their Contribution(s) alone or by combination
|
||||
of their Contribution(s) with the Work to which such Contribution(s) was
|
||||
submitted. If You institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
|
||||
Contribution incorporated within the Work constitutes direct or contributory
|
||||
patent infringement, then any patent licenses granted to You under this License
|
||||
for that Work shall terminate as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution.
|
||||
|
||||
You may reproduce and distribute copies of the Work or Derivative Works thereof
|
||||
in any medium, with or without modifications, and in Source or Object form,
|
||||
provided that You meet the following conditions:
|
||||
|
||||
You must give any other recipients of the Work or Derivative Works a copy of
|
||||
this License; and
|
||||
You must cause any modified files to carry prominent notices stating that You
|
||||
changed the files; and
|
||||
You must retain, in the Source form of any Derivative Works that You distribute,
|
||||
all copyright, patent, trademark, and attribution notices from the Source form
|
||||
of the Work, excluding those notices that do not pertain to any part of the
|
||||
Derivative Works; and
|
||||
If the Work includes a "NOTICE" text file as part of its distribution, then any
|
||||
Derivative Works that You distribute must include a readable copy of the
|
||||
attribution notices contained within such NOTICE file, excluding those notices
|
||||
that do not pertain to any part of the Derivative Works, in at least one of the
|
||||
following places: within a NOTICE text file distributed as part of the
|
||||
Derivative Works; within the Source form or documentation, if provided along
|
||||
with the Derivative Works; or, within a display generated by the Derivative
|
||||
Works, if and wherever such third-party notices normally appear. The contents of
|
||||
the NOTICE file are for informational purposes only and do not modify the
|
||||
License. You may add Your own attribution notices within Derivative Works that
|
||||
You distribute, alongside or as an addendum to the NOTICE text from the Work,
|
||||
provided that such additional attribution notices cannot be construed as
|
||||
modifying the License.
|
||||
You may add Your own copyright statement to Your modifications and may provide
|
||||
additional or different license terms and conditions for use, reproduction, or
|
||||
distribution of Your modifications, or for any such Derivative Works as a whole,
|
||||
provided Your use, reproduction, and distribution of the Work otherwise complies
|
||||
with the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions.
|
||||
|
||||
Unless You explicitly state otherwise, any Contribution intentionally submitted
|
||||
for inclusion in the Work by You to the Licensor shall be under the terms and
|
||||
conditions of this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify the terms of
|
||||
any separate license agreement you may have executed with Licensor regarding
|
||||
such Contributions.
|
||||
|
||||
6. Trademarks.
|
||||
|
||||
This License does not grant permission to use the trade names, trademarks,
|
||||
service marks, or product names of the Licensor, except as required for
|
||||
reasonable and customary use in describing the origin of the Work and
|
||||
reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty.
|
||||
|
||||
Unless required by applicable law or agreed to in writing, Licensor provides the
|
||||
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
|
||||
including, without limitation, any warranties or conditions of TITLE,
|
||||
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
|
||||
solely responsible for determining the appropriateness of using or
|
||||
redistributing the Work and assume any risks associated with Your exercise of
|
||||
permissions under this License.
|
||||
|
||||
8. Limitation of Liability.
|
||||
|
||||
In no event and under no legal theory, whether in tort (including negligence),
|
||||
contract, or otherwise, unless required by applicable law (such as deliberate
|
||||
and grossly negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special, incidental,
|
||||
or consequential damages of any character arising as a result of this License or
|
||||
out of the use or inability to use the Work (including but not limited to
|
||||
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
|
||||
any and all other commercial damages or losses), even if such Contributor has
|
||||
been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability.
|
||||
|
||||
While redistributing the Work or Derivative Works thereof, You may choose to
|
||||
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
|
||||
other liability obligations and/or rights consistent with this License. However,
|
||||
in accepting such obligations, You may act only on Your own behalf and on Your
|
||||
sole responsibility, not on behalf of any other Contributor, and only if You
|
||||
agree to indemnify, defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason of your
|
||||
accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work
|
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate
|
||||
notice, with the fields enclosed by brackets "[]" replaced with your own
|
||||
identifying information. (Don't include the brackets!) The text should be
|
||||
enclosed in the appropriate comment syntax for the file format. We also
|
||||
recommend that a file or class name and description of purpose be included on
|
||||
the same "printed page" as the copyright notice for easier identification within
|
||||
third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
||||
157
jieba-analysis/README.md
Normal file
157
jieba-analysis/README.md
Normal file
@@ -0,0 +1,157 @@
|
||||
结巴分词(java版) jieba-analysis
|
||||
===============================
|
||||
|
||||
首先感谢jieba分词原作者[fxsjy](https://github.com/fxsjy),没有他的无私贡献,我们也不会结识到结巴
|
||||
分词. 同时也感谢jieba分词java版本的实现团队[huaban](https://github.com/huaban),他们的努力使得Java也能直接做出效果很棒的分词。
|
||||
|
||||
不过由于huaban已经没有再对java版进行维护,所以我自己对项目进行了开发。除了结巴分词(java版)所保留的原项目针对搜索引擎分词的功能(cut~forindex~、cut~forsearch~),我加入了tfidf的关键词提取功能,并且实现的效果和python的jieba版本的效果一模一样!
|
||||
|
||||
|
||||
(以下内容在基于[jieba-java版本README.md](https://github.com/huaban/jieba-analysis])的基础上,加入了对我新加入的tfidf关键词提取模块的相关说明)
|
||||
***
|
||||
|
||||
简介
|
||||
====
|
||||
|
||||
支持分词模式
|
||||
------------
|
||||
|
||||
- Search模式,用于对用户查询词分词
|
||||
- Index模式,用于对索引文档分词
|
||||
|
||||
特性
|
||||
----
|
||||
|
||||
- 支持多种分词模式
|
||||
- 全角统一转成半角
|
||||
- 用户词典功能
|
||||
- conf 目录有整理的搜狗细胞词库
|
||||
- 因为性能原因,最新的快照版本去除词性标注,也希望有更好的 Pull
|
||||
Request 可以提供该功能。
|
||||
|
||||
**新特性:tfidf算法提取关键词**
|
||||
|
||||
```{.java}
|
||||
public static void main(String[] args)
|
||||
{
|
||||
String content="孩子上了幼儿园 安全防拐教育要做好";
|
||||
int topN=5;
|
||||
TFIDFAnalyzer tfidfAnalyzer=new TFIDFAnalyzer();
|
||||
List<Keyword> list=tfidfAnalyzer.analyze(content,topN);
|
||||
for(Keyword word:list)
|
||||
System.out.println(word.getName()+":"+word.getTfidfvalue()+",");
|
||||
// 防拐:0.1992,幼儿园:0.1434,做好:0.1065,教育:0.0946,安全:0.0924
|
||||
}
|
||||
```
|
||||
|
||||
<!-- 如何获取
|
||||
========
|
||||
|
||||
- 当前稳定版本
|
||||
|
||||
``` {.xml}
|
||||
<dependency>
|
||||
<groupId>com.huaban</groupId>
|
||||
<artifactId>jieba-analysis</artifactId>
|
||||
<version>1.0.2</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
- 当前快照版本
|
||||
|
||||
``` {.xml}
|
||||
<dependency>
|
||||
<groupId>com.huaban</groupId>
|
||||
<artifactId>jieba-analysis</artifactId>
|
||||
<version>1.0.3-SNAPSHOT</version>
|
||||
</dependency>
|
||||
``` -->
|
||||
|
||||
如何使用
|
||||
========
|
||||
|
||||
- Demo
|
||||
|
||||
``` {.java}
|
||||
|
||||
@Test
|
||||
public void testDemo() {
|
||||
JiebaSegmenter segmenter = new JiebaSegmenter();
|
||||
String[] sentences =
|
||||
new String[] {"这是一个伸手不见五指的黑夜。我叫孙悟空,我爱北京,我爱Python和C++。", "我不喜欢日本和服。", "雷猴回归人间。",
|
||||
"工信处女干事每月经过下属科室都要亲口交代24口交换机等技术性器件的安装工作", "结果婚的和尚未结过婚的"};
|
||||
for (String sentence : sentences) {
|
||||
System.out.println(segmenter.process(sentence, SegMode.INDEX).toString());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
算法(wiki补充...)
|
||||
=================
|
||||
|
||||
- \[ \] 基于 `trie` 树结构实现高效词图扫描
|
||||
- \[ \] 生成所有切词可能的有向无环图 `DAG`
|
||||
- \[ \] 采用动态规划算法计算最佳切词组合
|
||||
- \[ \] 基于 `HMM` 模型,采用 `Viterbi` (维特比)算法实现未登录词识别
|
||||
|
||||
性能评估
|
||||
========
|
||||
|
||||
- 测试机配置
|
||||
|
||||
``` {.screen}
|
||||
Processor 2 Intel(R) Pentium(R) CPU G620 @ 2.60GHz
|
||||
Memory:8GB
|
||||
|
||||
分词测试时机器开了许多应用(eclipse、emacs、chrome...),可能
|
||||
会影响到测试速度
|
||||
```
|
||||
|
||||
- *测试文本*
|
||||
- 测试结果(单线程,对测试文本逐行分词,并循环调用上万次)
|
||||
|
||||
``` {.screen}
|
||||
循环调用一万次
|
||||
第一次测试结果:
|
||||
time elapsed:12373, rate:2486.986533kb/s, words:917319.94/s
|
||||
第二次测试结果:
|
||||
time elapsed:12284, rate:2505.005241kb/s, words:923966.10/s
|
||||
第三次测试结果:
|
||||
time elapsed:12336, rate:2494.445880kb/s, words:920071.30/s
|
||||
|
||||
循环调用2万次
|
||||
第一次测试结果:
|
||||
time elapsed:22237, rate:2767.593144kb/s, words:1020821.12/s
|
||||
第二次测试结果:
|
||||
time elapsed:22435, rate:2743.167762kb/s, words:1011811.87/s
|
||||
第三次测试结果:
|
||||
time elapsed:22102, rate:2784.497726kb/s, words:1027056.34/s
|
||||
统计结果:词典加载时间1.8s左右,分词效率每秒2Mb多,近100万词。
|
||||
|
||||
2 Processor Intel(R) Core(TM) i3-2100 CPU @ 3.10GHz
|
||||
12G 测试效果
|
||||
time elapsed:19597, rate:3140.428063kb/s, words:1158340.52/s
|
||||
time elapsed:20122, rate:3058.491639kb/s, words:1128118.44/s
|
||||
|
||||
```
|
||||
|
||||
使用本库项目
|
||||
============
|
||||
|
||||
- [analyzer-solr](https://github.com/sing1ee/analyzer-solr) @sing1ee
|
||||
|
||||
许可证
|
||||
======
|
||||
|
||||
jieba(python版本)的许可证为MIT,jieba(java版本)的许可证为ApacheLicence
|
||||
2.0
|
||||
|
||||
``` {.screen}
|
||||
Copyright (C) 2013 Huaban Inc
|
||||
|
||||
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.
|
||||
```
|
||||
59
jieba-analysis/build.gradle
Normal file
59
jieba-analysis/build.gradle
Normal file
@@ -0,0 +1,59 @@
|
||||
plugins {
|
||||
id 'com.android.library' // Android Library 插件
|
||||
id 'kotlin-android' // Kotlin Android 插件
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.huaban.jieba"
|
||||
compileSdk = project.ext.compileSdk
|
||||
|
||||
defaultConfig {
|
||||
minSdk = project.ext.minSdk
|
||||
targetSdk = project.ext.targetSdk
|
||||
|
||||
group = 'com.huaban'
|
||||
versionName = '1.0.3-SNAPSHOT (Optimized for AutoJs6)'
|
||||
description = '结巴分词工具(jieba for java)'
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.toVersion(project.ext.javaVersion)
|
||||
targetCompatibility = JavaVersion.toVersion(project.ext.javaVersion)
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = project.ext.javaVersion
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
// 主 Java/Kotlin 目录
|
||||
java.srcDirs = ['src/main/java']
|
||||
// 将资源文件夹指定为 assets 目录
|
||||
assets.srcDirs = ['src/main/resources']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
google()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// implementation "org.jetbrains.kotlin:kotlin-stdlib:${project.ext.kotlinVersion}"
|
||||
|
||||
implementation 'androidx.appcompat:appcompat:1.7.0'
|
||||
|
||||
implementation 'org.apache.commons:commons-lang3:3.16.0'
|
||||
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
options.encoding = 'UTF-8'
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.huaban.analysis.jieba
|
||||
|
||||
import java.util.regex.Pattern
|
||||
|
||||
object CharacterUtil {
|
||||
|
||||
var reSkip: Pattern = Pattern.compile("(\\d+\\.\\d+|[a-zA-Z0-9]+)")
|
||||
|
||||
private val connectors = charArrayOf('+', '#', '&', '.', '_', '-')
|
||||
|
||||
internal fun isChineseLetter(ch: Char) = ch.code in 0x4E00..0x9FA5
|
||||
|
||||
internal fun isEnglishLetter(ch: Char) = ch.code in 0x0041..0x005A || ch.code in 0x0061..0x007A
|
||||
|
||||
internal fun isDigit(ch: Char) = ch.code in 0x0030..0x0039
|
||||
|
||||
private fun isConnector(ch: Char) = connectors.contains(ch)
|
||||
|
||||
fun ccFind(ch: Char) = isChineseLetter(ch) || isEnglishLetter(ch) || isDigit(ch) || isConnector(ch)
|
||||
|
||||
/**
|
||||
* 全角 to 半角,大写 to 小写
|
||||
*
|
||||
* @param input
|
||||
* 输入字符
|
||||
* @return 转换后的字符
|
||||
*/
|
||||
fun regularize(input: Char) = when (input.code) {
|
||||
12288 -> 32.toChar()
|
||||
in 65281..65374 -> (input.code - 65248).toChar()
|
||||
in 'A'.code..'Z'.code -> input + 32
|
||||
else -> input
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.huaban.analysis.jieba
|
||||
|
||||
import android.content.Context
|
||||
|
||||
open class CharsDictionaryDatabase private constructor(context: Context) : DictionaryDatabase(context) {
|
||||
|
||||
override val databaseName = "dict-chinese-chars.db"
|
||||
|
||||
companion object {
|
||||
|
||||
@Volatile
|
||||
private var instance: CharsDictionaryDatabase? = null
|
||||
|
||||
fun getInstance(context: Context): CharsDictionaryDatabase = instance ?: synchronized(this) {
|
||||
instance ?: CharsDictionaryDatabase(context.applicationContext).also { instance = it }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
package com.huaban.analysis.jieba
|
||||
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
/**
|
||||
* 词典树分段,表示词典树的一个分枝
|
||||
*/
|
||||
internal class DictSegment(
|
||||
// 当前节点上存储的字符
|
||||
private val nodeChar: Char,
|
||||
) : Comparable<DictSegment> {
|
||||
|
||||
private val childrenSegment: MutableMap<Char, DictSegment> by lazy { ConcurrentHashMap() }
|
||||
|
||||
// 数组方式存储结构
|
||||
private var childrenArray: Array<DictSegment?>? = null
|
||||
|
||||
// 当前节点存储的 Segment 数目
|
||||
// storeSize <= ARRAY_LENGTH_LIMIT, 使用数组存储
|
||||
// storeSize > ARRAY_LENGTH_LIMIT, 则使用 Map 存储
|
||||
private var storeSize = 0
|
||||
|
||||
// 当前 DictSegment 状态
|
||||
// 默认 0, 1 表示从根节点到当前节点的路径表示一个词
|
||||
private var nodeState = 0
|
||||
|
||||
// 判断是否有下一个节点
|
||||
private fun hasNextNode() = this.storeSize > 0
|
||||
|
||||
/**
|
||||
* 匹配词段
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun match(charArray: CharArray, begin: Int = 0, length: Int = charArray.size, searchHit: Hit? = null): Hit {
|
||||
var niceSearchHit = searchHit
|
||||
if (niceSearchHit == null) {
|
||||
// 如果hit为空,新建
|
||||
niceSearchHit = Hit()
|
||||
// 设置hit的其实文本位置
|
||||
niceSearchHit.begin = begin
|
||||
} else {
|
||||
// 否则要将HIT状态重置
|
||||
niceSearchHit.setUnmatch()
|
||||
}
|
||||
// 设置hit的当前处理位置
|
||||
niceSearchHit.end = begin
|
||||
|
||||
val keyChar = charArray[begin]
|
||||
var ds: DictSegment? = null
|
||||
|
||||
// 引用实例变量为本地变量,避免查询时遇到更新的同步问题
|
||||
val segmentArray = this.childrenArray
|
||||
val segmentMap: Map<Char, DictSegment> = this.childrenSegment
|
||||
|
||||
// STEP1 在节点中查找keyChar对应的DictSegment
|
||||
if (segmentArray != null) {
|
||||
// 在数组中查找
|
||||
val keySegment = DictSegment(keyChar)
|
||||
val position = Arrays.binarySearch(segmentArray, 0, this.storeSize, keySegment)
|
||||
if (position >= 0) {
|
||||
ds = segmentArray[position]
|
||||
}
|
||||
} else {
|
||||
// 在map中查找
|
||||
ds = segmentMap[keyChar]
|
||||
}
|
||||
|
||||
// STEP2 找到DictSegment,判断词的匹配状态,是否继续递归,还是返回结果
|
||||
if (ds != null) {
|
||||
if (length > 1) {
|
||||
// 词未匹配完,继续往下搜索
|
||||
return ds.match(charArray, begin + 1, length - 1, niceSearchHit)
|
||||
} else if (length == 1) {
|
||||
// 搜索最后一个char
|
||||
|
||||
if (ds.nodeState == 1) {
|
||||
// 添加HIT状态为完全匹配
|
||||
niceSearchHit.setMatch()
|
||||
}
|
||||
if (ds.hasNextNode()) {
|
||||
// 添加HIT状态为前缀匹配
|
||||
niceSearchHit.setPrefix()
|
||||
// 记录当前位置的DictSegment
|
||||
niceSearchHit.matchedDictSegment = ds
|
||||
}
|
||||
return niceSearchHit
|
||||
}
|
||||
}
|
||||
// STEP3 没有找到DictSegment, 将HIT设置为不匹配
|
||||
return niceSearchHit
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载填充词典片段
|
||||
*/
|
||||
private fun fillSegment(charArray: CharArray) {
|
||||
this.fillSegment(charArray, 0, charArray.size, 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* 屏蔽词典中的一个词
|
||||
*/
|
||||
@Suppress("unused")
|
||||
fun disableSegment(charArray: CharArray) {
|
||||
this.fillSegment(charArray, 0, charArray.size, 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载填充词典片段
|
||||
*/
|
||||
@Synchronized
|
||||
private fun fillSegment(charArray: CharArray, begin: Int, length: Int, enabled: Int) {
|
||||
// 获取字典表中的汉字对象
|
||||
val beginChar = charArray[begin]
|
||||
var keyChar = charMap[beginChar]
|
||||
// 字典中没有该字,则将其添加入字典
|
||||
if (keyChar == null) {
|
||||
charMap[beginChar] = beginChar
|
||||
keyChar = beginChar
|
||||
}
|
||||
|
||||
// 搜索当前节点的存储,查询对应keyChar的keyChar,如果没有则创建
|
||||
val ds = lookforSegment(keyChar, enabled)
|
||||
if (ds != null) {
|
||||
// 处理keyChar对应的segment
|
||||
if (length > 1) {
|
||||
// 词元还没有完全加入词典树
|
||||
ds.fillSegment(charArray, begin + 1, length - 1, enabled)
|
||||
} else if (length == 1) {
|
||||
// 已经是词元的最后一个char,设置当前节点状态为enabled,
|
||||
// enabled=1表明一个完整的词,enabled=0表示从词典中屏蔽当前词
|
||||
ds.nodeState = enabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找本节点下对应的keyChar的segment *
|
||||
*
|
||||
* @param keyChar
|
||||
* @param create
|
||||
* =1如果没有找到,则创建新的segment ; =0如果没有找到,不创建,返回null
|
||||
* @return
|
||||
*/
|
||||
private fun lookforSegment(keyChar: Char, create: Int): DictSegment? {
|
||||
var ds: DictSegment? = null
|
||||
|
||||
if (this.storeSize <= ARRAY_LENGTH_LIMIT) {
|
||||
// 获取数组容器,如果数组未创建则创建数组
|
||||
val segmentArray = getChildrenArray()
|
||||
// 搜寻数组
|
||||
val keySegment = DictSegment(keyChar)
|
||||
val position = Arrays.binarySearch(segmentArray, 0, this.storeSize, keySegment)
|
||||
if (position >= 0) {
|
||||
ds = segmentArray[position]
|
||||
}
|
||||
|
||||
// 遍历数组后没有找到对应的segment
|
||||
if (ds == null && create == 1) {
|
||||
ds = keySegment
|
||||
if (this.storeSize < ARRAY_LENGTH_LIMIT) {
|
||||
// 数组容量未满,使用数组存储
|
||||
segmentArray[storeSize] = ds
|
||||
// segment数目+1
|
||||
storeSize++
|
||||
Arrays.sort(segmentArray, 0, this.storeSize)
|
||||
} else {
|
||||
// 数组容量已满,切换Map存储
|
||||
// 获取Map容器,如果Map未创建,则创建Map
|
||||
val segmentMap = getChildrenMap()
|
||||
// 将数组中的segment迁移到Map中
|
||||
migrate(segmentArray, segmentMap)
|
||||
// 存储新的segment
|
||||
segmentMap[keyChar] = ds
|
||||
// segment数目+1 , 必须在释放数组前执行storeSize++ , 确保极端情况下,不会取到空的数组
|
||||
storeSize++
|
||||
// 释放当前的数组引用
|
||||
this.childrenArray = null
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 获取Map容器,如果Map未创建,则创建Map
|
||||
val segmentMap = getChildrenMap()
|
||||
// 搜索Map
|
||||
ds = segmentMap[keyChar]
|
||||
if (ds == null && create == 1) {
|
||||
// 构造新的segment
|
||||
ds = DictSegment(keyChar)
|
||||
segmentMap[keyChar] = ds
|
||||
// 当前节点存储segment数目+1
|
||||
storeSize++
|
||||
}
|
||||
}
|
||||
|
||||
return ds
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数组容器 线程同步方法
|
||||
*/
|
||||
private fun getChildrenArray(): Array<DictSegment?> {
|
||||
if (this.childrenArray == null) {
|
||||
synchronized(this) {
|
||||
if (this.childrenArray == null) {
|
||||
this.childrenArray = arrayOfNulls(ARRAY_LENGTH_LIMIT)
|
||||
}
|
||||
}
|
||||
}
|
||||
return childrenArray!!
|
||||
}
|
||||
|
||||
private fun getChildrenMap(): MutableMap<Char, DictSegment> {
|
||||
return childrenSegment
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数组中的segment迁移到Map中以支持高性能存储
|
||||
*/
|
||||
private fun migrate(segmentArray: Array<DictSegment?>, segmentMap: MutableMap<Char, DictSegment>) {
|
||||
for (segment in segmentArray) {
|
||||
if (segment != null) {
|
||||
segmentMap[segment.nodeChar] = segment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 实现Comparable接口
|
||||
*/
|
||||
override fun compareTo(other: DictSegment): Int {
|
||||
// 对当前节点存储的char进行比较
|
||||
return nodeChar.compareTo(other.nodeChar)
|
||||
}
|
||||
|
||||
fun fillSegments(words: List<String>) {
|
||||
for (word in words) {
|
||||
fillSegment(word.toCharArray())
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
// 公用字典表,存储汉字
|
||||
private val charMap: MutableMap<Char, Char> = HashMap(16, 0.95f)
|
||||
|
||||
// 数组大小上限
|
||||
private const val ARRAY_LENGTH_LIMIT = 3
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.huaban.analysis.jieba
|
||||
|
||||
import android.content.Context
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import java.io.Closeable
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.InputStream
|
||||
import java.security.MessageDigest
|
||||
import java.util.zip.GZIPInputStream
|
||||
|
||||
abstract class DictionaryDatabase internal constructor(context: Context): Closeable {
|
||||
|
||||
abstract val databaseName: String
|
||||
|
||||
private var shouldForciblyCopyDatabase = false
|
||||
|
||||
private val compressedDatabaseName: String
|
||||
get() = "$databaseName.gzip"
|
||||
|
||||
private val md5Key: String
|
||||
get() = "database_md5_$databaseName"
|
||||
|
||||
val database: SQLiteDatabase by lazy {
|
||||
copyDatabaseWithCompressionAndMd5(context)
|
||||
SQLiteDatabase.openDatabase(context.getDatabasePath(databaseName).absolutePath, null, SQLiteDatabase.OPEN_READONLY)
|
||||
}
|
||||
|
||||
override fun close() = database.close()
|
||||
|
||||
private fun copyDatabaseWithCompressionAndMd5(context: Context) {
|
||||
val dbFile = File(context.getDatabasePath(databaseName).path)
|
||||
val prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
|
||||
val storedMd5 = prefs.getString(md5Key, null)
|
||||
val currentMd5 by lazy { dbFile.md5() }
|
||||
|
||||
// 如果数据库文件存在,检查 MD5
|
||||
if (dbFile.exists()) {
|
||||
if (storedMd5 != null && storedMd5 == currentMd5) {
|
||||
if (!shouldForciblyCopyDatabase) return // 文件有效,无需复制或解压
|
||||
}
|
||||
}
|
||||
|
||||
// 数据文件不存在或 MD5 不匹配,重新复制
|
||||
dbFile.parentFile?.mkdirs()
|
||||
context.assets.open(compressedDatabaseName).use { compressedInputStream ->
|
||||
GZIPInputStream(compressedInputStream).use { gzipStream -> // 解压
|
||||
FileOutputStream(dbFile).use { outputStream ->
|
||||
gzipStream.copyTo(outputStream)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 保存 MD5 值
|
||||
prefs.edit().putString(md5Key, currentMd5).apply()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val PREF_NAME = "dict_prefs"
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算文件的 MD5
|
||||
*/
|
||||
private fun File.md5(): String = inputStream().use { it.md5() }
|
||||
|
||||
private fun InputStream.md5(): String {
|
||||
val md = MessageDigest.getInstance("MD5")
|
||||
val buffer = ByteArray(1024)
|
||||
var read: Int
|
||||
while (this.read(buffer).also { read = it } != -1) {
|
||||
md.update(buffer, 0, read)
|
||||
}
|
||||
val digest = md.digest()
|
||||
return digest.joinToString("") { "%02x".format(it) }
|
||||
}
|
||||
|
||||
}
|
||||
117
jieba-analysis/src/main/java/com/huaban/analysis/jieba/Hit.java
Normal file
117
jieba-analysis/src/main/java/com/huaban/analysis/jieba/Hit.java
Normal file
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
*
|
||||
* IK 中文分词 版本 5.0
|
||||
* IK Analyzer release 5.0
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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.
|
||||
*
|
||||
* 源代码由林良益(linliangyi2005@gmail.com)提供
|
||||
* 版权声明 2012,乌龙茶工作室
|
||||
* provided by Linliangyi and copyright 2012 by Oolong studio
|
||||
*
|
||||
*/
|
||||
package com.huaban.analysis.jieba;
|
||||
|
||||
/**
|
||||
* 表示一次词典匹配的命中
|
||||
*/
|
||||
public class Hit {
|
||||
//Hit不匹配
|
||||
private static final int UNMATCH = 0x00000000;
|
||||
//Hit完全匹配
|
||||
private static final int MATCH = 0x00000001;
|
||||
//Hit前缀匹配
|
||||
private static final int PREFIX = 0x00000010;
|
||||
|
||||
|
||||
//该HIT当前状态,默认未匹配
|
||||
private int hitState = UNMATCH;
|
||||
|
||||
//记录词典匹配过程中,当前匹配到的词典分支节点
|
||||
private DictSegment matchedDictSegment;
|
||||
/*
|
||||
* 词段开始位置
|
||||
*/
|
||||
private int begin;
|
||||
/*
|
||||
* 词段的结束位置
|
||||
*/
|
||||
private int end;
|
||||
|
||||
|
||||
/**
|
||||
* 判断是否完全匹配
|
||||
*/
|
||||
public boolean isMatch() {
|
||||
return (this.hitState & MATCH) > 0;
|
||||
}
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public void setMatch() {
|
||||
this.hitState = this.hitState | MATCH;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否是词的前缀
|
||||
*/
|
||||
public boolean isPrefix() {
|
||||
return (this.hitState & PREFIX) > 0;
|
||||
}
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public void setPrefix() {
|
||||
this.hitState = this.hitState | PREFIX;
|
||||
}
|
||||
/**
|
||||
* 判断是否是不匹配
|
||||
*/
|
||||
public boolean isUnmatch() {
|
||||
return this.hitState == UNMATCH ;
|
||||
}
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public void setUnmatch() {
|
||||
this.hitState = UNMATCH;
|
||||
}
|
||||
|
||||
public DictSegment getMatchedDictSegment() {
|
||||
return matchedDictSegment;
|
||||
}
|
||||
|
||||
public void setMatchedDictSegment(DictSegment matchedDictSegment) {
|
||||
this.matchedDictSegment = matchedDictSegment;
|
||||
}
|
||||
|
||||
public int getBegin() {
|
||||
return begin;
|
||||
}
|
||||
|
||||
public void setBegin(int begin) {
|
||||
this.begin = begin;
|
||||
}
|
||||
|
||||
public int getEnd() {
|
||||
return end;
|
||||
}
|
||||
|
||||
public void setEnd(int end) {
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
package com.huaban.analysis.jieba
|
||||
|
||||
import android.content.Context
|
||||
import com.huaban.analysis.jieba.viterbi.FinalSeg
|
||||
import java.util.*
|
||||
|
||||
@Suppress("LocalVariableName")
|
||||
class JiebaSegmenter(context: Context) {
|
||||
|
||||
val dictionary by lazy { WordDictionary.getInstance(context) }
|
||||
|
||||
private val finalSeg = FinalSeg.instance
|
||||
|
||||
enum class SegMode { INDEX, SEARCH }
|
||||
|
||||
private fun createDAG(sentence: String): Map<Int, MutableList<Int>> {
|
||||
val dag = HashMap<Int, MutableList<Int>>()
|
||||
val trie = dictionary.trie
|
||||
val chars: CharArray = sentence.toCharArray()
|
||||
val N = chars.size
|
||||
var i = 0
|
||||
var j = 0
|
||||
while (i < N) {
|
||||
val hit = trie.match(chars, i, j - i + 1)
|
||||
if (hit.isPrefix || hit.isMatch) {
|
||||
if (hit.isMatch) {
|
||||
if (!dag.containsKey(i)) {
|
||||
val value: MutableList<Int> = ArrayList()
|
||||
dag[i] = value
|
||||
value.add(j)
|
||||
} else dag[i]!!.add(j)
|
||||
}
|
||||
j += 1
|
||||
if (j >= N) {
|
||||
i += 1
|
||||
j = i
|
||||
}
|
||||
} else {
|
||||
i += 1
|
||||
j = i
|
||||
}
|
||||
}
|
||||
i = 0
|
||||
while (i < N) {
|
||||
if (!dag.containsKey(i)) {
|
||||
val value: MutableList<Int> = ArrayList()
|
||||
value.add(i)
|
||||
dag[i] = value
|
||||
}
|
||||
++i
|
||||
}
|
||||
return dag
|
||||
}
|
||||
|
||||
private fun calc(sentence: String, dag: Map<Int, MutableList<Int>>): Map<Int, Pair<Int>?> {
|
||||
val N = sentence.length
|
||||
val route = HashMap<Int, Pair<Int>?>()
|
||||
route[N] = Pair(0, 0.0)
|
||||
for (i in N - 1 downTo -1 + 1) {
|
||||
var candidate: Pair<Int>? = null
|
||||
for (x in dag[i]!!) {
|
||||
val freq = dictionary.getFreq(sentence.substring(i, x + 1)) + route[x + 1]!!.freq
|
||||
if (null == candidate) {
|
||||
candidate = Pair(x, freq)
|
||||
} else if (candidate.freq < freq) {
|
||||
candidate.freq = freq
|
||||
candidate.key = x
|
||||
}
|
||||
}
|
||||
route[i] = candidate
|
||||
}
|
||||
return route
|
||||
}
|
||||
|
||||
fun process(paragraph: String, mode: SegMode): List<SegToken> {
|
||||
val tokens: MutableList<SegToken> = ArrayList()
|
||||
var sb = StringBuilder()
|
||||
var offset = 0
|
||||
for (i in paragraph.indices) {
|
||||
val ch = CharacterUtil.regularize(paragraph[i])
|
||||
when {
|
||||
CharacterUtil.ccFind(ch) -> sb.append(ch)
|
||||
else -> {
|
||||
if (sb.isNotEmpty()) {
|
||||
// process
|
||||
when (mode) {
|
||||
SegMode.SEARCH -> {
|
||||
for (word in sentenceProcess(sb.toString())) {
|
||||
tokens.add(SegToken(word, offset, word.length.let { offset += it; offset }))
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
for (token in sentenceProcess(sb.toString())) {
|
||||
if (token.length > 2) {
|
||||
var gram2: String?
|
||||
var j = 0
|
||||
while (j < token.length - 1) {
|
||||
gram2 = token.substring(j, j + 2)
|
||||
if (dictionary.containsWord(gram2)) tokens.add(SegToken(gram2, offset + j, offset + j + 2))
|
||||
++j
|
||||
}
|
||||
}
|
||||
if (token.length > 3) {
|
||||
var gram3: String?
|
||||
var j = 0
|
||||
while (j < token.length - 2) {
|
||||
gram3 = token.substring(j, j + 3)
|
||||
if (dictionary.containsWord(gram3)) tokens.add(SegToken(gram3, offset + j, offset + j + 3))
|
||||
++j
|
||||
}
|
||||
}
|
||||
tokens.add(SegToken(token, offset, token.length.let { offset += it; offset }))
|
||||
}
|
||||
}
|
||||
}
|
||||
sb = StringBuilder()
|
||||
offset = i
|
||||
}
|
||||
if (dictionary.containsWord(paragraph.substring(i, i + 1))) tokens.add(SegToken(paragraph.substring(i, i + 1), offset, ++offset))
|
||||
else tokens.add(SegToken(paragraph.substring(i, i + 1), offset, ++offset))
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sb.isNotEmpty()) when (mode) {
|
||||
SegMode.SEARCH -> {
|
||||
sentenceProcess(sb.toString()).mapTo(tokens) { token -> SegToken(token, offset, token.length.let { offset += it; offset }) }
|
||||
}
|
||||
else -> sentenceProcess(sb.toString()).forEach { token ->
|
||||
if (token.length > 2) {
|
||||
var gram2: String?
|
||||
var j = 0
|
||||
while (j < token.length - 1) {
|
||||
gram2 = token.substring(j, j + 2)
|
||||
if (dictionary.containsWord(gram2)) tokens.add(SegToken(gram2, offset + j, offset + j + 2))
|
||||
++j
|
||||
}
|
||||
}
|
||||
if (token.length > 3) {
|
||||
var gram3: String?
|
||||
var j = 0
|
||||
while (j < token.length - 2) {
|
||||
gram3 = token.substring(j, j + 3)
|
||||
if (dictionary.containsWord(gram3)) tokens.add(SegToken(gram3, offset + j, offset + j + 3))
|
||||
++j
|
||||
}
|
||||
}
|
||||
tokens.add(SegToken(token, offset, token.length.let { offset += it; offset }))
|
||||
}
|
||||
}
|
||||
|
||||
return tokens
|
||||
}
|
||||
|
||||
private fun sentenceProcess(sentence: String): List<String> {
|
||||
val tokens: MutableList<String> = ArrayList()
|
||||
val N = sentence.length
|
||||
val dag = createDAG(sentence)
|
||||
val route = calc(sentence, dag)
|
||||
|
||||
var x = 0
|
||||
var y: Int
|
||||
var buf: String
|
||||
var sb = StringBuilder()
|
||||
while (x < N) {
|
||||
y = route[x]!!.key + 1
|
||||
val lWord: String = sentence.substring(x, y)
|
||||
when {
|
||||
y - x == 1 -> sb.append(lWord)
|
||||
else -> {
|
||||
if (sb.isNotEmpty()) {
|
||||
buf = sb.toString()
|
||||
sb = StringBuilder()
|
||||
when (buf.length) {
|
||||
1 -> tokens.add(buf)
|
||||
else -> when {
|
||||
dictionary.containsWord(buf) -> tokens.add(buf)
|
||||
else -> finalSeg.cut(buf, tokens)
|
||||
}
|
||||
}
|
||||
}
|
||||
tokens.add(lWord)
|
||||
}
|
||||
}
|
||||
x = y
|
||||
}
|
||||
buf = sb.toString()
|
||||
if (buf.isNotEmpty()) {
|
||||
when (buf.length) {
|
||||
1 -> tokens.add(buf)
|
||||
else -> when {
|
||||
dictionary.containsWord(buf) -> tokens.add(buf)
|
||||
else -> finalSeg.cut(buf, tokens)
|
||||
}
|
||||
}
|
||||
}
|
||||
return tokens
|
||||
}
|
||||
|
||||
fun cutSmall(hans: String, limit: Int): List<String> = when {
|
||||
hans.isEmpty() || limit <= 0 -> emptyList()
|
||||
else -> process(hans, SegMode.SEARCH)
|
||||
.map { token -> token.word }
|
||||
.flatMap { word ->
|
||||
when {
|
||||
word.length > limit -> word.chunked(limit)
|
||||
else -> listOf(word)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.huaban.analysis.jieba;
|
||||
|
||||
/**
|
||||
* @description: enable output content to be controlled by switch
|
||||
* @author: sharkdoodoo@foxmail.com
|
||||
* @date: 2022/6/21
|
||||
*/
|
||||
public class Log {
|
||||
|
||||
private static final boolean LOG_ENABLE = Boolean.parseBoolean(System.getProperty("jieba.log.enable", "true"));
|
||||
|
||||
public static final void debug(String debugInfo) {
|
||||
if (LOG_ENABLE) {
|
||||
System.out.println(debugInfo);
|
||||
}
|
||||
}
|
||||
|
||||
public static final void error(String errorInfo) {
|
||||
if (LOG_ENABLE) {
|
||||
System.err.println(errorInfo);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.huaban.analysis.jieba;
|
||||
|
||||
public class Node {
|
||||
|
||||
public Character value;
|
||||
public Node parent;
|
||||
|
||||
public Node(Character value, Node parent) {
|
||||
this.value = value;
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.huaban.analysis.jieba;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class Pair<K> {
|
||||
public K key;
|
||||
public Double freq;
|
||||
|
||||
public Pair(K key, double freq) {
|
||||
this.key = key;
|
||||
this.freq = freq;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Candidate [key=" + key + ", freq=" + freq + "]";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.huaban.analysis.jieba
|
||||
|
||||
import android.content.Context
|
||||
|
||||
open class PhrasesDictionaryDatabase private constructor(context: Context): DictionaryDatabase(context) {
|
||||
|
||||
override val databaseName = "dict-chinese-phrases.db"
|
||||
|
||||
companion object {
|
||||
|
||||
@Volatile
|
||||
private var instance: PhrasesDictionaryDatabase? = null
|
||||
|
||||
fun getInstance(context: Context): PhrasesDictionaryDatabase = instance ?: synchronized(this) {
|
||||
instance ?: PhrasesDictionaryDatabase(context.applicationContext).also { instance = it }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.huaban.analysis.jieba;
|
||||
|
||||
public class SegToken {
|
||||
|
||||
public String word;
|
||||
|
||||
public int startOffset;
|
||||
|
||||
public int endOffset;
|
||||
|
||||
public SegToken(String word, int startOffset, int endOffset) {
|
||||
this.word = word;
|
||||
this.startOffset = startOffset;
|
||||
this.endOffset = endOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[" + word + ", " + startOffset + ", " + endOffset + "]";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.huaban.analysis.jieba
|
||||
|
||||
import android.content.Context
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
class WordDictionary(context: Context) {
|
||||
|
||||
private val db: SQLiteDatabase by lazy { WordDictionaryDatabase.getInstance(context).database }
|
||||
private var minFreq by Delegates.notNull<Double>()
|
||||
|
||||
internal var trie = DictSegment(0.toChar())
|
||||
private set
|
||||
|
||||
init {
|
||||
db.rawQuery("SELECT value FROM metadata WHERE `key` = 'min_freq' LIMIT 1", null).use {
|
||||
minFreq = if (it.moveToFirst()) it.getDouble(0) else Double.MAX_VALUE
|
||||
}
|
||||
// db.rawQuery("SELECT word FROM dictionary", null).use { cursor ->
|
||||
// trie.fillSegments(generateSequence {
|
||||
// if (cursor.moveToNext()) cursor.getString(cursor.getColumnIndexOrThrow("word")) else null
|
||||
// })
|
||||
// }
|
||||
loadTrieConcurrently()
|
||||
}
|
||||
|
||||
private fun loadTrieConcurrently() = runBlocking {
|
||||
val chunkSize = 10000
|
||||
val deferredResults = mutableListOf<Deferred<Unit>>()
|
||||
|
||||
db.rawQuery("SELECT word FROM dictionary", null).use { cursor ->
|
||||
val words = mutableListOf<String>()
|
||||
while (cursor.moveToNext()) {
|
||||
val word = cursor.getString(cursor.getColumnIndexOrThrow("word"))
|
||||
words.add(word)
|
||||
if (words.size >= chunkSize) {
|
||||
val chunk = words.toList()
|
||||
words.clear()
|
||||
deferredResults.add(async(Dispatchers.Default) {
|
||||
trie.fillSegments(chunk)
|
||||
})
|
||||
}
|
||||
}
|
||||
// 最后一块数据
|
||||
if (words.isNotEmpty()) {
|
||||
val chunk = words.toList()
|
||||
deferredResults.add(async(Dispatchers.Default) {
|
||||
trie.fillSegments(chunk)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 等待所有分块完成
|
||||
deferredResults.awaitAll()
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun resetDict() {
|
||||
trie = DictSegment(0.toChar())
|
||||
}
|
||||
|
||||
fun containsWord(word: String?): Boolean {
|
||||
if (word.isNullOrEmpty()) return false
|
||||
db.rawQuery("SELECT 1 FROM dictionary WHERE word = ? LIMIT 1", arrayOf(word)).use {
|
||||
return it.count > 0
|
||||
}
|
||||
}
|
||||
|
||||
fun getFreq(key: String?): Double {
|
||||
if (!key.isNullOrEmpty()) {
|
||||
db.rawQuery("SELECT normalized_freq FROM dictionary WHERE word = ? LIMIT 1", arrayOf(key)).use {
|
||||
if (it.moveToFirst()) {
|
||||
return it.getDouble(it.getColumnIndexOrThrow("normalized_freq"))
|
||||
}
|
||||
}
|
||||
}
|
||||
return minFreq
|
||||
}
|
||||
|
||||
companion object {
|
||||
@Volatile
|
||||
private var INSTANCE: WordDictionary? = null
|
||||
|
||||
fun getInstance(context: Context) = INSTANCE ?: synchronized(this) {
|
||||
INSTANCE ?: WordDictionary(context.applicationContext).also { INSTANCE = it }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.huaban.analysis.jieba
|
||||
|
||||
import android.content.Context
|
||||
|
||||
open class WordDictionaryDatabase private constructor(context: Context): DictionaryDatabase(context) {
|
||||
|
||||
override val databaseName = "dict.db"
|
||||
|
||||
companion object {
|
||||
|
||||
@Volatile
|
||||
private var instance: WordDictionaryDatabase? = null
|
||||
|
||||
fun getInstance(context: Context): WordDictionaryDatabase = instance ?: synchronized(this) {
|
||||
instance ?: WordDictionaryDatabase(context.applicationContext).also { instance = it }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
package com.huaban.analysis.jieba.viterbi
|
||||
|
||||
import com.huaban.analysis.jieba.CharacterUtil
|
||||
import com.huaban.analysis.jieba.Log
|
||||
import com.huaban.analysis.jieba.Node
|
||||
import com.huaban.analysis.jieba.Pair
|
||||
import java.io.BufferedReader
|
||||
import java.io.IOException
|
||||
import java.io.InputStreamReader
|
||||
import java.nio.charset.Charset
|
||||
import java.util.*
|
||||
|
||||
class FinalSeg private constructor() {
|
||||
private fun loadModel() {
|
||||
val s = System.currentTimeMillis()
|
||||
val prevStatus = HashMap<Char, CharArray>().also { prevStatus = it }
|
||||
prevStatus['B'] = charArrayOf('E', 'S')
|
||||
prevStatus['M'] = charArrayOf('M', 'B')
|
||||
prevStatus['S'] = charArrayOf('S', 'E')
|
||||
prevStatus['E'] = charArrayOf('B', 'M')
|
||||
|
||||
val start = HashMap<Char, Double>().also { start = it }
|
||||
start['B'] = -0.26268660809250016
|
||||
start['E'] = -3.14e+100
|
||||
start['M'] = -3.14e+100
|
||||
start['S'] = -1.4652633398537678
|
||||
|
||||
val trans = HashMap<Char, Map<Char, Double>>().also { trans = it }
|
||||
val transB: MutableMap<Char, Double> = HashMap()
|
||||
transB['E'] = -0.510825623765990
|
||||
transB['M'] = -0.916290731874155
|
||||
trans['B'] = transB
|
||||
val transE: MutableMap<Char, Double> = HashMap()
|
||||
transE['B'] = -0.5897149736854513
|
||||
transE['S'] = -0.8085250474669937
|
||||
trans['E'] = transE
|
||||
val transM: MutableMap<Char, Double> = HashMap()
|
||||
transM['E'] = -0.33344856811948514
|
||||
transM['M'] = -1.2603623820268226
|
||||
trans['M'] = transM
|
||||
val transS: MutableMap<Char, Double> = HashMap()
|
||||
transS['B'] = -0.7211965654669841
|
||||
transS['S'] = -0.6658631448798212
|
||||
trans['S'] = transS
|
||||
|
||||
val `is` = javaClass.getResourceAsStream(PROB_EMIT)!!
|
||||
try {
|
||||
val br = BufferedReader(InputStreamReader(`is`, Charset.forName("UTF-8")))
|
||||
val emit = HashMap<Char, Map<Char, Double>>().also { emit = it }
|
||||
var values: MutableMap<Char, Double>? = null
|
||||
while (br.ready()) {
|
||||
val line = br.readLine()
|
||||
val tokens = line.split("\t".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
if (tokens.size == 1) {
|
||||
values = HashMap()
|
||||
emit[tokens[0][0]] = values
|
||||
} else {
|
||||
values!![tokens[0][0]] = tokens[1].toDouble()
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Log.error(String.format(Locale.getDefault(), "%s: load model failure!", PROB_EMIT))
|
||||
} finally {
|
||||
try {
|
||||
`is`.close()
|
||||
} catch (e: IOException) {
|
||||
Log.error(String.format(Locale.getDefault(), "%s: close failure!", PROB_EMIT))
|
||||
}
|
||||
}
|
||||
Log.debug(
|
||||
String.format(
|
||||
Locale.getDefault(), "model load finished, time elapsed %d ms.",
|
||||
System.currentTimeMillis() - s
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun cut(sentence: String, tokens: MutableList<String>) {
|
||||
var chinese = StringBuilder()
|
||||
var other = StringBuilder()
|
||||
for (element in sentence) {
|
||||
if (CharacterUtil.isChineseLetter(element)) {
|
||||
if (other.isNotEmpty()) {
|
||||
processOtherUnknownWords(other.toString(), tokens)
|
||||
other = StringBuilder()
|
||||
}
|
||||
chinese.append(element)
|
||||
} else {
|
||||
if (chinese.isNotEmpty()) {
|
||||
viterbi(chinese.toString(), tokens)
|
||||
chinese = StringBuilder()
|
||||
}
|
||||
other.append(element)
|
||||
}
|
||||
}
|
||||
if (chinese.isNotEmpty()) viterbi(chinese.toString(), tokens)
|
||||
else {
|
||||
processOtherUnknownWords(other.toString(), tokens)
|
||||
}
|
||||
}
|
||||
|
||||
private fun viterbi(sentence: String, tokens: MutableList<String>) {
|
||||
val v = Vector<MutableMap<Char, Double>>()
|
||||
var path: MutableMap<Char?, Node?> = HashMap()
|
||||
|
||||
v.add(HashMap())
|
||||
for (state in states) {
|
||||
var emP = emit!![state]!![sentence[0]]
|
||||
if (null == emP) emP = MIN_FLOAT
|
||||
v[0][state] = start!![state]!! + emP
|
||||
path[state] = Node(state, null)
|
||||
}
|
||||
|
||||
for (i in 1..<sentence.length) {
|
||||
val vv: MutableMap<Char, Double> = HashMap()
|
||||
v.add(vv)
|
||||
val newPath: MutableMap<Char?, Node?> = HashMap()
|
||||
for (y in states) {
|
||||
var emp = emit!![y]!![sentence[i]]
|
||||
if (emp == null) emp = MIN_FLOAT
|
||||
var candidate: Pair<Char>? = null
|
||||
for (y0 in prevStatus!![y]!!) {
|
||||
var tranp = trans!![y0]!![y]
|
||||
if (null == tranp) tranp = MIN_FLOAT
|
||||
tranp += (emp + v[i - 1][y0]!!)
|
||||
if (null == candidate) candidate = Pair(y0, tranp)
|
||||
else if (candidate.freq <= tranp) {
|
||||
candidate.freq = tranp
|
||||
candidate.key = y0
|
||||
}
|
||||
}
|
||||
vv[y] = candidate!!.freq
|
||||
newPath[y] = Node(y, path[candidate.key])
|
||||
}
|
||||
path = newPath
|
||||
}
|
||||
val probE = v[sentence.length - 1]['E']!!
|
||||
val probS = v[sentence.length - 1]['S']!!
|
||||
val posList = Vector<Char>(sentence.length)
|
||||
var win: Node?
|
||||
win = if (probE < probS) path['S']
|
||||
else path['E']
|
||||
|
||||
while (win != null) {
|
||||
posList.add(win.value)
|
||||
win = win.parent
|
||||
}
|
||||
posList.reverse()
|
||||
|
||||
var begin = 0
|
||||
var next = 0
|
||||
for (i in sentence.indices) {
|
||||
val pos = posList[i]
|
||||
when (pos) {
|
||||
'B' -> begin = i
|
||||
'E' -> {
|
||||
tokens.add(sentence.substring(begin, i + 1))
|
||||
next = i + 1
|
||||
}
|
||||
'S' -> {
|
||||
tokens.add(sentence.substring(i, i + 1))
|
||||
next = i + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
if (next < sentence.length) tokens.add(sentence.substring(next))
|
||||
}
|
||||
|
||||
private fun processOtherUnknownWords(other: String, tokens: MutableList<String>) {
|
||||
val mat = CharacterUtil.reSkip.matcher(other)
|
||||
var offset = 0
|
||||
while (mat.find()) {
|
||||
if (mat.start() > offset) {
|
||||
tokens.add(other.substring(offset, mat.start()))
|
||||
}
|
||||
tokens.add(mat.group())
|
||||
offset = mat.end()
|
||||
}
|
||||
if (offset < other.length) tokens.add(other.substring(offset))
|
||||
}
|
||||
|
||||
companion object {
|
||||
private var singleInstance: FinalSeg? = null
|
||||
private const val PROB_EMIT = "/prob_emit.txt"
|
||||
private val states = charArrayOf('B', 'M', 'E', 'S')
|
||||
private var emit: MutableMap<Char, Map<Char, Double>>? = null
|
||||
private var start: MutableMap<Char, Double>? = null
|
||||
private var trans: MutableMap<Char, Map<Char, Double>>? = null
|
||||
private var prevStatus: MutableMap<Char, CharArray>? = null
|
||||
private const val MIN_FLOAT = -3.14e100
|
||||
|
||||
@get:Synchronized
|
||||
val instance: FinalSeg
|
||||
get() {
|
||||
if (singleInstance == null) {
|
||||
singleInstance = FinalSeg().apply { loadModel() }
|
||||
}
|
||||
return singleInstance!!
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
jieba-analysis/src/main/resources/dict-chinese-chars.db.gzip
Normal file
BIN
jieba-analysis/src/main/resources/dict-chinese-chars.db.gzip
Normal file
Binary file not shown.
BIN
jieba-analysis/src/main/resources/dict-chinese-phrases.db.gzip
Normal file
BIN
jieba-analysis/src/main/resources/dict-chinese-phrases.db.gzip
Normal file
Binary file not shown.
BIN
jieba-analysis/src/main/resources/dict.db.gzip
Normal file
BIN
jieba-analysis/src/main/resources/dict.db.gzip
Normal file
Binary file not shown.
35228
jieba-analysis/src/main/resources/prob_emit.txt
Normal file
35228
jieba-analysis/src/main/resources/prob_emit.txt
Normal file
File diff suppressed because it is too large
Load Diff
@@ -75,11 +75,11 @@ android {
|
||||
|
||||
arguments(*[
|
||||
// @Hint by LZX284 (https://github.com/LZX284) on Sep 30, 2023.
|
||||
// ! "ANDROID_PLATFORM" 默认为 "android-23", 这里修改为与 AutoJs6 最低 SDK 版本一致的 24.
|
||||
// ! "ANDROID_PLATFORM" 默认为 "android-23", 这里修改为与 AutoJs6 最低 SDK 版本一致的 `versionMap.MIN_SDK`.
|
||||
// ! en-US (translated by SuperMonster003 on Oct 22, 2024):
|
||||
// ! "ANDROID_PLATFORM" with default value "android-23" was changed to 24
|
||||
// ! "ANDROID_PLATFORM" with default value "android-23" was changed to `versionMap.MIN_SDK`
|
||||
// ! to align with the min SDK version of AutoJs6.
|
||||
ANDROID_PLATFORM: "android-24",
|
||||
ANDROID_PLATFORM: "android-${versionMap.MIN_SDK}",
|
||||
ANDROID_STL : "c++_shared",
|
||||
ANDROID_ARM_NEON: "TRUE",
|
||||
].collect {
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
include(
|
||||
":app",
|
||||
|
||||
":jieba-analysis",
|
||||
":libs:android-job-simplified-1.4.3",
|
||||
":libs:androidx.appcompat-1.0.2",
|
||||
":libs:apk-parser-1.0.2",
|
||||
@@ -47,6 +47,8 @@ include(
|
||||
":libs:markwon-syntax-highlight-4.6.2",
|
||||
)
|
||||
|
||||
project(":jieba-analysis").projectDir = file("jieba-analysis")
|
||||
|
||||
pluginManagement {
|
||||
|
||||
val consts = object {
|
||||
@@ -619,4 +621,10 @@ pluginManagement {
|
||||
set("isCleanupRapidOcr", config.isCleanupRapidOcr)
|
||||
}
|
||||
|
||||
gradle.beforeProject {
|
||||
extensions.extraProperties["kotlinVersion"] = notations.classpath.find {
|
||||
it.contains("kotlin-gradle-plugin")
|
||||
}?.substringAfterLast(":") ?: config.fallbackKotlinVersion
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#Fri Dec 13 16:40:27 CST 2024
|
||||
BUILD_TIME=1734079227022
|
||||
#Tue Dec 24 20:14:13 CST 2024
|
||||
BUILD_TIME=1735042453409
|
||||
COMPILE_SDK_VERSION=34
|
||||
JAVA_VERSION=23
|
||||
JAVA_VERSION_MIN_RADICAL=0
|
||||
@@ -17,6 +17,6 @@ RAPID_OCR_OPENCV_MOBILE_LABEL_VERSION=13
|
||||
RAPID_OCR_OPENCV_MOBILE_VERSION=4.5.3
|
||||
TARGET_SDK_VERSION=34
|
||||
TARGET_SDK_VERSION_INRT=29
|
||||
VERSION_BUILD=2909
|
||||
VERSION_BUILD=2920
|
||||
VERSION_NAME=6.6.1 Alpha2
|
||||
VSCODE_EXT_REQUIRED_VERSION=1.0.8
|
||||
|
||||
Reference in New Issue
Block a user