Регулярное выражение не соответствует частичным последовательностям, но соответствует полным

У меня есть некоторый экранированный HTML:

<img border='0' />

Я пытаюсь сопоставить и заменить полные escape-последовательности, такие как ', но не частичные, например 39, так как 39 на самом деле не находится в неэкранированной строке. По сути, каждая escape-последовательность должна обрабатываться как один токен.

Это регулярное выражение JS. Есть ли способ исключить совпадения между & и ;, продолжая принимать последовательности, которые включают оба этих символа?

Желаемые результаты:

  • Поиск <img border='0' /> для lt: нет соответствия.
  • Поиск <img border='0' /> для 39: Нет соответствия.
  • Поиск <img border='0' /> для ': соответствие.
  • Поиск <img border='0' /> для border=': соответствие.

Текущий код:

> var str = '<img border='0' />'
> str.replace(/(border)/gi, '|$1|')
'<img |border|='0' />'  // ok
> str.replace(/(39)/gi, '|$1|')
'<img border=&#0|39|;0&#0|39|; />'  // not ok

Примечание: Я не могу unescape, а затем повторно убежать, чтобы соответствовать. Его нужно избегать.

Ответ 1

OP хочет, чтобы регулярное выражение JavaScript соответствовало и заменяло строку в escaped HTML при обработке управляющих последовательностей (например, <, ' или ) в виде отдельных символов, а не unescape строку HTML во время замены процесс.

Это означает, что замена

  • "lt" с "[lt]" в "< lt" приведет к "< [lt]" (избегайте совпадения внутри объекта)
  • "<" с "[<]" в "< lt" приведет к "[<] lt" (совпадению сущности)
  • "&l" с "[&l]" в "< &lt" приведет к "< [&l]t" (не соответствует частичной сущности)
  • "t;" с "[t;]" в "< lt;" приведет к "< l[t;]" (не соответствует частичной сущности)
  • "< l" с "[< l]" в "< lt" приведет к "[< l]t" (совпадению, включая сущность)
  • "lt; &l" с "[lt; &l]" в "< &lt" приведет к "< &lt" (не соответствует частичной сущности)
  • "t; <" с "[t; <]" в "lt; <" приведет к "l[t; <]" (совпадению, включая сущность)
  • "t; &lt" с "[t; &lt]" в "lt; <" приведет к "lt; <" (не соответствует частичной сущности)

С помощью следующего регулярного выражения для захвата экранированных последовательностей (например, <, ' или ),

/&[a-z]+;|&#x[a-f\d]+;|&#\d+;/gi

мы можем использовать следующую функцию в качестве отправной точки, которая обрабатывает большинство вышеперечисленных случаев (# 1, # 2, # 4, # 5 и # 7):

function searchAndReplace(searchFor, replacement, str) {
  return str.replace(
    new RegExp(
      prepare(searchFor) + 
      "|(&[a-z]+;|&#x[a-f\\d]+;|&#\\d+;)", // consume entities
      "gi"
    ),
    function(m, entity) {
      return entity || replacement;
    }
  );
}

function prepare(str) {
  return str.replace(/[^\w\s]/g, "\\$&"); //escape regex metachars [1]
}

// [1] from http://eloquentjavascript.net/09_regexp.html#h_Rhu25fogrG

Остальные случаи (# 3, # 6, # 8) включают потенциальную частичную экранированную последовательность в конце строки поиска.

Решение для этого состоит в том, чтобы проверить строку searchFor для возможных частично экранированных последовательностей в конце и добавить соответствующий отрицательный lookahead (?!), чтобы предотвратить сопоставление действительной escape-последовательности. Полное решение (передача набора из примерно 40 тестовых примеров) показано ниже и должно быть более быстрым и менее сложным, чем подход .exec():

function searchAndReplace(searchFor, replacement, str) {
  return str.replace(
    new RegExp(
      prepare(searchFor) + 
      "|(&[a-z]+;|&#x[a-f0-9]+;|&#\\d+;)", 
      "gi"
    ),
    function(m, entity) {
      return entity || replacement;
    }
  );
}

function prepare(str) {
  var add = "";
  if (/&$/.test(str)) {
    add = "(?!#x[a-z\\d]+;|#\\d+;|[a-z]+;)";
  } else if (/&[a-z]+$/i.test(str)) {
    add = "(?![a-z]*;)";
  } else if (/&#$/.test(str)) {
    add = "(?!x[a-f\\d]+;|\\d+;)";
  } else if (/&#x$/.test(str)) {
    add = "(?![a-f\\d]+;)";
  } else if (/&#x[a-f\d]+$/i.test(str)) {
    add = "(?![a-f\\d]*;)";
  }
  return str.replace(/[^\w\s]/g, "\\$&") + add;
}

// test function

function test(searchFor, replacement, str, expected) {
  var result = searchAndReplace(searchFor, replacement, str);
  console.log(
    searchFor +
      ": " +
      (result === expected ? "Passed" : "Failed: " + [expected, result])
  );
}

// test cases

test("lt", "[lt]", "<img border='0' />", "<img border='0' />");
test("39", "[39]", "<img border='0' />", "<img border='0' />");
test("'", "[']", "<img border='0' />", "<img border=[']0['] />");
test("border='", "[border=']", "<img border='0' />", "<img [border=']0' />");
test("39&", "[39&]", "39<img border=39'&gt&gt&&#039 t; 0'&39; />", "39<img border=39'&gt&gt&&#039 t; 0'&39; />")
test("0&#", "[0&#]", "39<img border=39'&gt&gt&&#039 t; 0'&39; />", "39<img border=39'&gt&gt&&#039 t; 0'&39; />")
test("lt", "[]", "&lt<t;t&l", "&[]<t;t&l");
test("<", "[]", "&lt<t;t&l", "&lt[]t;t&l");
test("&l", "[]", "&lt<t;t&l", "[]t<t;t[]");
test("t;", "[]", "&lt<t;t&l", "&lt<[]t&l");
test("t&", "[]", "&lt<t;t&l", "&lt<t;[]l");
test("<t", "[]", "&lt<t;t&l", "&lt[];t&l");
test("t<", "[]", "&lt<t;t&l", "&l[]t;t&l");
test("t;t", "[]", "&lt<t;t&l", "&lt<[]&l");
test("t&l", "[]", "&lt<t;t&l", "&lt<t;[]");
test("39", "[]", "&#039'9;9&#", "&#0[]'9;9&#");
test("'", "[]", "&#039'9;9&#", "&#039[]9;9&#");
test("&", "[]", "&#039'9;9&#", "[]#039'9;9[]#");
test("&#", "[]", "&#039'9;9&#", "[]039'9;9[]");
test("9;", "[]", "&#039'9;9&#", "&#039'[]9&#");
test("9&", "[]", "&#039'9;9&#", "&#039'9;[]#");
test("'9", "[]", "&#039'9;9&#", "&#039[];9&#");
test("9'", "[]", "&#039'9;9&#", "&#03[]9;9&#");
test("9;9", "[]", "&#039'9;9&#", "&#039'[]&#");
test("9&#", "[]", "&#039'9;9&#", "&#039'9;[]");
test("x7", "[]", "߿f&#x", "&#[]ff;f&#x");
test("", "[]", "߿f&#x", "&#x7f[]f;f&#x");
test("&", "[]", "߿f&#x", "[]#x7ff;f[]#x");
test("&#", "[]", "߿f&#x", "[]x7ff;f[]x");
test("&#x", "[]", "߿f&#x", "[]7ff;f[]");
test("&#x7", "[]", "߿f&#x", "[]ff;f&#x");
test("f;", "[]", "߿f&#x", "&#x7f[]f&#x");
test("f&", "[]", "߿f&#x", "߿[]#x");
test("f", "[]", "߿f&#x", "&#x7f[];f&#x");
test("f", "[]", "߿f&#x", "&#x7[]f;f&#x");
test("f;f", "[]", "߿f&#x", "&#x7f[]&#x");
test("f&#", "[]", "߿f&#x", "߿[]x");
test("f&#x", "[]", "߿f&#x", "߿[]");
test("t; < lt &l", "[]", "< < lt <lt; < lt &lt", "< < lt <l[]t");

Ответ 2

Один из вариантов заключается в том, чтобы временно заменить строку, которую нужно найти, с помощью строки "dummy", где бы она не появлялась в escape-последовательности символов до фактической замены. Строка "dummy" должна быть чем-то, что вряд ли появится в любом месте HTML. После выполнения фактической замены последующая замена может быть выполнена, чтобы изменить строку "dummy" на искомую строку.

Ниже приведена демонстрация этого метода в действии, который дает запрошенные результаты. Он использует этот полезный метод, когда требуется глобальная замена, которая не использует регулярное выражение, и этот полезный метод для преобразования любой строки в строковый литерал для использования в регулярном выражении (с любыми специальными символами, экранированными соответственно).

var html = "<img border='0' />"
replaceInHtml(html, 'lt', 'replacement');
replaceInHtml(html, '39', 'replacement');
replaceInHtml(html, ''', 'replacement');
replaceInHtml(html, 'border='', 'replacement');

function replaceInHtml(html, str, replacement) {
  // A unique string that is unlikely to appear in the HTML
  var dummyStr = '!*&$^£"^';

  var strInRegex = escapeRegExp(str);
  var dummyRegex = new RegExp('(&[#a-zA-Z0-9]*)'
      + strInRegex + '([#a-zA-Z0-9]*;)', 'g');

  var replaced = html.replace(dummyRegex, '$1' + dummyStr + '$2');
  replaced = replaced.split(str).join(replacement);
  replaced = replaced.split(dummyStr).join(str);
  console.log('Source:  ' + html
          + '\nReplace: ' + str
          + '\nWith:    ' + replacement
          + '\nGives:   ' + replaced);
}

function escapeRegExp(str) {
  return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
}

Ответ 3

Я начал с сопоставления всего, что между & и ;:

let str = "39<img border=39'0'&39; />39";
let search = '39';
let regexp = new RegExp('&[^&;]*?(' + search + ')[^&;]*?;', 'g'); // /&[^&;]*?(SEARCH)[^&;]*?;/g
let match = str.match(regexp);

console.log(match);

Ответ 4

RegEx должен использоваться для проверки этого , но он не может охватить все возможные объекты и не лучший инструмент для этого задания. Хотя ниже метод будет работать для всех объектов HTML.

Я пытаюсь сопоставить и заменить полные escape-последовательности, такие как ', но не частичные, например 39, так как 39 на самом деле не находится в неэкранированной строке.

В принципе, вы хотите заменить HTML-объекты своей нераскрытой формой. Это то, что ниже функция делает это, для этого вам не нужен RegEx.

Я буду использовать функцию unescapeHTML из этого из Web_Designer

var escape = document.createElement('textarea');

function unescapeHTML(html) {
    escape.innerHTML = html;
    return escape.textContent;
}

Сначала создайте новый элемент <textarea>. Внутренняя функция, строка, переданная как аргумент, затем назначается как innerHTML этого текстового поля и затем возвращает textContent. Это трюк, используемый для unescape HTML-объектов.

Мы можем повторно использовать это, чтобы определить, является ли строка допустимым объектом HTML или нет. Если функция способна отменить ее, то она является действительной HTML-сущностью, иначе это не так. Это то, что вы хотите определить.

var escape = document.createElement('textarea');

function unescapeHTML(html) {
  escape.innerHTML = html;
  return escape.textContent;
}

var str = '&lt;img border=&#039;0&#039; /&gt;';

console.log(unescapeHTML('lt') !== 'lt');
console.log(unescapeHTML('39') !== '39');
console.log(unescapeHTML('&#039;') !== '&#039;');
console.log(unescapeHTML('border=&#039;') !== 'border=&#039;');

Ответ 5

Есть ли способ исключить совпадения между & и ;, все еще принимая последовательности, которые включают оба этих символа? По сути, каждая escape-последовательность должна обрабатываться как один токен.

Чтобы рассматривать объекты как отдельные токены, мы можем создать регулярное выражение, которое захватывает объекты перед любой целевой подстрокой, а затем использовать функцию обратного вызова для возврата захваченных объектов, не модифицированных в строку.

Пример, заменив "39", когда он не находится внутри объекта:

str.replace(
  /(&[a-z]+;|&#[0-9a-f]+;)|39/gi,
  function(m, entity){
    return entity || replacement;
  }
);

Я пытаюсь сопоставить и заменить полные escape-последовательности, такие как &#039;, но не частичные, например 39

При замене объектов, таких как &#039;, требуется другой подход. Следующая рабочая демонстрация обрабатывает это, а также динамически генерирует регулярные выражения из предоставленных строк поиска, обрабатывая все тестовые примеры OP:

function searchAndReplace(str, searchFor, replacement){
    return /^&([a-z]+|#[\da-f]+);/i.test(searchFor) ?
      // if searchFor equals or starts with an entity
      str.split(searchFor).join(replacement) :
      // else
      str.replace(
        new RegExp(
          '(&[a-z]+;|&#[0-9a-f]+;)|' + 
          searchFor.replace(/[^\w\s]/g, "\\$&"), //escape metachars
          'gi'
        ),
        function(m, entity){
          return entity || replacement;
        }
      );   
}

// test cases

console.log('Search for "border": \n' + searchAndReplace(
  '&lt;img border=&#039;0&#039; /&gt;', 
  'border', '{{border}}'
) + '\nmatch'); //matches

console.log('Search for "0": \n' + searchAndReplace(
  '&lt;img border=&#039;0&#039; /&gt;', 
  '0', '{{0}}'
) + '\nmatch'); //matches outside entities

console.log('Search for "&#039;": \n' + searchAndReplace(
  '&lt;img border=&#039;0&#039; /&gt;', 
  '&#039;', '{{&#039;}}'
) + '\nmatch'); //matches

console.log('Search for "39": \n' + searchAndReplace(
  '&lt;img border=&#039;0&#039; /&gt;', 
  '39', '{{39}}'
) + '\nno match'); //does not match

console.log('Search for "lt": \n' + searchAndReplace(
  '&lt;img border=&#039;0&#039; /&gt;', 
  'lt', '{{lt}}'
) + '\nno match'); //does not match

console.log('Search for "&lt;": \n' + searchAndReplace(
  '&lt;img border=&#039;0&#039; /&gt;', 
  '&lt;', '{{&lt;}}'
) + '\nmatch'); //matches

console.log('Search for "border=&#039;": \n' + searchAndReplace(
  '&lt;img border=&#039;0&#039; /&gt;', 
  'border=&#039;', '{{border=&#039;}}'
) + '\nmatch'); //matches

console.log('Search for "&lt;img": \n' + searchAndReplace(
  '&lt;img border=&#039;0&#039; /&gt;', 
  '&lt;img', '{{&lt;img}}'
) + '\nmatch'); //matches

Ответ 6

Я думаю, что вы имеете в виду не захватывающие группы: http://www.regular-expressions.info/brackets.html, который легко обрабатывается несколькими сообщениями о переполнении стека (Почему группа регулярных выражений "не захватывает" не работает и Регулярное выражение, только группа, не захватывает "; похоже, не работает).

А именно, незахваченные группы не получают собственный селектор группы (например,/a (?: [XZ]) ([ac])/g будет соответствовать "aZb", но \1 будет равно "b", а не "Z".

Ответ 7

Это то, что вы пытаетесь сделать?

var str = "&lt;img border-color=&#039;0&#039;"
console.log(str)
console.log(str.match(/((?:[a-z-]+=)?&#.+?;)/gi))
console.log(str.replace(/((?:[a-z-]+=)?&#.+?;)/gi, "|$1|"))

Ответ 8

Я думаю, что было бы возможно, если бы мы смогли использовать обратные обратные вызовы. Учитывая, что аромат регулярного выражения - JavaScript, здесь я не думаю, что мы можем. Это довольно близко: [^&;]*(string)[^&;]*(?!9;|t;|;)

Ответ 9

Конечная версия Кандидат

4/29

Эта версия должна обрабатывать частичный объект в конце строки поиска
где частичная имеет символы пред-сущности, такие как xxx&yyy или a&#00 и т.д.

Это был последний случай, обнаруженный @TomasLangkaas.
Учитывая, что все остальные случаи были рассмотрены, это последний кандидат на выпускной экзамен
для @athancahill или кого-то еще заинтересованного.

(См. комментарии и предыдущие версии)

Модель была изменена с String.Replace() до while (match = Rx.exec())

Объясняется здесь, но см. JS-код для реализации.
Он по-прежнему использует строку поиска в качестве первого чередования с сущностью как второй.

    (?=
         # This is the optional entity captured at
         # the same position where the search string starts.
         # If this entity matches, it means the search string
         # matches. Either one may be a partial of the other.

         # (1) The container for pre-entity / entity
         (                             
              # (2) Pre-entity characters 
              ( sLongest )                

              # Entity   
              (?:&(?:[a-z_:][a-zd_:.-]*|(?:\#(?:[0-9]+|x[0-9a-f]+)))|%[a-z_:][a-zd_:.-]*);
         )?                            
    )

    # (3) The search string ( consumes )
    ( sToFind )                        
 | 

    # (4) Or, the entity last  ( consumes ) 
    ( (?:&(?:[a-z_:][a-zd_:.-]*|(?:\#(?:[0-9]+|x[0-9a-f]+)))|%[a-z_:][a-zd_:.-]*); )

Будьте предупреждены, вы не можете просто разбить синтаксис сущностей как часть регулярного выражения.
Он должен быть полностью сопоставлен, как отдельный элемент (был по этой дороге сто раз, это не может быть сделано..).

Обратите внимание, что это однопроходное, чистое регулярное выражение и очень быстрое.
Если вы выберете все комментарии, это действительно всего несколько строк кода.
Вы можете изменить субъект сущности и использовать все, что хотите.
Структура кода не потребуется изменять.

//=========================================================
// http://jsfiddle.net/b4b28a38/95/
//=========================================================

// ------------------------
// These are only used when pre-entity partials are detected
var RxEntPartial = new RegExp( '(?:&(?:[a-z_:][a-zd_:.-]*|(?:\#(?:[0-9]+|x[0-9a-f]*)?))?|%(?:[a-z_:][a-zd_:.-]*)?)$', 'ig' );
var RxEntFull = new RegExp( '(?:&(?:[a-z_:][a-zd_:.-]*|(?:\#(?:[0-9]+|x[0-9a-f]+)))|%[a-z_:][a-zd_:.-]*);', 'ig' );
// ------------------------

function MakeRegex( FindAry ) {
   // Escape metachars
     var longest = 0;
     for (var i = 0; i < FindAry.length; i++ )
     {
         if ( FindAry[i].length > longest )
            longest = FindAry[i].length;
         FindAry[i] = FindAry[i].replace(/(?!\s)\W/g, "\\$&"); 
     }
   // Make 'longest' sub-expression
     longest -= 1; 
     var sLongest = '';
     if ( longest > 0 )
         sLongest = '.{0,' + longest.toString() + '}?';
   // Join array using alternations
     var sToFind = FindAry.join('|');
   // Return new regex object
     var rx =  new RegExp( '(?=((' + sLongest + ')(?:&(?:[a-z_:][a-zd_:.-]*|(?:\#(?:[0-9]+|x[0-9a-f]+)))|%[a-z_:][a-zd_:.-]*);)?)(' + sToFind + ')|((?:&(?:[a-z_:][a-zd_:.-]*|(?:\#(?:[0-9]+|x[0-9a-f]+)))|%[a-z_:][a-zd_:.-]*);)',  
   'ig');
   //console.log( rx);
   return rx;
}  


function GetReplace( str, Rx )
{
   var sResult = '';    // New modified string to return
   var _M;              // Match object
   var ndxLast = 0;     // Previous Rx.lastIndex value when valid match
                        // ( where next match starts )

   Rx.lastIndex = 0;
   
   while ( _M = Rx.exec( str ) )
   {
       // Alternation 1: (1) = container (optiopnal), p2 = pre-entity, entity, p3 = search string
       // Alternation 2: p4 = entity
       // Form:      
       //     (?=
       //          (                    # (1) start container
       //            ( pre-entity )            # (2)
       //            entity
       //          )?                       # (1) end
       //     )
       //     ( search )                 # (3)
       //  |  
       //     ( entity )                 # (4)
       
       if ( _M[4] )
       {
          // Entity, continue unchanged.
          sResult += str.substr( ndxLast , _M.index - ndxLast ) + _M[4];
          ndxLast = Rx.lastIndex;
          continue;
       }
       // Check if entity container captured inside zero length assertion matched 
       if ( _M[1] )
       {
           // Get some lengths 
      
           var L1 = _M[1].length;
           var L2 = _M[2].length;
           var L3 = _M[3].length;

           if ( L1 == L3 )
           {
              // Ok - This means it matched a trailing full entity
              // Intended, modify the search string
              sResult += str.substr( ndxLast , _M.index - ndxLast ) + '[' + _M[3] + ']' ;
              ndxLast = Rx.lastIndex;
              continue;
           }

           // Pre entity check  ( pre-entity ) 
           if ( L2 > 0 )  
           {
               // This is a rare case and should not slow anything down.
               // End entity condition to check

               var sMatched = _M[3];
               var mpartial;
               RxEntPartial.lastIndex = 0;

               // Verify the match had a partial entity at the end
               if ( mpartial = RxEntPartial.exec( sMatched ) )
               {
                   // Check partial entity is not at the  beginning                   
                   if ( mpartial.index > 0 )
                   {
                       // Location in target string to check
                       // for a full entity.
                       var loc = _M.index + mpartial.index;

                       // Assure there is no full entity
                       RxEntFull.lastIndex = loc;
                       var mfull;
                       if ( mfull = RxEntFull.exec( str ) )
                       {
                           if ( mfull.index == loc )
                           {
                               // Not valid, move past it
                               RxEntFull.lastIndex = 0;
                               Rx.lastIndex += (L1 - L3);
                               continue;
                           }
                       }
                  }
               }
               // Ok - This definitely passes.
               // Intended, modify the search string
               sResult += str.substr( ndxLast , _M.index - ndxLast ) + '[' + _M[3] + ']' ;
               ndxLast = Rx.lastIndex;
               continue;
           }

           // Normal checks
           // -------------------------------------------------------

           // If the length of the search >= the entity length
           // then the search includes an entity at the begining
       

           if ( L3 >= L1 )
           {
              // Intended, modify the search string
              sResult += str.substr( ndxLast , _M.index - ndxLast ) + '[' + _M[3] + ']' ;
              ndxLast = Rx.lastIndex;
              continue;
           }

          // Uh oh, the search is a partial entity (from the beginning).
          // Since we see that it is part of an entity, we have to go past it.
          // The match position reflects the partial entity.
          // Adjust (advance) the match position by the difference
          // to go past the entity.

          Rx.lastIndex += ( L1 - L3 );
          continue;
       }

       // Here, the search string is pure, just modify it
       sResult += str.substr( ndxLast , _M.index - ndxLast ) + '[' + _M[3] + ']' ;
       ndxLast = Rx.lastIndex;
   }
   sResult += str.substr( ndxLast , str.length - ndxLast );
   return sResult;
}

var TargetStr = "39&lt;img border=39&#039;&gt&gt&&#039 t; xx&gt x&gt; 0&# r&#039;&39; cad&#092; r&#FFd0&#22 /&gtttttt;  39; end";
console.log( 'Target:\r\n' + TargetStr );

// Always put the longest first of/if alphabetically sorted (ie. aaa|aa|a, etc ..)

var rx = MakeRegex( ['39', '&lt;img', 'cad&#092;', '&gt', 't;', 'x&', '0&#', 'r&#'] );

var NewString = GetReplace( TargetStr, rx );

console.log('Find any of:  39, &lt;img, cad&#092;, &gt, t;, x&, 0&#, r&#' );
console.log( NewString );

Ответ 10

ОБНОВЛЕНИЕ 2017-04-28
добавление тестового примера 39& и 0&# - никакого изменения кода не требуется - ничего себе, это повезло:)

Важно отметить, что Im намеренно не разрешает амперсанд существовать в тексте, который нужно искать, за исключением того, что начинается escape-последовательность, независимо от того, является ли его допустимой escape-последовательность или нет, то есть im, позволяя &39; быть escape-последовательности, хотя технически недействительным. Это позволяет легко сказать, что если нас попросят найти строку с амперсандом, которая не является частью полной escape-последовательности (т.е. Как 0&#') then this should not be matched, and is an invalid search string. The use of AmpExcape "ниже, чтобы сделать это, а не прекращать и возвращать неизмененную строку, удобство, так как другие варианты использования этого регулярного выражения (т.е. вне JavaScript) не позволяют мне условные ветвящиеся операторы (или обратные вызовы функций на совпадениях на этот счет). Для моей цели это закрывает определение Im, работающее против для экранированного HTML.

ОБНОВЛЕНИЕ 2017-04-26
добавление тестового примера &g и редактирование ответа/кода для этого случая путем экранирования строки поиска

ОБНОВЛЕНИЕ 2017-04-25

Решение с двумя проходами:

function do_replace(test_str, find_str, replace_str) {
        let escaped_find_str = AmpExcape(find_str);
        escaped_find_str = RegExcape(escaped_find_str);
        let escaped_replace_str = RegExcape(replace_str);

        let first_regex = new RegExp('(' + escaped_find_str + ')|(&\\w*;|&#[0-9a-fA-F]*;)','gi');
        let first_pass = test_str.replace(first_regex,'&&$1'+replace_str+'&&$2');
        let second_regex = new RegExp('&&(?:'+escaped_replace_str+'&&|(' + escaped_find_str + ')?('+escaped_replace_str + ')?&&)','gi');
        let second_pass = first_pass.replace(second_regex,'$2');

        return second_pass;
}

Я искал подход, который использовал только регулярное выражение, и в то время как решения @sln и @tomas-langkaas были полезны (ну, разумея), мне все еще нужен метод, который я мог бы использовать в JS и далее.

У меня есть что-то, что работает для моих тестовых случаев, но оно вынуждено делать несколько проходов над текстом. Первоначально у меня было три прохода, но я думаю, что у меня есть работа с 2-мя проходами. Очевидно, что они не так эффективны, как другие ответы.

Ive пытается сопоставить: <target string> | <entity>, как и эти ответы, и перевернул заказ, чтобы быть сущностью сначала, как @sln в последнем обновлении. Но im использует && как "новую" escape-последовательность. Стратегия такова:

  • Изначально мы избегаем строк поиска и замены для их использования в регулярном выражении. для <target string>, если есть &, который не является частью объекта-компилятора, тогда мы заменяем его двойным && с целью предотвращения его соответствия всегда, т.е. Im, рассматривая поиск частичной сущности для никогда не будет возможным в экранированном HTML

  • первый проход заменяет любые соответствия (любых сущностей или целевых строк) с помощью

    &&<target group value><replacement string>&&<entity group value>
    
  • в соответствии с целевой строкой <entity group value> будет пустым, и мы вернем escape &&, целевую строку, заменяющую строку и окончательный && escape

  • в совпадении объекта <target group value> теперь будет пустым, поэтому мы вернемся к &&<replacement str>&&, за которым следует значение сущности.
  • второй проход может искать все вхождения &&<target string><replacement string>&& и заменять на <replacement string>
  • также во втором проходе мы можем искать &&<replacement string>&& и знать, что он должен быть заменен пустым.
  • нам не нужно ничего делать для совпадений сущностей на этот раз, поскольку мы оставили их нетронутыми в проходе 1

Вот полный код с тестовыми примерами (атрибуция для RexExcape поддерживается с @tomas-langkaas):

// helper for building regexes from strings
// http://stackoverflow.com/a/3561711/6738706
function AmpExcape(str) {
        return str.replace(/&(\w*;|#[0-9a-fA-F]*;)|(&)/g, '&$2$1');
}
function RegExcape(str) {
        return str.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
};
function do_replace(test_str, find_str, replace_str) {
        let escaped_find_str = AmpExcape(find_str);
        escaped_find_str = RegExcape(escaped_find_str);
        let escaped_replace_str = RegExcape(replace_str);

        let first_regex = new RegExp('(' + escaped_find_str + ')|(&\\w*;|&#[0-9a-fA-F]*;)','gi');
        let first_pass = test_str.replace(first_regex,'&&$1'+replace_str+'&&$2');
        let second_regex = new RegExp('&&(?:'+escaped_replace_str+'&&|(' + escaped_find_str + ')?('+escaped_replace_str + ')?&&)','gi');
        let second_pass = first_pass.replace(second_regex,'$2');

        return second_pass;
}
let str = '39&lt;img style=&#039;width: 39;&#039; bor9;wder=39&#039;0&#039;&39; /&gt;39;';
let test_list = ['39','39;','9;','9;w','39&','0&#'];
run_test(str,test_list);

str = '&lt;img border=&#039;0&#039; /$gt;';
test_list = ['lt','39','&#039;','border=&#039;'];
run_test(str,test_list);

str = 'test string ring ring';
test_list = ['ring'];
run_test(str,test_list);

str = '39&lt;img style=&#039;width: 39;&#039; border=&#039;0&#039;&39; /&gt;39;';
test_list = ['border','0','&#039;','39','lt','&lt;','border=&#039;','&lt;img','&g','t;'];
run_test(str,test_list);

function run_test(base_str, find_list) {

        let orig_str = 'original';

        let max_len = find_list.concat(orig_str).reduce(function(a,b) {
                return a > b.length ? a : b.length;
        },0);
        console.log();
        console.log(pad(orig_str,max_len) + ': ' + str);

        find_list.map(function(gstr) {
                console.log( pad( gstr, max_len) + ': ' + do_replace(str, gstr, '|' + gstr + '|'));
        });
}
function pad(str,len) {
        while ( str.length < len) { str = str + ' ' };
        return str;
}

и выход

original: 39&lt;img style=&#039;width: 39;&#039; bor9;wder=39&#039;0&#039;&39; /&gt;39;
39      : |39|&lt;img style=&#039;width: |39|;&#039; bor9;wder=|39|&#039;0&#039;&39; /&gt;|39|;
39;     : 39&lt;img style=&#039;width: |39;|&#039; bor9;wder=39&#039;0&#039;&39; /&gt;|39;|
9;      : 39&lt;img style=&#039;width: 3|9;|&#039; bor|9;|wder=39&#039;0&#039;&39; /&gt;3|9;|
9;w     : 39&lt;img style=&#039;width: 39;&#039; bor|9;w|der=39&#039;0&#039;&39; /&gt;39;
39&     : 39&lt;img style=&#039;width: 39;&#039; bor9;wder=39&#039;0&#039;&39; /&gt;39;
0&#     : 39&lt;img style=&#039;width: 39;&#039; bor9;wder=39&#039;0&#039;&39; /&gt;39;

original     : &lt;img border=&#039;0&#039; /$gt;
lt           : &lt;img border=&#039;0&#039; /$gt;
39           : &lt;img border=&#039;0&#039; /$gt;
&#039;       : &lt;img border=|&#039;|0|&#039;| /$gt;
border=&#039;: &lt;img |border=&#039;|0&#039; /$gt;

original: test string ring ring
ring    : test st|ring| |ring| |ring|

original     : 39&lt;img style=&#039;width: 39;&#039; border=&#039;0&#039;&39; /&gt;39;
border       : 39&lt;img style=&#039;width: 39;&#039; |border|=&#039;0&#039;&39; /&gt;39;
0            : 39&lt;img style=&#039;width: 39;&#039; border=&#039;|0|&#039;&39; /&gt;39;
&#039;       : 39&lt;img style=|&#039;|width: 39;|&#039;| border=|&#039;|0|&#039;|&39; /&gt;39;
39           : |39|&lt;img style=&#039;width: |39|;&#039; border=&#039;0&#039;&39; /&gt;|39|;
lt           : 39&lt;img style=&#039;width: 39;&#039; border=&#039;0&#039;&39; /&gt;39;
&lt;         : 39|&lt;|img style=&#039;width: 39;&#039; border=&#039;0&#039;&39; /&gt;39;
border=&#039;: 39&lt;img style=&#039;width: 39;&#039; |border=&#039;|0&#039;&39; /&gt;39;
&lt;img      : 39|&lt;img| style=&#039;width: 39;&#039; border=&#039;0&#039;&39; /&gt;39;
&g           : 39&lt;img style=&#039;width: 39;&#039; border=&#039;0&#039;&39; /&gt;39;
t;           : 39&lt;img style=&#039;width: 39;&#039; border=&#039;0&#039;&39; /&gt;39;

ОБНОВЛЕНИЕ 2017-04-24

Я отказался от подхода ниже по нескольким причинам, но, самое главное, он найдет только первое вхождение строки поиска в пробеге текста, который не имеет амперсандов или полуколоней. Например, строка test string ring ring будет соответствовать только test st|ring| ring ring, если совпадение соответствует ring - это кажется бесполезным для поиска и замены aa - Im обновляет ответ, поэтому он работает для соответствия ring по крайней мере первого когда я раньше пропустил начало строки как разрешенный символ терминала, но я не считаю это допустимым решением для всех возможных текстов.

ОРИГИНАЛЬНЫЙ ОТВЕТ (с изменениями)

Если вы заботитесь о том, что в тексте, отличном от символа терминала, в качестве символа терминала, в качестве символа терминала используется символ "двоеточие", например, для встроенных стилей, где он может сказать style="width: 39;", вам нужно что-то немного сложное:

'((?:^|(?!(?:[^&;]+' + str + ')))(?=(?:(?:&|;|^)[^&;]*))(?:(?!(?:&))(?:(?:^|[;]?)[^&;]*?))?)(' + str + ')'

В приведенном ниже коде будут представлены исходные тестовые примеры:

"original":      &lt;img border=&#039;0&#039; /$gt;
"lt":            &lt;img border=&#039;0&#039; /$gt;
"39":            &lt;img border=&#039;0&#039; /$gt;
"&#039;":        &lt;img border=|&#039;|0|&#039;| /$gt;
"border=&#039;": &lt;img |border=&#039;|0&#039; /$gt;

Он также показывает конкретный результат против полуколоней, появляющихся в тексте и поисковых терминах.

"original":      39&lt;img style=&#039;width: 39;&#039; bor;der=39&#039;0&#039;&39; /&gt;39;
test string that may be followed by semi-colon :
|39|&lt;img style=&#039;width: |39|;&#039; bor;der=|39|&#039;0&#039;&39; /&gt;|39|;
test match with semi-colon:
39&lt;img style=&#039;width: |39;|&#039; bor;der=39&#039;0&#039;&39; /&gt;|39;|
test match with semi-colon mid string
39&lt;img style=&#039;width: 39;&#039; |bor;der|=39&#039;0&#039;&39; /&gt;39;

ПРИМЕЧАНИЕ И это пример того, где подход разваливается:

"original":      test string ring ring
test st|ring| ring ring

Нет никакого искажения, которое позволило бы эффекту "памяти" различать нулевые символы после предыдущего совпадения и нулевые символы, но в пределах экранированной последовательности. Таким образом, было бы невозможно сопоставить второе вхождение ring без соответствия 39 в строке &#039;

Вот пример кода:

function make_regex(str) {
  let regexp = new RegExp('((?:^|(?!(?:[^&;]+' + str + ')))(?=(?:(?:&|;|^)[^&;]*))(?:(?!(?:&))(?:(?:^|[;]?)[^&;]*?))?)(' + str + ')','gi');
  return regexp;
}

function do_replace(test_str, find_str, replace_str) {
        let new_reg = make_regex(find_str);
        return  test_str.replace(new_reg,'$1' + replace_str);
}

let str = '39&lt;img style=&#039;width: 39;&#039; bor;der=39&#039;0&#039;&39; /&gt;39;';
console.log();
console.log('"original":     ', str);
console.log('test string that may be followed by semi-colon :');
console.log(do_replace(str, '39', '|$2|' ));
console.log('test match with semi-colon:');
console.log(do_replace(str, '39;', '|39;|' ));
console.log('test match with semi-colon mid string');
console.log(do_replace(str, 'bor;der', '|bor;der|' ))

str = '&lt;img border=&#039;0&#039; /$gt;';
console.log();
console.log('"original":     ', str);
console.log('"lt":           ', do_replace(str, 'lt', '|lt|' ));
console.log('"39":           ', do_replace(str, '39', '|39|' ));
console.log('"&#039;":       ', do_replace(str, '&#039;', '|&#039;|' ));
console.log('"border=&#039;":', do_replace(str, 'border=&#039;', '|border=&#039;|' ));
str = 'test string ring ring';
console.log();
console.log('"original":     ', str);
console.log(do_replace(str, 'ring', '|$2|')); 

Важно отметить, что регулярное выражение захватывает не только текст, который вы хотите, но и кусок текста перед этим. Это влияет на использование $1 в качестве замещающего значения, поскольку текст, который вы хотите, теперь $2

Объяснение терминов не просто, но его можно разбить на:

Отрицательный lookahead, включая строку, чтобы найти, что предотвращает начало сопоставления для нетерминальных символов

(?:^|(?!(?:[^&;]+' + str + ')))

Положительный результат, который заставляет совпадение начинаться с символа терминала или начала строки

(?=(?:(?:&|;|^)[^&;]*))

Отрицательный взгляд, который предотвращает совпадение с началом на &, но позволяет начать линию или предыдущую полуколону

(?:(?!(?:&))(?:(?:^|[;]?)[^&;]*?))?

И наконец, строка, соответствующая

Эффект заключается в том, что мы сопоставляем весь раздел текста, который содержит текст, который мы хотим, от предыдущего значения терминала, до конца строки для соответствия. Из-за этого javascript заканчивается совпадениями, которые соответствуют полному фрагменту. Например, когда мы попросим border=&#039;, мы получим кусок ;img border=&#039;. Таким образом, мы определяем две группы захвата, одну для части, которую мы не интересуем, и одну для нашего матча. Это позволяет нам использовать $1$2 для воссоздания строки или $1whatever для замены только той части, которую мы хотим. Затем мы можем использовать str.replace() с этой стратегией

Ответ 11

это поможет вам наверняка

pattern=new RegExp('\&\#.*?\;',"g") str="&lt;img border=&#039;0&#039; /&gt;" str.replace(pattern,"yourReplacementText")