2009-05-06 4 views
1

J'ai besoin d'écrire une petite fonction de rubis qui fait un emballage de mots. J'ai la fonction suivante:Ruby: Comment implémenter Word Wrap qui ignore les étiquettes <span> lors de la calcul de la longueur de la ligne?

def word_wrap(text, line_width) 
    if line_width.nil? || line_width < 2 
    line_width = 40 
    end 
    text.split("\n").collect do |line| 
    line.length > line_width ? line.gsub(/.{1,#{line_width}})(\s+|$)/, "\\1\n").strip : line 
    end * "\n" 
end 

Ceci est essentiellement la fonction word_wrap inclus dans Rails.

Je voudrais écrire une fonction similaire qui analyse une chaîne avec des éléments span à l'intérieur, sauf que les balises ne doivent pas être comptées pour envelopper la ligne.

Exemple:

s = "Lorem <span>ipsum dolor</span> si<span>t</span> amet, conse<span>ctetur adipiscing elit</span> Praesent" 

Actuellement, word_wrap (s, 20) donne quelque chose comme ceci:

Lorem <span>ipsum 
dolor</span> 
si<span>t</span> 
amet, 
conse<span>ctetur 
adipiscing 
elit</span> Praesent 

Il devrait être:

Lorem <span>ipsum dolor</span> si 
<span>t</span> amet, conse<span>ctetur 
adipiscing elit</span> 
Praesent 

Comme vous pouvez le voir , la nouvelle fonction word_wrap crée des lignes de (max) 20 caractères, sans compter les <span> et </span> étiquettes.

Comment le feriez-vous? Toutes les suggestions sont les bienvenues!

Merci d'avance pour votre aide.

Répondre

2

est ici une solution regex

irb> SPAN_RE = /(?i:<\/?span[^>]*>)/ 
#=> /(?i:<\/?span[^>]*>)/ 
irb> ALL_SPANS_RE = /(?:#{SPAN_RE}*(?!#{SPAN_RE}))/ 
#=> /(?:(?i-mx:<\/?span[^>]*>)*(?!(?i-mx:<\/?span[^>]*>)))/ 
irb> def word_wrap(str,width) 
     full_re = /((?:#{ALL_SPANS_RE}.){0,#{width-1}}#{ALL_SPANS_RE}\S(?:#{SPAN_RE}+|\b))\s*/ 
     str.gsub(/\s*\n/, ' ').gsub(full_re, "\\1\n") 
    end 
#=> nil 
irb> text =<<TEXT 
    Lorem <span>ipsum 
    dolor</span> 
    si<span>t</span> 
    amet, 
    conse<span>ctetur 
    adipiscing 
    elit</span> Praesent 
    TEXT 
#=> "Lorem <span>ipsum\ndolor</span>\nsi<span>t</span>\namet,\nconse<span>ctetur\nadipiscing\nelit</span> Praesent\n" 
irb> puts word_wrap(text,20) 
Lorem <span>ipsum dolor</span> si<span> 
t</span> amet, conse<span>ctetur 
adipiscing elit</span> 
Praesent 
#=> nil 
irb> word_wrap(text,20) 
#=> "Lorem <span>ipsum dolor</span> si<span>\nt</span> amet, conse<span>ctetur\nadipiscing elit</span>\nPraesent\n" 

Fondamentalement, nous avalons autant de caractères que nous pouvons jusqu'à la largeur de mot, sans tenir compte des portées (et en faisant que nous ne saisissez pas partie de la portée) et en nous assurant que se termine par un caractère non-espace, suivi par soit une durée, soit un saut de mot.

Je vais te casser vers le bas comment le regex fonctionne:

SPAN_RE correspond à une balise span (soit <span> ou </span> ou ou ...)

(?i: - Start of a non-capturing parenthesis (useful for grouping patterns) 
      The i flag means the inner pattern is case-insensitive 
    <  - a literal '<' character 
    \/? - 0 or 1 a forward slashes 
    span - the letters "span" 
    [^>]* - 0 or more other characters that are not a '>' character 
    >  - a literal '>' character 
)  - end of the non-capturing parenthesis 

ALL_SPAN_RE matchs tous les travées à une position donnée - en garantissant que le caractère correspondant est et non le début d'une balise span.

(?:    - Start of a non-capturing parenthesis (useful for grouping patterns) 
    #{SPAN_RE}* - 0 or more spans 
    (?!   - Start of a negative lookahead 
    #{SPAN_RE} - exactly 1 span 
        Since this is inside a negative lookahead, it means that the next 
        character in the string is not allowed to start a span 
)    - end of the negative lookahead 
)    - end of the non-capturing parenthesis 

Cela signifie que nous pouvons trouver un caractère après la ALL_SPAN_RE et être sûr que nous ne sommes pas accaparement partie d'une travée.

Le full_re alors juste correspond avidement autant de caractères que possible, jusqu'à la largeur désirée (sans tenir compte des portées), en vous assurant qu'elle se termine sur un caractère non-espace qui est soit la fin d'un mot ou suivi d'une durée.

(     - start of a capturing parenthesis 
    (?:     - start of a non-capturing parenthesis 
    #{ALL_SPANS_RE} - any and all spans 
    .     - one character (which can't be the start of a span) 
)     - end of non-capturing parenthesis 
    {0,#{width-1}}  - match preceding pattern up to width-1 times 
         so this matches width-1 characters (ignoring spans) 
    #{ALL_SPANS_RE}  - any and all spans 
    \S     - a non-whitespace character 
         we don't want to insert a "\n" after whitespace 
    (?:     - a non-capturing parenthesis 
    #{SPAN_RE}+  - 1 or more spans 
    |     - OR 
    \b    - the end of a word 
         these alternatives makes sure we aren't breaking in the middle of a word 
    )     - end of non-capturing parentheis 
)     - end of capturing parenthesis 
\s*     - any whitespace 
         since we're wrapping, we can just toss this when we insert the newline 
+0

c'est joli. – kch

+0

Très impressionnant regexp. J'y suis allé, mais évidemment, je ne maîtrise pas les expressions rationnelles aussi bien que vous. Merci beaucoup pour votre aide. Pour l'information de tous, je posterai le résultat de votre fonction, ce qui est légèrement différent de celui que j'ai posté, mais est tout à fait valable: Lorem ipsum dolor si t amet, conse ctetur adipiscing elit Présentez Merci! –

+0

Rampion, merci beaucoup pour toutes ces explications. C'était vraiment gentil de votre part de prendre le temps d'expliquer votre solution dans les détails. :) –

0

C'est marrant! (quoique a un peu d'odeur de devoirs)

J'ai supprimé mes tentatives précédentes, vous pouvez vérifier l'histoire de cette réponse si curieux. Voici ma réponse définitive, un peu plus longue verticalement, mais plus terre à terre et pas aussi hacky que le précédent.

s = "Lorem <span>ipsum dolor</span> si<span>t</span> amet, conse<span>ctetur adipiscing elit</span> Praesent" 

def word_wrap(s_arg, line_width = 40) 
    producer = s_arg.dup 
    consumer = "" 
    counter = 0 
    while !producer.empty? 
    if producer =~ %r[\A</?span>] 
     consumer << producer.slice!(0, $&.length) 
     next 
    end 
    consumer << producer.slice!(0, 1) 
    counter += 1 
    next if counter <= line_width 
    consumer.sub!(/ (\S*?)\z/, "\n\\1") 
    counter = $1.length 
    end 
    consumer 
end 

puts word_wrap(s, 20) 
+0

Nous vous remercions de votre aide. Cependant, je dois dire que je trouve la solution de rampion plus jolie. En outre, le résultat est pas tout à fait le même que celui prévu: Lorem ipsum dolor \ n si t amet, \ n consé ctetur \ n adipiscing elit \ n Praesent Merci pour les dépenses le temps m'a aidé! –

Questions connexes