2008-11-30 4 views
8

J'ai besoin d'aide pour remplacer tous les caractères \ n (nouvelle ligne)
dans une chaîne, mais pas ceux \ nà l'intérieur [code] [/ code] tags. Mon cerveau brûle, je ne peux pas résoudre ce problème en mon propre :(Regex pour remplacer tout n dans une chaîne, mais pas ceux dans la balise [code] [/ code]

Exemple:

test test test 
test test test 
test 
test 

[code]some 
test 
code 
[/code] 

more text 

devrait être:.

test test test<br /> 
test test test<br /> 
test<br /> 
test<br /> 
<br /> 
[code]some 
test 
code 
[/code]<br /> 
<br /> 
more text<br /> 

Merci pour votre temps Les meilleurs

salutations.
+1

Je suis un peu surpris de la profondeur de la discussion que cette question apparemment simple a généré. Upvote. – dmckee

+0

Ceci est incroyablement facile dans .NET regex ... tant pis c'est java :( –

+0

Je vous le dis, tout sauf simple :) –

Répondre

7

Je suggérerais un analyseur (simple), et non une expression régulière Quelque chose comme ceci (mauvais pseudocode):

stack elementStack; 

foreach(char in string) { 
    if(string-from-char == "[code]") { 
     elementStack.push("code"); 
     string-from-char = ""; 
    } 

    if(string-from-char == "[/code]") { 
     elementStack.popTo("code"); 
     string-from-char = ""; 
    } 

    if(char == "\n" && !elementStack.contains("code")) { 
     char = "<br/>\n"; 
    } 
} 
6

Vous avez balisé la question regex, mais ce n'est peut-être pas le meilleur outil pour ce travail.

Vous pourriez utiliser les techniques de compilation de base du compilateur (c'est-à-dire une lexeur alimentant un simple analyseur de machine d'état).

Votre lexer identifierait cinq jetons: ("[code]", '\ n', "[/ code]", EOF,: toutes les autres chaînes :) et votre machine d'état ressemble:

 
state token action 
------------------------ 
begin :none: --> out 
out  [code] OUTPUT(token), --> in 
out  \n  OUTPUT(break), OUTPUT(token) 
out  *  OUTPUT(token) 
in  [/code] OUTPUT(token), --> out 
in  *  OUTPUT(token) 
*  EOF  --> end 

EDIT: Je vois d'autres affiches discutant du besoin éventuel d'imbrication des blocs. Cette machine d'état ne gérera pas cela. Pour les blocs d'imbrication, utilisez un analyseur correct récursif (pas tout à fait si simple mais toujours assez facile et extensible).

EDIT: Axeman note que cette conception exclut l'utilisation de "[/ code]" dans le code. Un mécanisme d'échappement peut être utilisé pour battre cela. Quelque chose comme ajouter «\» à vos jetons et ajouter:

 
state token action 
------------------------ 
in  \  -->esc-in 
esc-in *  OUTPUT(token), -->in 
out  \  -->esc-out 
esc-out *  OUTPUT(token), -->out 

à la machine d'état.

Les arguments habituels en faveur des lexers générés par machine et des parseurs s'appliquent.

+0

Ce n'est pas trop mauvais, mais il ne permet pas au code d'utiliser la chaîne " [/ code] ", ou avoir cette valeur dans les commentaires. Cependant, certains d'entre nous se sont habitués à écrire '' dans JavaScript. Pourtant, il ne laisserait pas le code juste être le code. – Axeman

+0

Assez vrai. Mais l'OP n'a pas identifié de mécanisme d'échappement pour le bloc de code. "Oh, quelle toile enchevêtrée nous tissons, quand nous pratiquons d'abord la conception de la langue." Ou quelque chose comme ça. – dmckee

1

