KMP 实现鸿蒙跨端:Kotlin 正则表达式匹配和替换工具
本文档介绍如何在鸿蒙跨端开发中实现一个完整的正则表达式匹配和替换工具系统。这个案例展示了如何使用 Kotlin 的正则表达式功能来创建一个功能丰富的文本处理工具。通过 KMP,这个工具可以无缝编译到 JavaScript,在 OpenHarmony 应用中运行,并支持用户输入进行实时处理。✅ 使用 findAll() 提取所有匹配项✅ 使用 toSet() 去重✅ 使用 count() 统计✅ 预
·
目录
概述
本文档介绍如何在 Kotlin Multiplatform (KMP) 鸿蒙跨端开发中实现一个完整的正则表达式匹配和替换工具系统。这个案例展示了如何使用 Kotlin 的正则表达式功能来创建一个功能丰富的文本处理工具。通过 KMP,这个工具可以无缝编译到 JavaScript,在 OpenHarmony 应用中运行,并支持用户输入进行实时处理。
工具的特点
- 多模式匹配:支持邮箱、电话、URL、数字、单词等多种模式
- 灵活替换:支持将匹配项替换为指定内容
- 详细统计:提供匹配项的详细统计信息
- 频率分析:分析单词和数字的出现频率
- 跨端兼容:一份 Kotlin 代码可同时服务多个平台
算法功能
1. 模式匹配
- 邮箱匹配:匹配标准的电子邮件地址
- 电话匹配:匹配格式化的电话号码
- URL匹配:匹配 HTTP/HTTPS 链接
- 数字匹配:匹配整数和浮点数
- 单词匹配:匹配英文单词
2. 匹配统计
- 匹配数量:统计每种模式的匹配数量
- 匹配列表:列出所有匹配项
- 唯一匹配:统计唯一的匹配项
3. 替换操作
- 邮箱替换:将邮箱替换为占位符
- 电话替换:将电话替换为占位符
- 数字替换:将数字替换为占位符
4. 字符统计
- 字母数:统计字母个数
- 数字数:统计数字个数
- 空格数:统计空格个数
- 特殊字符:统计特殊字符个数
- 大小写:统计大写和小写字母
5. 频率分析
- 单词频率:统计每个单词出现的次数
- 数字频率:统计每个数字出现的次数
- 排序:按频率从高到低排序
核心实现
1. 定义正则表达式模式
val emailPattern = Regex("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}")
val phonePattern = Regex("\\d{3}-\\d{3}-\\d{4}")
val urlPattern = Regex("https?://[^\\s]+")
val numberPattern = Regex("\\d+")
val wordPattern = Regex("\\b[a-zA-Z]+\\b")
2. 提取匹配项
val emails = emailPattern.findAll(inputText).map { it.value }.toList()
val phones = phonePattern.findAll(inputText).map { it.value }.toList()
val urls = urlPattern.findAll(inputText).map { it.value }.toList()
val numbers = numberPattern.findAll(inputText).map { it.value }.toList()
val words = wordPattern.findAll(inputText).map { it.value }.toList()
3. 替换匹配项
val emailReplaced = inputText.replace(emailPattern, "[EMAIL]")
val phoneReplaced = inputText.replace(phonePattern, "[PHONE]")
val numberReplaced = inputText.replace(numberPattern, "[NUM]")
4. 频率统计
val uniqueWords = words.toSet()
val wordStats = uniqueWords.map { word ->
val count = words.count { it == word }
"$word: $count 次"
}.sorted()
5. 字符统计
val letterCount = inputText.count { it.isLetter() }
val digitCount = inputText.count { it.isDigit() }
val spaceCount = inputText.count { it.isWhitespace() }
val specialCount = inputText.count { !it.isLetterOrDigit() && !it.isWhitespace() }
实战案例
案例:完整的正则表达式匹配和替换工具
Kotlin 源代码
@OptIn(ExperimentalJsExport::class)
@JsExport
fun regexMatcherReplacer(inputText: String = "Email: john@example.com, Phone: 123-456-7890"): String {
if (inputText.isEmpty()) {
return "❌ 错误: 输入不能为空\n请输入要处理的文本"
}
// 1. 常用正则表达式模式
val emailPattern = Regex("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}")
val phonePattern = Regex("\\d{3}-\\d{3}-\\d{4}")
val urlPattern = Regex("https?://[^\\s]+")
val numberPattern = Regex("\\d+")
val wordPattern = Regex("\\b[a-zA-Z]+\\b")
// 2. 提取匹配项
val emails = emailPattern.findAll(inputText).map { it.value }.toList()
val phones = phonePattern.findAll(inputText).map { it.value }.toList()
val urls = urlPattern.findAll(inputText).map { it.value }.toList()
val numbers = numberPattern.findAll(inputText).map { it.value }.toList()
val words = wordPattern.findAll(inputText).map { it.value }.toList()
// 3. 统计匹配数量
val emailCount = emails.size
val phoneCount = phones.size
val urlCount = urls.size
val numberCount = numbers.size
val wordCount = words.size
// 4. 替换操作
val emailReplaced = inputText.replace(emailPattern, "[EMAIL]")
val phoneReplaced = inputText.replace(phonePattern, "[PHONE]")
val numberReplaced = inputText.replace(numberPattern, "[NUM]")
// 5. 提取所有单词
val uniqueWords = words.toSet()
val wordStats = uniqueWords.map { word ->
val count = words.count { it == word }
"$word: $count 次"
}.sorted()
// 6. 提取所有数字
val uniqueNumbers = numbers.toSet()
val numberStats = uniqueNumbers.map { num ->
val count = numbers.count { it == num }
"$num: $count 次"
}.sorted()
// 7. 字符类型统计
val letterCount = inputText.count { it.isLetter() }
val digitCount = inputText.count { it.isDigit() }
val spaceCount = inputText.count { it.isWhitespace() }
val specialCount = inputText.count { !it.isLetterOrDigit() && !it.isWhitespace() }
// 8. 大小写统计
val uppercaseCount = inputText.count { it.isUpperCase() }
val lowercaseCount = inputText.count { it.isLowerCase() }
return "🔍 正则表达式匹配和替换\n" +
"━━━━━━━━━━━━━━━━━━━━━\n" +
"原始文本: $inputText\n\n" +
"1️⃣ 模式匹配统计:\n" +
" 邮箱: $emailCount 个\n" +
" 电话: $phoneCount 个\n" +
" URL: $urlCount 个\n" +
" 数字: $numberCount 个\n" +
" 单词: $wordCount 个\n\n" +
"2️⃣ 匹配项详情:\n" +
(if (emails.isEmpty()) " 邮箱: 无" else " 邮箱: ${emails.joinToString(", ")}") + "\n" +
(if (phones.isEmpty()) " 电话: 无" else " 电话: ${phones.joinToString(", ")}") + "\n" +
(if (urls.isEmpty()) " URL: 无" else " URL: ${urls.joinToString(", ")}") + "\n\n" +
"3️⃣ 字符统计:\n" +
" 字母: $letterCount 个\n" +
" 数字: $digitCount 个\n" +
" 空格: $spaceCount 个\n" +
" 特殊字符: $specialCount 个\n" +
" 大写: $uppercaseCount 个\n" +
" 小写: $lowercaseCount 个\n\n" +
"4️⃣ 单词频率 (Top 10):\n" +
wordStats.take(10).mapIndexed { index, stat -> " ${index + 1}. $stat" }.joinToString("\n") + "\n\n" +
"5️⃣ 数字频率:\n" +
numberStats.take(5).mapIndexed { index, stat -> " ${index + 1}. $stat" }.joinToString("\n") + "\n\n" +
"6️⃣ 替换示例:\n" +
" 邮箱替换: $emailReplaced\n" +
" 电话替换: $phoneReplaced\n" +
" 数字替换: $numberReplaced\n\n" +
"━━━━━━━━━━━━━━━━━━━━━\n" +
"✅ 处理完成!"
}
ArkTS 调用代码(带输入框)
import { regexMatcherReplacer } from './hellokjs';
@Entry
@Component
struct Index {
@State message: string = '加载中...';
@State results: string[] = [];
@State caseTitle: string = '正则表达式匹配和替换';
@State inputText: string = 'Email: john@example.com, Phone: 123-456-7890';
aboutToAppear(): void {
this.loadResults();
}
loadResults(): void {
try {
const results: string[] = [];
const algorithmResult = regexMatcherReplacer(this.inputText);
results.push(algorithmResult);
this.results = results;
this.message = '✓ 匹配完成';
} catch (error) {
this.message = `✗ 错误: ${error}`;
}
}
build() {
Column() {
// 顶部标题栏
Row() {
Text('KMP 鸿蒙跨端')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(Color.White)
Spacer()
Text('Kotlin 案例')
.fontSize(14)
.fontColor(Color.White)
}
.width('100%')
.height(50)
.backgroundColor('#3b82f6')
.padding({ left: 20, right: 20 })
.alignItems(VerticalAlign.Center)
.justifyContent(FlexAlign.SpaceBetween)
// 案例标题
Column() {
Text(this.caseTitle)
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#1f2937')
Text(this.message)
.fontSize(13)
.fontColor('#6b7280')
.margin({ top: 5 })
}
.width('100%')
.padding({ left: 20, right: 20, top: 20, bottom: 15 })
.alignItems(HorizontalAlign.Start)
// 输入框区域
Column() {
Text('输入文本:')
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor('#1f2937')
.margin({ bottom: 8 })
TextInput({ placeholder: '输入要匹配的文本...', text: this.inputText })
.width('100%')
.height(60)
.padding(12)
.border({ width: 1, color: '#d1d5db' })
.borderRadius(6)
.onChange((value: string) => {
this.inputText = value
})
Button('匹配')
.width('100%')
.height(40)
.margin({ top: 12 })
.backgroundColor('#3b82f6')
.fontColor(Color.White)
.onClick(() => {
this.loadResults()
})
}
.width('100%')
.padding({ left: 16, right: 16, bottom: 16 })
// 结果显示区域
Scroll() {
Column() {
ForEach(this.results, (result: string) => {
Column() {
Text(result)
.fontSize(13)
.fontFamily('monospace')
.fontColor('#374151')
.width('100%')
.margin({ top: 10 })
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.border({ width: 1, color: '#e5e7eb' })
.borderRadius(8)
.margin({ bottom: 12 })
})
}
.width('100%')
.padding({ left: 16, right: 16 })
}
.layoutWeight(1)
.width('100%')
// 底部按钮区域
Row() {
Button('示例文本')
.width('48%')
.height(44)
.backgroundColor('#10b981')
.fontColor(Color.White)
.fontSize(14)
.onClick(() => {
this.inputText = 'Email: john@example.com, Phone: 123-456-7890'
this.loadResults()
})
Button('清空')
.width('48%')
.height(44)
.backgroundColor('#6b7280')
.fontColor(Color.White)
.fontSize(14)
.onClick(() => {
this.inputText = ''
this.results = []
})
}
.width('100%')
.padding({ left: 16, right: 16, bottom: 20 })
}
.width('100%')
.height('100%')
.backgroundColor('#f9fafb')
}
}
编译过程详解
Kotlin 到 JavaScript 的转换
| Kotlin 特性 | JavaScript 等价物 |
|---|---|
| Regex | RegExp |
| findAll() | match() 或 matchAll() |
| map() | 数组 map 方法 |
| count() | 条件计数循环 |
| toSet() | Set 对象 |
关键转换点
- 正则表达式:转换为 JavaScript RegExp
- 集合操作:转换为数组操作
- Lambda 表达式:转换为 JavaScript 函数
- 字符串处理:保持功能一致
算法扩展
扩展 1:添加自定义模式
fun matchCustomPattern(text: String, pattern: String): List<String> {
return try {
Regex(pattern).findAll(text).map { it.value }.toList()
} catch (e: Exception) {
emptyList()
}
}
扩展 2:添加分组提取
fun extractGroups(text: String, pattern: String): List<List<String>> {
return Regex(pattern).findAll(text).map { match ->
match.groupValues
}.toList()
}
扩展 3:添加条件替换
fun conditionalReplace(text: String, pattern: String, condition: (String) -> Boolean, replacement: String): String {
return Regex(pattern).replace(text) { match ->
if (condition(match.value)) replacement else match.value
}
}
扩展 4:添加多模式替换
fun multiPatternReplace(text: String, replacements: Map<String, String>): String {
var result = text
for ((pattern, replacement) in replacements) {
result = result.replace(Regex(pattern), replacement)
}
return result
}
最佳实践
1. 使用 findAll() 提取所有匹配项
// ✅ 好:使用 findAll()
val matches = pattern.findAll(text).map { it.value }.toList()
// ❌ 不好:使用 find() 循环
val matches = mutableListOf<String>()
var remaining = text
while (true) {
val match = pattern.find(remaining)
if (match != null) {
matches.add(match.value)
remaining = remaining.substring(match.range.last + 1)
} else break
}
2. 使用 toSet() 去重
// ✅ 好:使用 toSet()
val uniqueMatches = matches.toSet()
// ❌ 不好:手动去重
val uniqueMatches = mutableSetOf<String>()
for (match in matches) {
if (!uniqueMatches.contains(match)) {
uniqueMatches.add(match)
}
}
3. 使用 count() 统计
// ✅ 好:使用 count()
val emailCount = emails.size
// ❌ 不好:使用 filter().size
val emailCount = emails.filter { it.isNotEmpty() }.size
4. 预编译正则表达式
// ✅ 好:预编译
val emailPattern = Regex("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}")
val emails = emailPattern.findAll(text)
// ❌ 不好:每次都编译
val emails = Regex("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}").findAll(text)
常见问题
Q1: 如何匹配中文字符?
A: 使用 Unicode 范围:
val chinesePattern = Regex("[\\u4e00-\\u9fa5]+")
val chineseWords = chinesePattern.findAll(text).map { it.value }.toList()
Q2: 如何实现不区分大小写的匹配?
A: 使用 RegexOption.IGNORE_CASE:
val pattern = Regex("hello", RegexOption.IGNORE_CASE)
val matches = pattern.findAll(text)
Q3: 如何提取分组内容?
A: 使用 groupValues:
val pattern = Regex("(\\w+)@(\\w+\\.\\w+)")
pattern.findAll(text).forEach { match ->
val username = match.groupValues[1]
val domain = match.groupValues[2]
}
Q4: 如何处理特殊字符?
A: 使用 Regex.escape():
val specialChars = "$100"
val pattern = Regex(Regex.escape(specialChars))
Q5: 如何实现多行匹配?
A: 使用 RegexOption.MULTILINE:
val pattern = Regex("^\\w+$", RegexOption.MULTILINE)
val lines = pattern.findAll(text)
总结
关键要点
- ✅ 使用 findAll() 提取所有匹配项
- ✅ 使用 toSet() 去重
- ✅ 使用 count() 统计
- ✅ 预编译正则表达式
- ✅ KMP 能无缝编译到 JavaScript
下一步
- 实现自定义模式匹配
- 添加分组提取功能
- 实现条件替换
- 添加多模式替换
- 实现正则表达式验证
参考资源
更多推荐


所有评论(0)