Javascript e regex: divida a string e mantenha o separador

130

Eu tenho uma string:

var string = "aaaaaa<br />&dagger; bbbb<br />&Dagger; cccc"

E eu gostaria de dividir essa string com o delimitador <br /> seguido por um caractere especial.

Para fazer isso, eu estou usando isso:

string.split(/<br \/>&#?[a-zA-Z0-9]+;/g);

Estou conseguindo o que preciso, exceto que estou perdendo o delimitador. Aqui está o exemplo: http://jsfiddle.net/JwrZ6/1/

Como posso manter o delimitador?

Miloš
fonte
se você conhece o delimitador de antemão, por que não apenas ... var delim = "<br/>";?
Andreas Wong
Obrigado @SiGanteng, conheço o delimitador de antemão, mas não posso fazê-lo funcionar no meu exemplo. Eu preciso manter o delimitador para ser <br /> seguido pelo caractere especial, porque às vezes eu posso ter um <br /> não seguido pelo caractere especial e este não precisa ser dividido.
Miloš
2
Boa pergunta, eu tenho um caso semelhante em que conhecer o delimitador não ajuda. Estou dividindo "] e [". Então, na verdade, meu delimitador é "&", mas a divisão não é precisa o suficiente. Preciso colocar os colchetes de ambos os lados para determinar uma divisão adequada. No entanto, preciso desses colchetes nas minhas strings de divisão. 1 em cada lado.
PandaWood

Respostas:

103

Use lookahead (positivo) para que a expressão regular afirme que o caractere especial existe, mas na verdade não corresponde a ele:

string.split(/<br \/>(?=&#?[a-zA-Z0-9]+;)/g);

Veja em ação:

var string = "aaaaaa<br />&dagger; bbbb<br />&Dagger; cccc";
console.log(string.split(/<br \/>(?=&#?[a-zA-Z0-9]+;)/g));

Jon
fonte
Quando uso esse código, ele adiciona um 0no final de cada string
keyboard-warrior
2
Não consigo encontrar nada sobre lookahead positivo no link que você deu.
Paul Chris Jones
@PaulJones, o conteúdo foi movido no intervalo. Obrigado por me informar, eu corrigi o link.
Jon
178

Eu estava tendo um problema semelhante, mas um pouco diferente. De qualquer forma, aqui estão exemplos de três cenários diferentes para onde manter o deliminador.

"1、2、3".split("、") == ["1", "2", "3"]
"1、2、3".split(/(、)/g) == ["1", "、", "2", "、", "3"]
"1、2、3".split(/(?=、)/g) == ["1", "、2", "、3"]
"1、2、3".split(/(?!、)/g) == ["1、", "2、", "3"]
"1、2、3".split(/(.*?、)/g) == ["", "1、", "", "2、", "3"]

Aviso: O quarto só funcionará para dividir caracteres únicos. ConnorsFan apresenta uma alternativa :

// Split a path, but keep the slashes that follow directories
var str = 'Animation/rawr/javascript.js';
var tokens = str.match(/[^\/]+\/?|\//g);
jichi
fonte
3
Eu estava procurando por algo como o terceiro exemplo, mas isso só funciona se os elementos tiverem apenas um caractere - caso contrário, ele será dividido em caracteres individuais. Eu tive que seguir a rota tediosa de RegExp.exec no final.
Gordon
2
Eu não entendo por que todo mundo está usando / g
Salsaparrilha
1
Como usaria esse regex "1、2、3" .split (/ (?! 、) / G) == ["1 、", "2 、", "3"] para palavras completas? Por exemplo "foo1, foo2, foo3"
Waltari
Você é um génio!. onde você encontra a documentação que explica como funciona? você não precisa gdo final
pery mimon
1
Tradução da .matchsolução não gananciosa para estes exemplos: "11、22、33".match(/.*?、|.+$/g)-> ["11、", "22、", "33"]. O /gmodificador de nota é crucial para a correspondência.
Beni Cherniavsky-Paskin
57

Se você colocar o delimitador entre parênteses, ele fará parte da matriz retornada.

string.split(/(<br \/>&#?[a-zA-Z0-9]+);/g);
// returns ["aaaaaa", "<br />&dagger;", "bbbb", "<br />&Dagger;", "cccc"]

Dependendo de qual parte você deseja manter, altere qual subgrupo você corresponde

string.split(/(<br \/>)&#?[a-zA-Z0-9]+;/g);
// returns ["aaaaaa", "<br />", "bbbb", "<br />", "cccc"]

Você pode melhorar a expressão ignorando o caso das letras string.split (/ () & #? [A-z0-9] +; / gi);

E você pode corresponder a grupos predefinidos como este: \d iguais [0-9]e \wiguais [a-zA-Z0-9_]. Isso significa que sua expressão pode ficar assim.

string.split(/<br \/>(&#?[a-z\d]+;)/gi);

Há uma boa referência de expressão regular no JavaScriptKit .

Torsten Walter
fonte
4
Melhor ainda, não sei se podemos manter apenas uma parte do delimitador. Na verdade, eu preciso manter apenas o caractere especial, posso fazer isso com: string.split (/ <br \/> (& #? [A-zA-Z0-9] +;) / g);
Miloš
1
Você pode otimizar sua expressão ignorando o caso das palavras. Ou corresponda a uma classe de caracteres predefinida. Vou atualizar minha resposta.
Torsten Walter
2
Por isso é tão baixo .. Sua perfeita e tão flexível
Tofandel
2
Esta é certamente a maneira mais fácil e a sintaxe mais legível.
Timar Ivo Batis 31/12/19
4

respondeu aqui também JavaScript Split Regular Expression keep the delimiter

use o padrão lookahead (? = pattern) no exemplo regex

var string = '500x500-11*90~1+1';
string = string.replace(/(?=[$-/:-?{-~!"^_`\[\]])/gi, ",");
string = string.split(",");

isso lhe dará o seguinte resultado.

[ '500x500', '-11', '*90', '~1', '+1' ]

Também pode ser dividido diretamente

string = string.split(/(?=[$-/:-?{-~!"^_`\[\]])/gi);

dando o mesmo resultado

[ '500x500', '-11', '*90', '~1', '+1' ]
Fritar
fonte
Por que não se separar imediatamente, como na resposta aceita por Jon?
Gordon
@Gordon ... :) eu pudesse fazer isso ... atualizou o código ... Felicidades
Fry
1

Uma função de extensão divide a string com substring ou RegEx e o delimitador é colocado de acordo com o segundo parâmetro à frente ou atrás.

    String.prototype.splitKeep = function (splitter, ahead) {
        var self = this;
        var result = [];
        if (splitter != '') {
            var matches = [];
            // Getting mached value and its index
            var replaceName = splitter instanceof RegExp ? "replace" : "replaceAll";
            var r = self[replaceName](splitter, function (m, i, e) {
                matches.push({ value: m, index: i });
                return getSubst(m);
            });
            // Finds split substrings
            var lastIndex = 0;
            for (var i = 0; i < matches.length; i++) {
                var m = matches[i];
                var nextIndex = ahead == true ? m.index : m.index + m.value.length;
                if (nextIndex != lastIndex) {
                    var part = self.substring(lastIndex, nextIndex);
                    result.push(part);
                    lastIndex = nextIndex;
                }
            };
            if (lastIndex < self.length) {
                var part = self.substring(lastIndex, self.length);
                result.push(part);
            };
            // Substitution of matched string
            function getSubst(value) {
                var substChar = value[0] == '0' ? '1' : '0';
                var subst = '';
                for (var i = 0; i < value.length; i++) {
                    subst += substChar;
                }
                return subst;
            };
        }
        else {
            result.add(self);
        };
        return result;
    };

O teste:

    test('splitKeep', function () {
        // String
        deepEqual("1231451".splitKeep('1'), ["1", "231", "451"]);
        deepEqual("123145".splitKeep('1', true), ["123", "145"]);
        deepEqual("1231451".splitKeep('1', true), ["123", "145", "1"]);
        deepEqual("hello man how are you!".splitKeep(' '), ["hello ", "man ", "how ", "are ", "you!"]);
        deepEqual("hello man how are you!".splitKeep(' ', true), ["hello", " man", " how", " are", " you!"]);
        // Regex
        deepEqual("mhellommhellommmhello".splitKeep(/m+/g), ["m", "hellomm", "hellommm", "hello"]);
        deepEqual("mhellommhellommmhello".splitKeep(/m+/g, true), ["mhello", "mmhello", "mmmhello"]);
    });
Berezh
fonte
1

Fiz uma modificação na resposta de jichi e a coloquei em uma função que também suporta várias letras.

String.prototype.splitAndKeep = function(separator, method='seperate'){
    var str = this;
    if(method == 'seperate'){
        str = str.split(new RegExp(`(${separator})`, 'g'));
    }else if(method == 'infront'){
        str = str.split(new RegExp(`(?=${separator})`, 'g'));
    }else if(method == 'behind'){
        str = str.split(new RegExp(`(.*?${separator})`, 'g'));
        str = str.filter(function(el){return el !== "";});
    }
    return str;
};

As respostas de jichi O terceiro método não funcionaria nessa função, então eu peguei o quarto método e removi os espaços vazios para obter o mesmo resultado.

edit: segundo método que excede uma matriz para dividir char1 ou char2

String.prototype.splitAndKeep = function(separator, method='seperate'){
    var str = this;
    function splitAndKeep(str, separator, method='seperate'){
        if(method == 'seperate'){
            str = str.split(new RegExp(`(${separator})`, 'g'));
        }else if(method == 'infront'){
            str = str.split(new RegExp(`(?=${separator})`, 'g'));
        }else if(method == 'behind'){
            str = str.split(new RegExp(`(.*?${separator})`, 'g'));
            str = str.filter(function(el){return el !== "";});
        }
        return str;
    }
    if(Array.isArray(separator)){
        var parts = splitAndKeep(str, separator[0], method);
        for(var i = 1; i < separator.length; i++){
            var partsTemp = parts;
            parts = [];
            for(var p = 0; p < partsTemp.length; p++){
                parts = parts.concat(splitAndKeep(partsTemp[p], separator[i], method));
            }
        }
        return parts;
    }else{
        return splitAndKeep(str, separator, method);
    }
};

uso:

str = "first1-second2-third3-last";

str.splitAndKeep(["1", "2", "3"]) == ["first", "1", "-second", "2", "-third", "3", "-last"];

str.splitAndKeep("-") == ["first1", "-", "second2", "-", "third3", "-", "last"];
SwiftNinjaPro
fonte
0

Eu tenho usado isso:

String.prototype.splitBy = function (delimiter) {
  var 
    delimiterPATTERN = '(' + delimiter + ')', 
    delimiterRE = new RegExp(delimiterPATTERN, 'g');

  return this.split(delimiterRE).reduce((chunks, item) => {
    if (item.match(delimiterRE)){
      chunks.push(item)
    } else {
      chunks[chunks.length - 1] += item
    };
    return chunks
  }, [])
}

Exceto que você não deve mexer String.prototype, então aqui está uma versão da função:

var splitBy = function (text, delimiter) {
  var 
    delimiterPATTERN = '(' + delimiter + ')', 
    delimiterRE = new RegExp(delimiterPATTERN, 'g');

  return text.split(delimiterRE).reduce(function(chunks, item){
    if (item.match(delimiterRE)){
      chunks.push(item)
    } else {
      chunks[chunks.length - 1] += item
    };
    return chunks
  }, [])
}

Então você poderia fazer:

var haystack = "aaaaaa<br />&dagger; bbbb<br />&Dagger; cccc"
var needle =  '<br \/>&#?[a-zA-Z0-9]+;';
var result = splitBy(haystack , needle)
console.log( JSON.stringify( result, null, 2) )

E você vai acabar com:

[
  "<br />&dagger; bbbb",
  "<br />&Dagger; cccc"
]
vasilevich
fonte