Récupère toutes les correspondances d'expression rationnelle dans le tampon sous forme de liste

18

Sur le site de Code Golf Stack Exchange aujourd'hui, j'ai trouvé cette réponse dans Clojure à la question "Obtenez tous les liens sur une page Web".

(->> (slurp "http://www.stroustrup.com")
     (re-seq #"(?:http://)?www(?:[./#\+-]\w*)+"))

Sans la macro fantaisie, c'est juste ceci:

(re-seq #"(?:http://)?www(?:[./#\+-]\w*)+" (slurp "http://www.stroustrup.com"))

Cela renvoie la liste:

("http://www.morganstanley.com/" "http://www.cs.columbia.edu/" "http://www.cse.tamu.edu" ...)

Puis-je faire quelque chose de similaire dans Emacs Lisp?

Peut-être qu'une fonction comme (re-seq regexp (buffer-string))celle-là revient '(firstmatch secondmatch thirdmatch ...)?

nounou
la source
C'est ce qui M-x occurfait, mais je regarderais à l'intérieur pour plus de fonctions de bas niveau pour le faire.
wvxvw
@wvxvw C'est un bon point, je n'y ai même pas pensé occur. Je vais devoir regarder à travers sa source.
nounou
J'ai regardé à l'intérieur, et oh malheur, ce code en fait trop et ce n'est pas facile de le réutiliser, pas du tout. Mon prochain candidat serait s.el, mais peut-être y a-t-il plus. Ici: github.com/magnars/s.el#s-match-strings-all-regex-string que diriez-vous?
wvxvw du

Réponses:

16

Voici comment vous pouvez le faire en fonction des chaînes, comme demandé.

(defun re-seq (regexp string)
  "Get a list of all regexp matches in a string"
  (save-match-data
    (let ((pos 0)
          matches)
      (while (string-match regexp string pos)
        (push (match-string 0 string) matches)
        (setq pos (match-end 0)))
      matches)))

; Sample URL
(setq urlreg "\\(?:http://\\)?www\\(?:[./#\+-]\\w*\\)+")
; Sample invocation
(re-seq urlreg (buffer-string))
Alan Shutko
la source
Cela ne semble pas tout à fait complet, pourriez-vous étendre cela à une réponse pleinement fonctionnelle?
wasamasa
1
Le code était complet, mais j'ai également ajouté un exemple d'utilisation. Qu'aimeriez-vous voir d'autre?
Alan Shutko
1
Cette solution est malheureusement trop simple. Essayez (re-seq "^.*$" ""). Expression valide, chaîne valide, mais elle ne se termine jamais.
Phil Lord
8

Il est probablement intéressant de noter que l'invocation occuravec l'argument universel entraîne le remplissage du *Occur*tampon avec uniquement des correspondances - aucun nom de fichier, numéro de ligne ou information d'en-tête. Lorsqu'il est combiné avec un groupe de capture, cela permet d'extraire le motif souhaité.

Par exemple, C-u M-x occursuivi de \"\(.*\)\"invite l'utilisateur à sélectionner le groupe de capture à collecter (par défaut \1), puis place le contenu de chaque chaîne entre guillemets dans le *Occur*tampon.

Jack Rusher
la source
5

J'ai une réponse lisp emacs à cette question publiée: /codegolf//a/44319/18848

En utilisant la même structure (while (search) (print)), vous pouvez la modifier en fonction pour pousser les correspondances dans un tampon vers une liste et la retourner comme ceci:

(defun matches-in-buffer (regexp &optional buffer)
  "return a list of matches of REGEXP in BUFFER or the current buffer if not given."
  (let ((matches))
    (save-match-data
      (save-excursion
        (with-current-buffer (or buffer (current-buffer))
          (save-restriction
            (widen)
            (goto-char 1)
            (while (search-forward-regexp regexp nil t 1)
              (push (match-string 0) matches)))))
      matches)))
Jordon Biondo
la source
Bonne réponse, notez que vous voudrez peut-être remplacer match-stringpar match-string-no-propertiespour que la coloration syntaxique ne soit pas extraite. Vous voudrez peut-être passer un regexp-group-indexà utiliser pour pouvoir choisir le texte à stocker. En plus d'inverser l'ordre de recherche (la liste actuelle est la dernière à la première). Voir cette réponse qui inclut une version modifiée emacs.stackexchange.com/a/38752/2418
ideasman42
3

Son utilisation s.elaurait été plus courte, mais malheureusement, cela donne trop de correspondances:

(defun all-urls-in-buffer ()
  (s-match-strings-all
   "\\(?:http://\\)?www\\(?:[./#+-]\\w*\\)+"
   (buffer-string)))

Si cela est correct (l'expression rationnelle des URL n'est pas parfaite de toute façon), cela pourrait être plus court, sinon, je ne pense pas que je pourrais le rendre plus court que la réponse d'Alan Shutko.

wvxvw
la source
2

Permettez-moi de mentionner pourquoi je pense que cela n'est pas implémenté dans le noyau. Tout simplement pour des raisons d'efficacité: il n'est pas nécessaire de copier, créer des listes, les transmettre et les récupérer. Au lieu de cela, stockez la chaîne entière comme tampon et opérez avec des limites de correspondance entières. C'est ainsi que cela occurfonctionne, par exemple: il correspond à une chaîne à la fois et insère la correspondance dans *occur*. Il ne correspond pas à toutes les chaînes à la fois, les place dans la liste, fait une boucle dans la liste pour l'insérer *occur*et récupérer la liste et ses chaînes.

Tout comme vous n'écririez pas (do (def x 1) (def x (+ 2 x)))dans Clojure, vous ne devriez pas essayer par défaut de faire en sorte qu'Elisp se comporte comme un langage fonctionnel. J'adorerais que ce soit le cas, mais nous devons nous en tenir à ce que nous avons en ce moment.

abo-abo
la source
1

Si je peux me permettre un plug, jetez un oeil à ma bibliothèque "m-buffer".

(m-buffer-match buffer "foo")

Renvoie une liste de marqueurs auxquels correspond foo.

Phil Lord
la source