【问题标题】:How can I build up a regex from component parts in Clojure?如何从 Clojure 中的组件构建正则表达式?
【发布时间】:2021-06-04 22:26:17
【问题描述】:

正则表达式可能很复杂,我想将它们构建为组件,而不是作为单个巨大的正则表达式字符串。例如,如果我想匹配 UUID、SSN 或电话号码:

(let [uuid    "cafebabe-0867-5309-0666-0123456789ff"   
      ssn     "333-22-4444"
      phone   "619-239-5464"]

如何将组成部分的正则表达式字符串组合成一个整体的正则表达式?

【问题讨论】:

    标签: regex clojure


    【解决方案1】:

    我们希望使用预定义的字符类将每个正则表达式片段编写为 Java 字符串。但是,这不方便写成字符串文字,因为它将包含反斜杠,当写成纯字符串时,每个反斜杠又必须用反斜杠转义。如果我们的模式本身必须包含反斜杠,那就更糟了。

    我们可以通过使用像美元符号$ 这样的占位符来克服这个问题,而不是使用反斜杠\。在我们完成构建最终的字符串表达式后,我们将替换$ => \,然后构造一个java.util.regex.Pattern 对象。

    Clojure 中的解决方案:

    (ns tst.demo.core
      (:use tupelo.core tupelo.test)
      (:require [tupelo.string :as str]))
    
    ;-----------------------------------------------------------------------------
    ; regex "string" values with `$` as placeholder for backslash
    
    (def uuid-regex-str
      "$p{XDigit}{8}       # 8 hex digits (posix char class)
       -                   # hyphen
       $p{XDigit}{4}       # 4 hex digits
       -                   # hyphen
       $p{XDigit}{4}       # 4 hex digits
       -                   # hyphen
       $p{XDigit}{4}       # 4 hex digits
       -                   # hyphen
       $p{XDigit}{12}      # 12 hex digits
       ")
    
    (def phone-regex-str
      "$p{Digit}{3}   # 3 digits (posix char class)
       -              # hyphen
       $d{3}          # 3 digits (short form)
       -              # hyphen
       $p{Digit}{4}   # 4 digits
       ")
    
    (def ssn-regex-str
     "$p{Digit}{3}   # 3 digits (posix char class)
      -              # hyphen
      $p{Digit}{2}   # 2 digits
      -              # hyphen
      $d{4}          # 4 digits (short form)
      ")
    

    然后,将片段组合成最终的正则表达式模式:

    (def uuid-phone-ssn-pattern
      (re-pattern
        (let [regex-str (str
                          "(?x)"  ; expanded form ignores whitespace & comments
                          uuid-regex-str
                          \|  ; or
                          phone-regex-str
                          \|  ; or
                          ssn-regex-str)]
          (str/replace regex-str \$ \\))))
    

    结果

    (dotest
      (let [uuid  "cafebabe-0867-5309-0666-0123456789ff"
            phone "619-239-5464" ; 619-239-KING
            ssn   "333-22-4444"]
        (is (re-matches uuid-phone-ssn-pattern uuid))
        (is (re-matches uuid-phone-ssn-pattern phone))
        (is (re-matches uuid-phone-ssn-pattern ssn))))
    

    【讨论】:

      【解决方案2】:

      您可以硬拉spec 这样做。否则,您可能需要查看解析库,例如 Instaparse

      Spec 并不特定于文本解析,并且缺少一些regice 的便利性,但是您可以使用您在spec/def 中定义的组件来构建您的解析器:

      (require '[clojure.spec.alpha :as spec])
      
      (defn flatten-seqs [dst src]
        (if (sequential? src)
          (reduce flatten-seqs dst src)
          (conj dst src)))
      
      (defmacro cat-seq [& nested-seqs]
        (let [flat (flatten-seqs [] (eval `[~@nested-seqs]))]
          `(spec/cat ~@(interleave
                        (->> flat count range (map #(keyword (str "k" %))))
                        flat))))
      
      (spec/def :demo/dec-digit (set (vec "0123456789")))
      (spec/def :demo/hex-digit (set (vec "0123456789abcdef")))
      (spec/def :demo/uuid (cat-seq (repeat 8 :demo/hex-digit)
                                    #{\-}
                                    (repeat 4 :demo/dec-digit)
                                    #{\-}
                                    (repeat 4 :demo/dec-digit)
                                    #{\-}
                                    (repeat 4 :demo/dec-digit)
                                    #{\-}
                                    (repeat 12 :demo/hex-digit)))
      (spec/def :demo/ssn (cat-seq (repeat 3 :demo/dec-digit)
                                    #{\-}
                                   (repeat 2 :demo/dec-digit)
                                    #{\-}
                                   (repeat 4 :demo/dec-digit)))
      (spec/def :demo/phone (cat-seq (repeat 3 :demo/dec-digit)
                                     #{\-}
                                     (repeat 3 :demo/dec-digit)
                                     #{\-}
                                     (repeat 4 :demo/dec-digit)))
      
      (spec/def :demo/input (spec/alt :uuid :demo/uuid
                                      :ssn :demo/ssn
                                      :phone :demo/phone))
      (spec/def :demo/inputs (spec/* :demo/input))
      

      使用方法如下:

      (def uuid "cafebabe-0867-5309-0666-0123456789ff")
      (def ssn "333-22-5464")
      (def phone "123-555-6789")
      
      (spec/valid? :demo/inputs (vec (str ssn phone)))
      ;; => true
      
      (spec/valid? :demo/inputs (vec (str ssn phone "mjao")))
      ;; => false
      

      请注意,如果有许多不同的解析替代方案,它们会被标记为它们的类型,例如:ssn,使用conform时:

      (spec/conform :demo/inputs (vec (str ssn ssn)))
      ;; => [[:ssn {:k8 \4, :k5 \2, :k7 \5, :k9 \6, :k6 \-, :k0 \3, :k10 \4, :k3 \-, :k4 \2, :k1 \3, :k2 \3}] [:ssn {:k8 \4, :k5 \2, :k7 \5, :k9 \6, :k6 \-, :k0 \3, :k10 \4, :k3 \-, :k4 \2, :k1 \3, :k2 \3}]]
      

      【讨论】:

        猜你喜欢
        • 2017-07-24
        • 2014-06-06
        • 2014-11-11
        • 2021-12-04
        • 1970-01-01
        • 2011-03-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多