Pour obtenir ce droit, vous avez vraiment besoin de faire trois passes:

  1. Trouver des blocs [code] et les remplacer par un jeton unique + index (sauvegarde du bloc d'origine), par exemple, « foo [code ] abc [/ code] bar [code] efg [/ code] "devient" foo TOKEN-1 barTOKEN-2 "
  2. Remplacez votre nouvelle ligne.
  3. Recherchez des jetons d'échappement et restaurez le bloc d'origine.

Le code ressemble à quelque chose comme *:

Matcher m = escapePattern.matcher(input); 
while(m.find()) { 
    String key = nextKey(); 
    escaped.put(key,m.group()); 
    m.appendReplacement(output1,"TOKEN-"+key); 
} 
m.appendTail(output1); 
Matcher m2 = newlinePatten.matcher(output1); 
while(m2.find()) { 
    m.appendReplacement(output2,newlineReplacement); 
} 
m2.appendTail(output2); 
Matcher m3 = Pattern.compile("TOKEN-(\\d+)").matcher(output2); 
while(m3.find()) { 
    m.appendReplacement(finalOutput,escaped.get(m3.group(1))); 
} 
m.appendTail(finalOutput); 

C'est la façon rapide et sale. Il y a des manières plus efficaces (d'autres ont mentionné l'analyseur/lexers), mais à moins que vous ne traitiez des millions de lignes et que votre code soit lié au CPU (plutôt que lié aux E/S, comme la plupart des webapps) C'est le goulot d'étranglement, ils ne valent probablement pas le coup.

* Je ne l'ai pas exécuté, tout est de la mémoire. Il suffit de vérifier le API et vous serez en mesure de le régler.

+0

Vous avez raison sur le coût d'écriture d'un lexeur/analyseur, mais ils évoluent bien avec la complexité de l'énoncé du problème. Et ça pourrait devenir plus gros que ça. – dmckee

2

Comme mentionné par d'autres affiches, les expressions régulières ne sont pas le meilleur outil pour le travail car elles sont presque universellement implémentées comme des algorithmes gourmands. Cela signifie que même si vous avez essayé de faire correspondre les blocs de code en utilisant quelque chose comme:

(\[code\].*\[/code\]) 

Ensuite, l'expression correspondra tout de la première balise [code] à la dernière balise [/code], ce qui est clairement pas ce que vous voulez. Bien qu'il existe des moyens de contourner ce problème, les expressions régulières qui en résultent sont généralement fragiles, non intuitives et carrément laides. Quelque chose comme le code python suivant fonctionnerait beaucoup mieux.

output = [] 
def add_brs(str): 
    return str.replace('\n','<br/>\n') 
# the first block will *not* have a matching [/code] tag 
blocks = input.split('[code]') 
output.push(add_brs(blocks[0])) 
# for all the rest of the blocks, only add <br/> tags to 
# the segment after the [/code] segment 
for block in blocks[1:]: 
    if len(block.split('[/code]'))!=1: 
     raise ParseException('Too many or few [/code] tags') 
    else: 
     # the segment in the code block is pre, everything 
     # after is post 
     pre, post = block.split('[/code]') 
     output.push(pre) 
     output.push(add_brs(post)) 
# finally join all the processed segments together 
output = "".join(output) 

Notez le code ci-dessus était pas testé, il est juste une idée approximative de ce que vous aurez besoin de faire.

+0

Pour ce cas d'utilisation, il n'y aura pas de blocs [code] imbriqués, donc un quantificateur réticent s'en charge. par exemple, "\ [code \]. *? \ [\\ code]" s'arrêtera dès qu'il rencontrera "[/ code]" – noah

+0

Vous avez un peu tort sur la description de l'expression rationnelle (comme l'a souligné @noah), mais le Python a l'air bien (du moins en théorie). – strager

+0

Ce n'est pas une mauvaise façon de gérer ce problème, mais il ne généralisera pas facilement si le problème devient beaucoup plus compliqué. +1 de toute façon. – dmckee

3

Cela semble le faire:

private final static String PATTERN = "\\*+"; 

public static void main(String args[]) { 
    Pattern p = Pattern.compile("(.*?)(\\[/?code\\])", Pattern.DOTALL); 
    String s = "test 1 ** [code]test 2**blah[/code] test3 ** blah [code] test * 4 [code] test 5 * [/code] * test 6[/code] asdf **"; 
    Matcher m = p.matcher(s); 
    StringBuffer sb = new StringBuffer(); // note: it has to be a StringBuffer not a StringBuilder because of the Pattern API 
    int codeDepth = 0; 
    while (m.find()) { 
     if (codeDepth == 0) { 
      m.appendReplacement(sb, m.group(1).replaceAll(PATTERN, "")); 
     } else { 
      m.appendReplacement(sb, m.group(1)); 
     } 
     if (m.group(2).equals("[code]")) { 
      codeDepth++; 
     } else { 
      codeDepth--; 
     } 
     sb.append(m.group(2)); 
    } 
    if (codeDepth == 0) { 
     StringBuffer sb2 = new StringBuffer(); 
     m.appendTail(sb2); 
     sb.append(sb2.toString().replaceAll(PATTERN, "")); 
    } else { 
     m.appendTail(sb); 
    } 
    System.out.printf("Original: %s%n", s); 
    System.out.printf("Processed: %s%n", sb); 
} 

ne est pas un simple regex mais je ne pense pas que vous pouvez faire ce que vous voulez avec une expression régulière simple. Pas avec la manipulation des éléments imbriqués et ainsi de suite.

+0

Bon, mais je doute que les balises [code] puissent être imbriquées (en BBCode standard, au moins). – PhiLho

+1

True, mais le point est l'algorithme peut être modifié pour gérer l'imbrication arbitraire correctement. – cletus

1

C'est difficile parce que si les expressions rationnelles sont bonnes pour trouver quelque chose, elles ne sont pas très bonnes pour faire correspondre tout sauf quelque chose ... Donc vous devez utiliser une boucle, je doute que vous puissiez le faire en une fois. Après la recherche, j'ai trouvé quelque chose de proche de la solution de Cletus, sauf que je suppose que le bloc de code ne peut pas être imbriqué, ce qui conduit à un code plus simple: choisissez ce qui est adapté à vos besoins.

import java.util.regex.*; 

class Test 
{ 
    static final String testString = "foo\nbar\n[code]\nprint'';\nprint{'c'};\n[/code]\nbar\nfoo"; 
    static final String replaceString = "<br>\n"; 
    public static void main(String args[]) 
    { 
    Pattern p = Pattern.compile("(.+?)(\\[code\\].*?\\[/code\\])?", Pattern.DOTALL); 
    Matcher m = p.matcher(testString); 
    StringBuilder result = new StringBuilder(); 
    while (m.find()) 
    { 
     result.append(m.group(1).replaceAll("\\n", replaceString)); 
     if (m.group(2) != null) 
     { 
     result.append(m.group(2)); 
     } 
    } 
    System.out.println(result.toString()); 
    } 
} 

Test rapide brut, vous avez besoin de plus (chaîne vide, vide, pas de code, multiple, etc.).

Questions connexes