文本替换的lisp语句

字数统计我用的这个函数:

;; count words
(defvar wc-regexp-chinese-char-and-punc
  (rx (category chinese)))
(defvar wc-regexp-chinese-punc
  "[。,!?;:「」『』()、【】《》〈〉※—]")
(defvar wc-regexp-english-word
  "[a-zA-Z0-9-]+")
(defun geekblog-word-count ()
  "「較精確地」統計中/日/英文字數。
- 文章中的註解不算在字數內。
- 平假名與片假名亦包含在「中日文字數」內,每個平/片假名都算單獨一個字(但片假
  名不含連音「ー」)。
- 英文只計算「單字數」,不含標點。
- 韓文不包含在內。
※計算標準太多種了,例如英文標點是否算入、以及可能有不太常用的標點符號沒算入等
。且中日文標點的計算標準要看 Emacs 如何定義特殊標點符號如ヴァランタン・アルカン
中間的點也被 Emacs 算為一個字而不是標點符號。"
  (interactive)
  (let* ((v-buffer-string
          (progn
            (if (eq major-mode 'org-mode) ; 去掉 org 文件的 OPTIONS(以#+開頭)
                (setq v-buffer-string (replace-regexp-in-string "^#\\+.+" ""
								(buffer-substring-no-properties (point-min) (point-max))))
              (setq v-buffer-string (buffer-substring-no-properties (point-min) (point-max))))
            (replace-regexp-in-string (format "^ *%s *.+" comment-start) "" v-buffer-string)))
                                        ; 把註解行刪掉(不把註解算進字數)。
         (chinese-char-and-punc 0)
         (chinese-punc 0)
         (english-word 0)
         (chinese-char 0))
    (with-temp-buffer
      (insert v-buffer-string)
      (goto-char (point-min))
      ;; 中文(含標點、片假名)
      (while (re-search-forward wc-regexp-chinese-char-and-punc nil :no-error)
        (setq chinese-char-and-punc (1+ chinese-char-and-punc)))
      ;; 中文標點符號
      (goto-char (point-min))
      (while (re-search-forward wc-regexp-chinese-punc nil :no-error)
        (setq chinese-punc (1+ chinese-punc)))
      ;; 英文字數(不含標點)
      (goto-char (point-min))
      (while (re-search-forward wc-regexp-english-word nil :no-error)
        (setq english-word (1+ english-word))))
    (setq chinese-char (- chinese-char-and-punc chinese-punc))
    (+ chinese-char english-word)))

其他的感觉用到的比较少,反正在emacs中总有办法解决。

3 个赞

太棒了,整合一下就可以称作「办公工具插件」了。

这些感觉更像是排版软件才有的功能呐

是啊,@kinney 大佬说过 emacs没啥不能的。

把emacs组装成一个文秘编辑,比现在都用word好多了,也符合当前国产软件替代的大环境。

(setq repl-list '((“old” “new”)

	  ("." "。")         ;中文标点替換
    
	  (" " "")           ;刪除空格

	  ("," ",")))   ;中文标点替換

第一個.和。替換時,會將所有字替換成。。。。

原因應該是把「.」當作通配符了,測試了「.」,但問題沒有解決,請問如何替換通配符呢?謝謝!

因为我用的是 re-search-forward 函数进行搜索匹配,匹配的正则表达式,不是纯字符串。在正则中点号需要转义,正确的写法是 "\\."。我应该把 replall写成两个函数 replall-stringreplall-regexp, 这样更清晰。

我修改了一下,现在使用 replall-string 函数可以直接匹配字符串,不用考虑正则特殊字符。加了一个 replall-regexp 函数可以匹配正则,不过只能设置 repl-regexp-list 变量。文件中的值只适用于纯字符串匹配。代码在 这里

1 个赞

經過測試,沒有出問題,非常好。

多数句子能看懂了,这两句最长的能用汉语翻译一下吗?谢谢!

