首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >用cl-ppcre regex转义引号

用cl-ppcre regex转义引号
EN

Stack Overflow用户
提问于 2021-09-10 06:37:36
回答 1查看 113关注 0票数 1

背景

我需要解析CSV文件和cl-csv et。阿尔。在大型文件上速度太慢,并且依赖于cl-unicode,这是我首选的lisp实现不支持的。因此,我正在改进简单桌,它是评议中速度最快的csv阅读器。

目前,简单表的行解析器相当脆弱,如果分隔符出现在引用的字符串中,它就会中断。我试图用cl替换行解析器。

尝试

使用Regex Coach,我发现了一个几乎在所有情况下都有效的regex:

("[^"]+"|[^,]+)(?:,\s*)?

现在的挑战是将这个Perl regex字符串转化为我可以在cl中用于split行的内容。我尝试过传递regex字符串,并为"提供了各种转义。

代码语言:javascript
复制
(defparameter bads "\"AER\",\"BenderlyZwick\",\"Benderly and Zwick Data: Inflation, Growth and Stock returns\",31,5,0,0,0,0,5,\"https://vincentarelbundock.github.io/Rdatasets/csv/AER/BenderlyZwick.csv\",\"https://vincentarelbundock.github.io/Rdatasets/doc/AER/BenderlyZwick.html\"
"Bad string, note a separator character in the quoted field, near Inflation")

(ppcre:split "(\"[^\"]+\"|[^,]+)(?:,\s*)?" bads)
NIL

单、双、三、四倍的\都不起作用。

我分析了这个字符串,看看解析树是什么样子的:

