2

I'm a newbie elisp user. I would like to process a few hundred xml files and call an elisp function for each line which does an query-replace to fix a problem in these files.

My question is: how do I write the modified lines back to the file?

Subquestion: currently the lines are not changed to upper case - can you give me a hint why this does not work?

(defun fix-xml-file (filePath)
  "Fix xml file"
    (with-temp-file **filePath**  <<-- Edit: the file path was missing here!
      (insert-file-contents filePath)
      ... do work here ... ) )

(defun fix-xml-folder (folderPath)
  "Fix all xml files in folder"
  (interactive "folder: ")
  (require 'find-lisp)
  (mapcar 'fix-xml-file (find-lisp-find-files folderPath "\\.xml$") ) )
  • @kaushalmodi how did you get the syntax highlighting to work? – Beginner Mar 19 '15 at 12:35
  • Click on the edit button in your question and you'll see. ;-) – Malabarba Mar 19 '15 at 13:09
  • @Malabarba got it, compare does not work... <!-- language: lang-el --> – Beginner Mar 19 '15 at 13:10
  • @Beginner Yes, you'll need to hit edit on the question and then edit on the last revision to see that. The diff hides the html comments. – Kaushal Modi Mar 19 '15 at 17:15
1

with-temp-file writes buffer to FILE. So on it's own it should do the trick. However it requires FILE to be passed as an argument before BODY so that it knows what file to write. Based on your code above, the following should upcase the entire file.

(defun fix-xml-file (filepath)
  (with-temp-file filepath
    (insert-file-contents filepath)
    (upcase-region (point-min)
                   (point-max))))

For other purposes, replace (upcase-region ...) with an appropriate modification.

For a regex-replace you will probably want something similar to:

(unless (eobp)
  (re-search-forward "string to change" (point-max) 'noerror)
  (replace-match "replace with this"))
2

Here are two apporaches one uses only emacs, and the other also uses shell.

In both cases it is usefull to separate the problem in two:

  1. write a defun which process a single file
  2. get the list of files and apply the defun to each file

Also I wouldn't insert the file contents in the temp buffer. I would open the file, process it, and the save it -- so that I only need file name once.

A. Using Lisp only

A.1. defun which process a single file

(defun my-process-file (fpath)
  "process the file at fullpath FPATH ..."
  (let (mybuffer)
    (setq mybuffer (find-file fpath))

    (goto-char (point-min)) ;; in case buffer is open
    (while (search-forward-regexp "REGEXP" nil t)
      (replace-match "SUBSTITUTION" nil t)) ;; if you need to reference things like (string-match 1) use: t nil

    (save-buffer)
    (kill-buffer mybuffer)))

A.2. process files

Do:

(require 'find-lisp)
(mapc 'my-process-file (find-lisp-find-files "/path/to/dir/" "\\.xml$"))

B. Using shell + elisp

B.1. script which process a single file

Make the following shell script my-process-file.bash:

#! /bin/sh
":"; exec emacs --no-site-file --script "$0" -- "$@" # -*- mode:emacs-lisp -*-

;; emacs options:
(setq make-backup-files nil)
(setq next-line-add-newlines nil)

;; open file:
(find-file (nth 1 argv))

;; process file:
(goto-char (point-min))
(while (search-forward-regexp "REGEXP" nil t)
  (replace-match "SUBSTITUTION" nil t)) ;; if you need to reference things like (string-match 1) use: t nil

;; save file:
(save-buffer)

(do not alter first two lines)

B.2. command to process files

Apply it to xml files:

find . -type f -name "*.xml" | parallel my-process-file.bash {}
  • The shell scripting tip is nice! Can you explain what the parts of the line beginning with ":"; exec emacs does? – Beginner Mar 19 '15 at 12:33
  • @Beginner: first two lines send the whole script to emacs. The start of the second line: ":"; makes the most of the second line a comment in emacs (and the whole second line is a valid elisp), while still keeping the line a valid shell expression. As far as I remember that is an explanation that was given where I picked the trick up. – Adobe Mar 19 '15 at 12:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.