• 既要又要的正则匹配规则



    目录

    1 背景

    2 浅谈

    3 分析

    3.1 如何识别成整体块?

    3.1.1 正则匹配整体块

    3.1.2 “ - ”开头“ - ”结尾

    3.1.3 模糊匹配不行,采取精准匹配

    3.2 如何作为整体块显示?

    3.3 光标不可以中间插入

    4 效果展示

    5 参考代码


    1 背景

            在上面这样一个文本编辑框里,点击Server name时,需要在当前光标处插入真实的Server name,且此Server name需要作为一个整体块,光标不可以在Server name中间插入,同时也需要支持整体删除。

            这里可分解成如下3个需求:

    1. 点击Server name时,需要在当前光标处插入真实的Server name;
    2. Server name需要作为一个整体块,光标不可以在Server name中间插入;
    3. Server name需要支持整体删除。

    2 浅谈

            Server name作为一个文本插入到某个位置,这比较好实现,但需要作为整体块不可插入且可整体删除,这实现起来没那么容易。

    • 首先会遇到这样一个问题,如何实现整体块?
    • 其次还需要考虑,怎么实现光标不可移到整体块中间?
    • 以及,如何实现整体删除的逻辑?

    3 分析

    3.1 如何识别成整体块?

            要识别成整体块,需要精准确定整体块的位置,有两种方式可实现:

    • 其一,利用String的indexOf方法找到index,加上length即可确定其具体位置,但如果同时有多个整体块,这种方式就需要优化;
    • 其二,利用正则规则去匹配,可同时匹配到多个整体块。

    3.1.1 正则匹配整体块

            使用正则匹配的方式可以实现,但需要考虑误匹配的问题,比如:直接使用字符串匹配肯定不合适,会直接导致整句话被匹配上。所以常规做法就是加入特殊字符,比如:“@username ”采用正则匹配的前提是——以“@”开头以“ ”结尾。username正则匹配规则和内容提取规则如下:

    1. "[@][^@# \\f\\r\\t\\n]{1,30}[ ]" // username匹配正则规则
    2. "(?<=@)[^@# \\f\\r\\t\\n]{1,30}(?=[ ])" // 提取username正则规则

            注:username中不允许使用“@”、“#”和空格“ ”。

    3.1.2 “ - ”开头“ - ”结尾

            Server name匹配规则我们采用如下方式:以“ - ”开头以“ - ”结尾。

    1. "( - )[^- \\f\\r\\t\\n]{1,50}( - )" // Server name匹配正则规则
    2. "(?<=( - ))[^- \\f\\r\\t\\n]{1,50}(?=( - ))" // 提取Server name正则规则

            我们知道正则规则中的“[^- \\f\\r\\t\\n]{1,50}”表示:匹配上的50个字符中不能包含“-”、“ ”、换页“\f”、回车“\r”、制表“\t”、换行“\n”。

            这就存在一个问题,Server name是有可能包含“-”和“ ”的,要么需要定义好Server name中不允许“-”和“ ”,要么就重新设计正则匹配规则,否则会匹配不上Server name。

            那么去掉“^- ”的约束呢,像下面这种正则规则是否合适?

    "( - )[^\\f\\r\\t\\n]{1,50}( - )" // Server name匹配正则规则

            答案是不行的🙅‍♂️,会存在误匹配。只有一个Server name能够精确匹配,但是多个Server name会被识别成一个Server name,因为正则匹配采用的贪婪模式,会一直往后找寻。

     

    3.1.3 模糊匹配不行,采取精准匹配

            这里我们采用的是模糊匹配:“[^- \\f\\r\\t\\n]{1,50}”,那么如果采用Server name精准匹配呢?

    像下面这种,事实证明可以解决模糊匹配的问题。

    "( - )Server name( - )" // Server name匹配正则规则

            这个时候只要我们,根据Server name,动态生成匹配规则即可。

    1. private const val REGEXP_TAG_SERVER_NAME = "( - )"
    2. val pattern = Pattern.compile(
    3. REGEXP_TAG_SERVER_NAME + "Server name" + REGEXP_TAG_SERVER_NAME,
    4. Pattern.CASE_INSENSITIVE
    5. )
    6. val matcher = pattern.matcher("待匹配的字符串")
    7. if (matcher.find()) {
    8. val start = matcher.start()
    9. val end = matcher.end()
    10. }

    3.2 如何作为整体块显示?

            整块显示,实现光标不可移到整体块中间,这里Span可解决问题。万物皆Span,如果你还没使用过Span,那可以去了解下了。

    1. fun function(){
    2. val ssb = SpannableStringBuilder("Hey @username , welcome to - agg group - !");
    3. val what = UnEditableSpan(" - agg group - ", "", "", "")
    4. ssb.setSpan(what, 27, 42, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
    5. }
    6. // 自定义EditText构造中调用
    7. setEditableFactory(
    8. NoCopySpanEditableFactory(
    9. SelectionSpanWatcher(
    10. UnEditableSpan::class
    11. )
    12. )
    13. )

            顺带一提,@username这种紫色高亮显示,他们实现如出一辙,增加下面一条setSpan即可: 

    1. ssb.setSpan(
    2. ForegroundColorSpan(Color.parseColor("#B5ABFF")),
    3. 27,
    4. 42,
    5. Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
    6. )

    3.3 光标不可以中间插入

    1. class SelectionSpanWatcher<T : Any>(private val kClass: KClass) : SpanWatcherAdapter() {
    2. private var selStart = 0
    3. private var selEnd = 0
    4. override fun onSpanChanged(text: Spannable,what: Any,ostart: Int,oend: Int,nstart: Int,nend: Int) {
    5. if (what === Selection.SELECTION_END && selEnd != nstart) {
    6. selEnd = nstart
    7. text.getSpans(nstart, nend, kClass.java).firstOrNull()?.run {
    8. val spanStart = text.getSpanStart(this)
    9. val spanEnd = text.getSpanEnd(this)
    10. val index =
    11. if (Math.abs(selEnd - spanEnd) > Math.abs(selEnd - spanStart)) spanStart else spanEnd
    12. Selection.setSelection(text, Selection.getSelectionStart(text), index)
    13. }
    14. }
    15. if (what === Selection.SELECTION_START && selStart != nstart) {
    16. selStart = nstart
    17. text.getSpans(nstart, nend, kClass.java).firstOrNull()?.run {
    18. val spanStart = text.getSpanStart(this)
    19. val spanEnd = text.getSpanEnd(this)
    20. val index =
    21. if (Math.abs(selStart - spanEnd) > Math.abs(selStart - spanStart)) spanStart else spanEnd
    22. Selection.setSelection(text, index, Selection.getSelectionEnd(text))
    23. }
    24. }
    25. }
    26. }

    注:手动输入匹配规则及其内容也可成功匹配,如:手动输入“ - agg group - ”。 

    4 效果展示

    5 参考代码

    1. class UnEditableSpan(val showText: String = "", val hashTagName: String = "", val id: String = "", val type: String = "") : MetricAffectingSpan() {
    2. override fun updateMeasureState(p: TextPaint) {
    3. }
    4. override fun updateDrawState(tp: TextPaint) {
    5. }
    6. }
    7. class NoCopySpanEditableFactory(private vararg val spans: NoCopySpan) : Editable.Factory() {
    8. override fun newEditable(source: CharSequence): Editable {
    9. return SpannableStringBuilder.valueOf(source).apply {
    10. spans.forEach {
    11. setSpan(it, 0, source.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
    12. }
    13. }
    14. }
    15. }
    16. open class SpanWatcherAdapter : SpanWatcher {
    17. override fun onSpanChanged(
    18. text: Spannable, what: Any, ostart: Int, oend: Int, nstart: Int,
    19. nend: Int
    20. ) {
    21. }
    22. override fun onSpanRemoved(text: Spannable, what: Any, start: Int, end: Int) {
    23. }
    24. override fun onSpanAdded(text: Spannable, what: Any, start: Int, end: Int) {
    25. }
    26. }
  • 相关阅读:
    生产问题 Recv-Q101
    AI绘图软件Stable Diffusion 安装和使用之二controlnet插件
    Python-Tkinter 图形化界面设计
    程序员职场生活记录分享——打工人沪漂五年,不想工作了
    java基于微信小程序面向企事业单位的项目申报小程序+ssm+uinapp+Mysql+计算机毕业设计
    项目中用的网关Gateway及SpringCloud
    Qt+Hook实现全局鼠标背景色
    关于使用elementUI中select和el-checkbox-group的回显问题
    在基于乐鑫芯片的用户定制开发板上开发 UI
    【Nano Framework ESP32篇】WS2812 彩色灯带实验
  • 原文地址:https://blog.csdn.net/Agg_bin/article/details/127119613