(setq repl-pair (split-string (thing-at-point 'line) “[ \f\t\n\r\v]+” t “[ \f\t\n\r\v]+”))

(setq repl-list (append repl-list (list repl-pair)))

  1. (thing-at-point 'line …) 就是获取光标所在行字符串。split-string的作用是使用"[ \f\t\n\r\v]"这个正则分割前面的字符串为数组,也就是按照空格分割字符串成数组。split-string的其他参数可以看文档。最终赋值的repl-pair是一个包含两个元素的list。比如 ("old" "new")

  2. 就是把前面获得的单个pair放到一个list里。比如 '(("old1" "new1") ("old2" "new2") ("old3" "new3"))。append可以连接多个list。

Demos #+BEGIN_SRC elisp (split-string “The quick brown fox.” " +") #+END_SRC

#+RESULTS: : (“The” “quick” “brown” “fox.”)

例子很簡單,都是空格分割的,這樣也行啊。爲何用[ \f\t\n\r\v]這樣好怪的正則表達式啊。

这个正则考虑的情况更全面吧,因为你不能保证中间的一定是空格,可能是一个tab,或其他制表符。你写的也够用了,不过写全一点不更好吗。

1 个赞

这几天学习了lisp的基本语法,模仿着上面的程序,第一次写一个程序。

想达成的效果是:把一个ceshi.yaml文件拆分成若干个.org文件。

ceshi.yaml是这样的:

日:    # No.1
  字源: 象形-甲骨文
  字意: [體]太陽。
  組合字:
昌:    # No.2
  字源: 象形-甲骨文
  字意: [用]說太陽之光,明亮,光大,美好。

根据有「#」的行,分解为 日 .org 昌.org等等

(setq new-file new-file-title new-file-cont) ;新文件、文件名、文件内容
(defun mydo-yaml-file (file)
  (interactive "fchoose a file to do:")  ;;ceshi.yaml
    (with-temp-buffer
      (insert-file-contents file)
      (goto-char (point-min))
      (while (< (point) (point-max))
        (if (search-forward "#" nil t)
            (progn
              (beginning-of-line)
              (setq new-file-title (current-word))
              (next-line)
              (region-beginning))
          (next-line))
              (region-end)
              (setq new-file-cont(buffer-substring-no-properties (region-beginning) (region-end)))
              (find-file (concat "~/org/" new-file-title ".org"))
              (with-output-to-temp-buffer
                  (insert new-file-title "\n" new-file-cont)
                 (write-file new-file)))))

错误提示说:没有定义mark,但我想这只是一个小错误,可能上面的结构都有问题,但不知道如何改。请大佬们不吝赐教,谢谢!

这种实际问题的难点不在于如何写代码,而在于解决问题的思路和算法。在写代码之前要先把每一步需要做什么想清楚,不能只是大概知道。

我看了你的代码,大概的思路是没有问题的,但是其中的逻辑没有想清楚。

逻辑上的问题:根据的你的代码逻辑,搜索到"#"后读取文件名,然后移到下一行的开头,然后就开始写文件了?

有一些代码上的问题:

  • 局部变量用let绑定,不要写在函数外面。

  • search-forward的第二个参数为nil时表示在整个buffer搜索,这里需要的应该是在一行内搜索,使用(line-end-position)

  • (region-beginning)和(region-end)是用来获取region的beg和end的position的,一般它赋值给变量,单独写没有意义。建议不要用region,因为从代码看出你对它的概念不是很清晰。

  • find-file是交互函数,一般不在代码中使用,写文件用with-temp-file或write-file。

我的代码:

(defun my-split-yaml-file (file)
  (interactive "fchoose a file: ")
  (let ((filedir "~/test-dir/")
	    filename beg end content) ;; 绑定局部变量
    (with-current-buffer (get-buffer-create "*split-yaml*")
      (insert-file-contents file)
      (goto-char (point-min))
      (while (< (point) (point-max))
	    (if (search-forward "#" (line-end-position) t) ;; 在一行内搜索 "#"
	        (progn
	          (when filename
		        (setq content
		              (string-join
		               (split-string (buffer-substring-no-properties beg end)
				                     "[\n]+" t "[ \f\t\n\r\v]+")
		               "\n")) ;; 获取beg和end之间的内容,去掉每行首部的空格。
		        (with-temp-file (concat filedir filename ".org")
		          (insert content))) ;; 写文件
	          (setq filename (current-word))
	          (next-line)
	          (beginning-of-line)
	          (setq beg (point))) ;; 获取内容的起始位置beg
	      (save-excursion ;; 保存光标的位置:只判断下一行的情况,不改变实际要操作的光标。
	         (next-line)
	         (beginning-of-line)
	         (when (or (search-forward "#" (line-end-position) t)
		               (= (point) (point-max)))
	           (previous-line)
	           (end-of-line)
	           (setq end (point)))) ;; 如果当前行的下一行能够搜索到# 或 下一行到达buffer的尾部(最后一块的情形),则将当前行的结尾位置设为内容的end位置。
	      (next-line)
	      (beginning-of-line))))))
3 个赞

学习了一下午,有两个疑问,一个不懂。

疑问的是 (setq filename (current-word)) 这句写在后面,为何在 (when filename 这句程序就知道filename是什么呢?这个问题真是小白了。

还有一个疑问:说两句话就能搞懂的事情,为何用那么多行语句才能让电脑明白且执行。

不懂的还是正则表达式,这个以后再系统学一下。

非常感谢!

因为filename在let中绑定了。let绑定的局部变量在let块内都可以访问到。

第二个问题,哈哈有意思 :smile:

说说我的理解:因为它是电脑不是人。本质上说,电脑硬件只能理解0和1(高低电平)的区别,通过0,1的组合构成了指令系统,在指令系统上有了更容易理解的汇编语言,高级语言更加接近人的逻辑思维。所以我们应该庆幸有高级语言的存在,不然程序员得回到写汇编的年代,甚至是01代码。再说lisp,实现同等的功能,lisp的代码量要比现在主流的高级语言还要简洁。

再说说函数式编程:把小的功能写成函数,然后就可以在其他的函数里面使用,这是函数式编程。你说的几句话搞定这个问题也不是不可以实现,前提是对于特定的功能有现成的函数实现。但问题是,对于不具有通用性的功能,lisp不可能有现成的函数,所以需要我们用通用的函数来实现这个功能。一旦实现了,我们就可以说:这个问题现在我可以用一行代码来解决,就是直接调用这个函数。同理,更复杂的功能也可以复用我们实现好的函数。

所谓通用的功能就是elisp内置的函数,api。不通用的功能就是我们个性化的需求,比如说你的这个问题。

有点啰嗦,希望对你的理解有帮助。

另外,这个问题里的正则不是重点,只是为了去掉每块内容行首的空格。这是split-string的功能,和之前代码里的是一样的。

是啊,有了这段函数,这个问题就能用一句语句解决了,解决的次数多了,函数也就越来越多,越来越简单了。

文笔真棒,听说文本好的人水平更高,我也在重新学习中文,向你学习。

继续折腾lisp,今天搞定一个正则表达匹配,感觉不错。接着是文本的行操作,学习了李杀网站上的相关内容,粗浅感觉elisp行操作方面的功能比较弱,也许是跟vim相比吧。

想达成的目标:把匹配“某汉字”的行,挪到文件的第二行。代码如下:

(with-temp-buffer

    (insert-file-contents file)
    (goto-char (point-min))

     (search-forward "某汉字" nil t)
    (goto-char (point))
  ;;   (evil-delete)
    (kill-whole-line)
    (goto-char (point-min))
    ;;(evil-paste-pop)
     (insert ?\n)
    ;; (yank)
    (evil-paste-after)
    (write-file file))

问题:

1,kill-whole-line 可以删除当前行,但evil-delete evil-paste-after无法删除和粘贴,evil的命令好像在这里不起作用,用evil的vim的命令更方便一些;

2,yank 和yank-pop会粘贴数据,但是好像yank把kill-ring的数据都保存粘贴了。如何能只粘贴kill-whole-line删除的行呢?

请指教!谢谢!

1.不要用evil,能用elisp内置的函数就不要用第三方库。这里evil函数不起作用是因为evil函数要在evil-mode为major-mode时才能用。

2.elisp确实没有太多直接操作行的内置函数。point,mark和region几乎可以解决所有buffer内文字的处理,行本质上也是一种region,而且关于行的操作也没多少。

3.(yank)默认粘贴的就是最近一次的kill操作,所以是能够只粘贴(kill-whole-line)的内容的。

(defun my-move-matched-text-line (file)
  "move string match line to the second line."
  (interactive "fchoose a file: ")
  (let ((line 2)
	    (str "某汉字"))
    (with-temp-file file
      (insert-file-contents file)
      (goto-char (point-min))
      (search-forward str nil t)
      (kill-whole-line)
      (goto-line line)
      (yank))))

另外,论坛回复是支持markdown语法的。你把代码放在代码块里好看一点。

针对这个问题写了篇文章。其中的思路对理解应该有帮助。