web-dev-qa-db-fra.com

comment utiliser une expression régulière d'une ligne pour obtenir le contenu correspondant

Je suis un débutant chez Ruby, je veux savoir si je peux utiliser une seule ligne pour faire le travail.

Prenez la 'recherche' de ce site par exemple. Lorsque l'utilisateur a tapé [Ruby] regex, je peux utiliser le code suivant pour obtenir la balise et le mot clé

'[Ruby] regex' =~ /\[(.*?)\](.*)/
tag, keyword = $1, $2

Pouvons-nous l'écrire en une seule ligne?


METTRE À JOUR

Merci beaucoup! Puis-je rendre la chose plus difficile et plus intéressante, que l'entrée puisse contenir plus d'une balise, comme:

[Ruby] [regex] [Rails] one line

Est-il possible d'utiliser un code de ligne pour obtenir le tableau de balises et le mot-clé? J'ai essayé mais j'ai échoué.

29
Freewind

Vous avez besoin de la méthode Regexp#match. Si vous écrivez /\[(.*?)\](.*)/.match('[Ruby] regex'), ceci retournera un objet MatchData. Si nous appelons cet objet matches, alors, entre autres choses:

  • matches[0] renvoie la chaîne complète correspondante.
  • matches[n] renvoie le nième groupe de capture ($n).
  • matches.to_a renvoie un tableau composé de matches[0] à matches[N].
  • matches.captures renvoie un tableau composé uniquement du groupe de capture (matches[1] à matches[N]).
  • matches.pre_match renvoie tout ce qui précède la chaîne correspondante.
  • matches.post_match renvoie tout ce qui suit la chaîne correspondante.

Il y a plus de méthodes, qui correspondent à d'autres variables spéciales, etc. vous pouvez consulter la documentation de MatchData pour en savoir plus. Ainsi, dans ce cas précis, tout ce que vous devez écrire est

tag, keyword = /\[(.*?)\](.*)/.match('[Ruby] regex').captures

Edit 1: Très bien, pour votre tâche la plus difficile, vous allez plutôt vouloir utiliser la méthode String#scan, qui @Theo used; Cependant, nous allons utiliser une expression régulière différente. Le code suivant devrait fonctionner:

# You could inline the regex, but comments would probably be Nice.
tag_and_text = / \[([^\]]*)\] # Match a bracket-delimited tag,
                 \s*          # ignore spaces,
                 ([^\[]*) /x  # and match non-tag search text.
input        = '[Ruby] [regex] [Rails] one line [foo] [bar] baz'
tags, texts  = input.scan(tag_and_text).transpose

La input.scan(tag_and_text) retournera une liste de paires tag-search-text:

[ ["Ruby", ""], ["regex", ""], ["Rails", "one line "]
, ["foo", ""], ["bar", "baz"] ]

L’appel transpose le retourne, de sorte que vous avez une paire composée d’une liste de balises et d’une liste de textes de recherche:

[["Ruby", "regex", "Rails", "foo", "bar"], ["", "", "one line ", "", "baz"]]

Vous pouvez ensuite faire ce que vous voulez avec les résultats. Je pourrais suggérer, par exemple

search_str = texts.join(' ').strip.gsub(/\s+/, ' ')

Cela concaténera les extraits de recherche avec des espaces uniques, supprimera les espaces de début et de fin et remplacera les exécutions de plusieurs espaces par un seul espace.

43
'[Ruby] regex'.scan(/\[(.*?)\](.*)/)

reviendra

[["Ruby", " regex"]]

vous pouvez en savoir plus sur String # scan ici: http://Ruby-doc.org/core/classes/String.html#M000812 (en bref, il retourne un tableau de toutes les correspondances consécutives, le tableau extérieur de cette case est le tableau de correspondances et le groupe interne correspond aux groupes de capture de la correspondance).

pour faire l’affectation, vous pouvez la réécrire comme ceci (en supposant que vous n’ayez jamais qu’une seule correspondance dans la chaîne):

tag, keyword = '[Ruby] regex'.scan(/\[(.*?)\](.*)/).flatten

en fonction de exactement ce que vous voulez accomplir, vous voudrez peut-être changer la regex en 

/^\s*\[(.*?)\]\s*(.+)\s*$/

qui correspond à toute la chaîne d'entrée et limite certains espaces du deuxième groupe de capture. Ancrer le motif au début et à la fin le rendra un peu plus efficace, et évitera dans certains cas d'obtenir des correspondances fausses ou dupliquées (mais cela dépend beaucoup de l'entrée) - cela garantit également que vous pouvez utiliser en toute sécurité le résultat renvoyé. tableau en affectation, car il n'y aura jamais plus d'une correspondance.

En ce qui concerne la question suivante, voici ce que je ferais:

def tags_and_keyword(input)
  input.scan(/^\s*\[(.+)\]\s+(.+)\s*$/) do |match|
    tags = match[0].split(/\]\s*\[/)
    line = match[1]
    return tags, line
  end
end

tags, keyword = tags_and_keyword('[Ruby] [regex] [Rails] one line')
tags # => ["Ruby", "regex", "Rails"]
keyword # => "one line"

il peut être réécrit en une ligne, mais je ne le ferais pas:

tags, keyword = catch(:match) { input.scan(/^\s*\[(.+)\]\s+(.+)\s*$/) { |match| throw :match, [match[0].split(/\]\s*\[/), match[1]] } }

Ma solution suppose que toutes les balises viennent avant le mot-clé et qu'il n'y a qu'une balise/expression de mot-clé dans chaque entrée. La première capture englobe toutes les balises, mais ensuite, j'ai scindé cette chaîne. Il s’agit donc d’un processus en deux étapes (qui, comme @Tim l’a écrit dans son commentaire, est obligatoire sauf si vous disposez d’un moteur capable de faire une correspondance récursive).

11
Theo

Mettez ceci dans votre ApplicationHelper ou ailleurs

def element_id_for(f, element)
  matcher   = /id=(".*"|'.*')/
  el_string = f.hidden_field(element.to_sym)
  id_string = matcher.match(el_string)[0].gsub(/id="/, '').chomp('"')
  return    id_string
end

Enfin, vous pouvez utiliser cette méthode comme ceci:

form_for :test_form do |f|
  my_id = element_id_for(f, :start_date)
  # => "text_form_start_date"
end
0
mld.oscar