代码语言:javascript
复制
(ppcre:parse-string "(\"[^\"]+\"|[^,]+)(?:,s*)?")
(:SEQUENCE (:REGISTER (:ALTERNATION (:SEQUENCE #\" (:GREEDY-REPETITION 1 NIL (:INVERTED-CHAR-CLASS #\")) #\") (:GREEDY-REPETITION 1 NIL (:INVERTED-CHAR-CLASS #\,)))) (:GREEDY-REPETITION 0 1 (:GROUP (:SEQUENCE #\, (:GREEDY-REPETITION 0 NIL #\s)))))

并将结果树传递给split

代码语言:javascript
复制
(ppcre:split '(:SEQUENCE (:REGISTER (:ALTERNATION (:SEQUENCE #\" (:GREEDY-REPETITION 1 NIL (:INVERTED-CHAR-CLASS #\")) #\") (:GREEDY-REPETITION 1 NIL (:INVERTED-CHAR-CLASS #\,)))) (:GREEDY-REPETITION 0 1 (:GROUP (:SEQUENCE #\, (:GREEDY-REPETITION 0 NIL #\s))))) bads)
NIL

我还尝试了各种形式的*allow-quoting*

代码语言:javascript
复制
 (let ((ppcre:*allow-quoting* t))
  (ppcre:split "(\\Q\"\\E[^\\Q\"\\E]+\\Q\"\\E|[^,]+)(?:,\s*)?" bads))

我读过cl-ppcre文档,但是很少有使用解析树的例子,也没有转义引号的例子。

似乎什么都起不到作用。

我希望Regex能提供一种方法来查看Perl语法字符串的S表达式解析树形式。这将是一个非常有用的特性,允许您尝试使用regex字符串,然后在Lisp代码中复制和粘贴解析树。

在这个例子中,有人知道如何转义引号吗?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-09-10 10:23:52

在这个答案中,我将重点介绍代码中的错误,并试图解释如何使其工作。正如@Svante所解释的,对于用例来说,这可能不是最佳的操作过程。特别是,您的regex可能为您已知的测试输入量量身定做,并且可能会错过以后可能出现的情况。

例如,regex将字段视为由双引号分隔的字符串,没有内部双引号(甚至是转义),或者是与逗号不同的字符序列。但是,如果您的字段以普通字母开头,然后包含双引号,则它将是字段名的一部分。

修复测试字符串

也许在格式化您的问题时出现了问题,但是引入bads的表单格式错误。下面是*bads*的一个固定定义(注意特殊变量周围的星号,这是一个有用的约定,它有助于区分它们与词汇变量(名称周围的星号也称为“earmuff”):

代码语言:javascript
复制
(defparameter *bads*
  "\"AER\",\"BenderlyZwick\",\"Benderly and Zwick Data: Inflation, Growth and Stock returns\",31,5,0,0,0,0,5,\"https://vincentarelbundock.github.io/Rdatasets/csv/AER/BenderlyZwick.csv\",\"https://vincentarelbundock.github.io/Rdatasets/doc/AER/BenderlyZwick.html\"")

regex中的转义字符

您获得的解析树包含以下内容:

代码语言:javascript
复制
(... (:GREEDY-REPETITION 0 NIL #\s) ...)

解析树中有一个文字字符#\s。为了理解为什么,让我们定义两个辅助函数:

代码语言:javascript
复制
(defun chars (string)
  "Convert a string to a list of char names"
  (map 'list #'char-name string))

(defun test (s)
  (list :parse (chars s)
        :as (ppcre:parse-string s)))

例如,下面是如何解析以下不同的字符串:

代码语言:javascript
复制
(test "s")
=> (:PARSE ("LATIN_SMALL_LETTER_S") :AS #\s)

(test "\s")
=> (:PARSE ("LATIN_SMALL_LETTER_S") :AS #\s)

(test "\\s")
=> (:PARSE ("REVERSE_SOLIDUS" "LATIN_SMALL_LETTER_S")
    :AS :WHITESPACE-CHAR-CLASS)

只有在最后一种情况下,反斜杠(反向solidus)被转义,PPCRE解析器才会同时看到这个反斜杠和下一个字符#\s,并将这个序列解释为:WHITESPACE-CHAR-CLASS。Lisp阅读器将\s解释为s,因为它不是可以在Lisp中转义的字符的一部分。

我倾向于直接使用解析树,因为w.r.t有很多麻烦。逃避就会消失(在我看来,\Q和\E会加剧这种情况)。例如,固定的解析树如下所示,其中我用所需的关键字替换了#\s,并删除了无用的:register节点:

代码语言:javascript
复制
 (:sequence
   (:alternation
    (:sequence #\"
     (:greedy-repetition 1 nil
      (:inverted-char-class #\"))
     #\")
    (:greedy-repetition 1 nil (:inverted-char-class #\,)))
   (:greedy-repetition 0 1
    (:group
     (:sequence #\,
      (:greedy-repetition 0 nil :whitespace-char-class)))))

为什么结果是零?

请记住,您试图使用这个regex来split字符串,但是regex实际上描述了一个字段和下面的逗号。出现零结果的原因是您的字符串只是一个分隔符序列,如下例所示:

代码语言:javascript
复制
(split #\, ",,,,,,")
NIL

通过一个简单的例子,您可以看到分隔符所提供的分裂词:

代码语言:javascript
复制
(split "[a-z]+" "abc0def1z3")
=> ("" "0" "1" "3")

但是,如果分隔符也包括数字,则结果为零:

代码语言:javascript
复制
(split "[a-z0-9]+" "abc0def1z3")
=> NIL

在田野上绕圈

使用您定义的正则表达式,使用do-register-groups更容易。它是一个循环构造,通过尝试在字符串上依次匹配regex,将regex中的每个(:register ...)绑定到一个变量来迭代字符串。

如果将(:register ...)放在第一个(:alternation ...)周围,有时会捕获双引号(交替的第一个分支):

代码语言:javascript
复制
(do-register-groups (field)
    ('(:SEQUENCE
       (:register
        (:ALTERNATION
         (:SEQUENCE #\"
          (:GREEDY-REPETITION 1 NIL
           (:INVERTED-CHAR-CLASS #\"))
          #\")
         (:GREEDY-REPETITION 1 NIL (:INVERTED-CHAR-CLASS #\,))))
       (:GREEDY-REPETITION 0 1
        (:GROUP
         (:SEQUENCE #\,
          (:GREEDY-REPETITION 0 NIL :whitespace-char-class)))))
     *bads*)
  (print field))

"\"AER\"" 
"\"BenderlyZwick\"" 
"\"Benderly and Zwick Data: Inflation, Growth and Stock returns\"" 
"31" 
"5" 
"0" 
"0" 
"0" 
"0" 
"5" 
"\"https://vincentarelbundock.github.io/Rdatasets/csv/AER/BenderlyZwick.csv\"" 
"\"https://vincentarelbundock.github.io/Rdatasets/doc/AER/BenderlyZwick.html\"" 

另一个选项是添加两个:register节点,一个用于交替的每个分支;这意味着绑定两个变量,其中一个对于每个成功匹配都为零:

代码语言:javascript
复制
(do-register-groups (quoted simple)
    ('(:SEQUENCE
       (:ALTERNATION
        (:SEQUENCE #\"
         (:register ;; <- quoted (first register)
          (:GREEDY-REPETITION 1 NIL
           (:INVERTED-CHAR-CLASS #\")))
         #\")
        (:register ;; <- simple (second register)
         (:GREEDY-REPETITION 1 NIL (:INVERTED-CHAR-CLASS #\,))))
       (:GREEDY-REPETITION 0 1
        (:GROUP
         (:SEQUENCE #\,
          (:GREEDY-REPETITION 0 NIL :whitespace-char-class)))))
     *bads*)
  (print (or quoted simple)))

"AER" 
"BenderlyZwick" 
"Benderly and Zwick Data: Inflation, Growth and Stock returns" 
"31" 
"5" 
"0" 
"0" 
"0" 
"0" 
"5" 
"https://vincentarelbundock.github.io/Rdatasets/csv/AER/BenderlyZwick.csv" 
"https://vincentarelbundock.github.io/Rdatasets/doc/AER/BenderlyZwick.html" 

在循环中,您可以将每个字段push为列表或向量,以便稍后处理。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/69128186

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档