Конструкции обратных ссылок в регулярных выражениях

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

Примечание.

Для ссылки на именованные и нумерованные захватываемые группы в строках замены используется отдельный синтаксис. Для получения дополнительной информации см. Подстановки.

.NET определяет отдельные элементы языка для ссылки на нумерованные и именованные захватываемые группы. Дополнительные сведения о группах записи см. в статье Конструкции группирования.

Нумерованные обратные ссылки

Нумерованная обратная ссылка использует следующий синтаксис:

\number

где число — это порядковое положение захватываемой группы в регулярном выражении. Например, \4 соответствует содержимому четвертой захватываемой группы. Если параметр число не определен в шаблоне регулярного выражения, возникает ошибка синтаксического анализа, и обработчик регулярных выражений создает исключение ArgumentException. Например, регулярное выражение \b(\w+)\s\1 является допустимым, поскольку (\w+) — это первая и единственная захватываемая группа в выражении. С другой стороны, выражение \b(\w+)\s\2 недопустимо и создает исключение аргумента, так как захватываемая группа с номером \2 отсутствует. Кроме того, если число определяет захватываемую группу с определенным порядковым номером, но захватываемой группе назначено числовое имя, отличное от ее порядкового номера, то анализатор регулярных выражений также создает исключение ArgumentException.

Следует отметить неоднозначность такой записи, которая может означать как восьмеричные escape-коды (например, \16), так и \число для обратных ссылок. Эта неопределенность разрешается следующим образом:

  • Выражения с \1 по \9 всегда интерпретируются как обратные ссылки, а не как восьмеричные коды.

  • Если первая цифра многоразрядного выражения — 8 или 9 (например, \80 или \91), выражение интерпретируется как литерал.

  • Выражения от \10 и более считаются обратными ссылками, если имеется обратная ссылка, соответствующая этому номеру. В противном случае они интерпретируются как восьмеричные коды.

  • Если регулярное выражение содержит обратную ссылку на неопределенный номер группы, возникает ошибка синтаксического анализа, и обработчик регулярных выражений создает исключение ArgumentException.

Если неоднозначность представляет проблему, можно использовать однозначное представление \k<name>, которое невозможно спутать с восьмеричными кодами символов. Аналогичным образом шестнадцатеричные коды, например \xdd, однозначны, и их нельзя спутать с обратными ссылками.

В приведенном ниже примере в строке выделяются двойные словообразующие символы. Здесь определяется регулярное выражение, (\w)\1, которое состоит из следующих элементов.

Элемент Description
(\w) Совпадение со словообразующим символом и его назначение первой захватываемой группе.
\1 Совпадение со следующим символом, значение которого совпадает с первой захватываемой группой.
using System;
using System.Text.RegularExpressions;

public class Example
{
   public static void Main()
   {
      string pattern = @"(\w)\1";
      string input = "trellis llama webbing dresser swagger";
      foreach (Match match in Regex.Matches(input, pattern))
         Console.WriteLine("Found '{0}' at position {1}.",
                           match.Value, match.Index);
   }
}
// The example displays the following output:
//       Found 'll' at position 3.
//       Found 'll' at position 8.
//       Found 'bb' at position 16.
//       Found 'ss' at position 25.
//       Found 'gg' at position 33.
Imports System.Text.RegularExpressions

Module Example
    Public Sub Main()
        Dim pattern As String = "(\w)\1"
        Dim input As String = "trellis llama webbing dresser swagger"
        For Each match As Match In Regex.Matches(input, pattern)
            Console.WriteLine("Found '{0}' at position {1}.", _
                              match.Value, match.Index)
        Next
    End Sub
End Module
' The example displays the following output:
'       Found 'll' at position 3.
'       Found 'll' at position 8.
'       Found 'bb' at position 16.
'       Found 'ss' at position 25.
'       Found 'gg' at position 33.

Именованные обратные ссылки

Именованная обратная ссылка задается с помощью следующего синтаксиса:

\k<name>

или:

\k'name'

где name— это имя захватываемой группы, определенное в шаблоне регулярного выражения. Если параметр имя не определен в шаблоне регулярного выражения, возникает ошибка синтаксического анализа, и обработчик регулярных выражений создает исключение ArgumentException.

В приведенном ниже примере в строке выделяются двойные словообразующие символы. Здесь определяется регулярное выражение, (?<char>\w)\k<char>, которое состоит из следующих элементов.

Элемент Description
(?<char>\w) Сопоставляется с буквенным символом и назначает его группе записи с именем char.
\k<char> Сопоставляется со следующим символом, значение которого совпадает со значением группы записи char.
using System;
using System.Text.RegularExpressions;

public class Example
{
   public static void Main()
   {
      string pattern = @"(?<char>\w)\k<char>";
      string input = "trellis llama webbing dresser swagger";
      foreach (Match match in Regex.Matches(input, pattern))
         Console.WriteLine("Found '{0}' at position {1}.",
                           match.Value, match.Index);
   }
}
// The example displays the following output:
//       Found 'll' at position 3.
//       Found 'll' at position 8.
//       Found 'bb' at position 16.
//       Found 'ss' at position 25.
//       Found 'gg' at position 33.
Imports System.Text.RegularExpressions

Module Example
    Public Sub Main()
        Dim pattern As String = "(?<char>\w)\k<char>"
        Dim input As String = "trellis llama webbing dresser swagger"
        For Each match As Match In Regex.Matches(input, pattern)
            Console.WriteLine("Found '{0}' at position {1}.", _
                              match.Value, match.Index)
        Next
    End Sub
End Module
' The example displays the following output:
'       Found 'll' at position 3.
'       Found 'll' at position 8.
'       Found 'bb' at position 16.
'       Found 'ss' at position 25.
'       Found 'gg' at position 33.

Именованные числовые обратные ссылки

В именованной обратной ссылке с \kимя также может быть строковым представлением числа. Например, далее используется регулярное выражение (?<2>\w)\k<2> для поиска в строке двойных словообразующих символов. В этом случае в примере определяется захватываемая группа, которая явно названа "2", и обратная ссылка, соответственно названная "2".

using System;
using System.Text.RegularExpressions;

public class Example
{
   public static void Main()
   {
      string pattern = @"(?<2>\w)\k<2>";
      string input = "trellis llama webbing dresser swagger";
      foreach (Match match in Regex.Matches(input, pattern))
         Console.WriteLine("Found '{0}' at position {1}.",
                           match.Value, match.Index);
   }
}
// The example displays the following output:
//       Found 'll' at position 3.
//       Found 'll' at position 8.
//       Found 'bb' at position 16.
//       Found 'ss' at position 25.
//       Found 'gg' at position 33.
Imports System.Text.RegularExpressions

Module Example
    Public Sub Main()
        Dim pattern As String = "(?<2>\w)\k<2>"
        Dim input As String = "trellis llama webbing dresser swagger"
        For Each match As Match In Regex.Matches(input, pattern)
            Console.WriteLine("Found '{0}' at position {1}.", _
                              match.Value, match.Index)
        Next
    End Sub
End Module
' The example displays the following output:
'       Found 'll' at position 3.
'       Found 'll' at position 8.
'       Found 'bb' at position 16.
'       Found 'ss' at position 25.
'       Found 'gg' at position 33.

Если имя является строковым представлением числа и не существует захватываемой группы с таким именем, то \k<имя> совпадает со значением обратной ссылки \номер, где номер — порядковый номер записи. В следующем примере существует одна захватываемая группа с именем char. Конструкция обратной ссылки ссылается на нее как \k<1>. Как видно из результата выполнения примера, вызов Regex.IsMatch завершается успешно, поскольку char является первой группой записи.

using System;
using System.Text.RegularExpressions;

public class Example
{
   public static void Main()
   {
      Console.WriteLine(Regex.IsMatch("aa", @"(?<char>\w)\k<1>"));
      // Displays "True".
   }
}

Imports System.Text.RegularExpressions

Module Example
    Public Sub Main()
        Console.WriteLine(Regex.IsMatch("aa", "(?<char>\w)\k<1>"))
        ' Displays "True".
    End Sub
End Module

Однако, если имя является строковым представлением числа и захватываемой группе на этой позиции явно назначено числовое имя, обработчик регулярных выражений не сможет идентифицировать захватываемую группу по ее порядковому номеру. Вместо этого он создаст исключение ArgumentException. Единственная захватываемая группа в следующем примере имеет имя "2". Поскольку конструкция \k используется для определения обратных ссылок с именем "1", обработчик регулярных выражений не может идентифицировать первую группу записи и вызывает исключение.

using System;
using System.Text.RegularExpressions;

public class Example
{
   public static void Main()
   {
      Console.WriteLine(Regex.IsMatch("aa", @"(?<2>\w)\k<1>"));
      // Throws an ArgumentException.
   }
}

Imports System.Text.RegularExpressions

Module Example
    Public Sub Main()
        Console.WriteLine(Regex.IsMatch("aa", "(?<2>\w)\k<1>"))
        ' Throws an ArgumentException.
    End Sub
End Module

С чем сопоставляются обратные ссылки

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

В примере ниже показан шаблон регулярного выражения (?<1>a)(?<1>\1b)*, который переопределяет именованную группу \1. В следующей таблице описывается каждый шаблон регулярного выражения.

Расписание Description
(?<1>a) Сопоставляется с символом "a" и назначает его значение группе записи с именем 1.
(?<1>\1b)* Сопоставляется с нулем или одним вхождением группы с именем 1 рядом с символом "b" и назначает полученное значение группе записи с именем 1.
using System;
using System.Text.RegularExpressions;

public class Example
{
   public static void Main()
   {
      string pattern = @"(?<1>a)(?<1>\1b)*";
      string input = "aababb";
      foreach (Match match in Regex.Matches(input, pattern))
      {
         Console.WriteLine("Match: " + match.Value);
         foreach (Group group in match.Groups)
            Console.WriteLine("   Group: " + group.Value);
      }
   }
}
// The example displays the following output:
//          Group: aababb
//          Group: abb
Imports System.Text.RegularExpressions

Module Example
    Public Sub Main()
        Dim pattern As String = "(?<1>a)(?<1>\1b)*"
        Dim input As String = "aababb"
        For Each match As Match In Regex.Matches(input, pattern)
            Console.WriteLine("Match: " + match.Value)
            For Each group As Group In match.Groups
                Console.WriteLIne("   Group: " + group.Value)
            Next
        Next
    End Sub
End Module
' The example display the following output:
'          Group: aababb
'          Group: abb

При сравнении регулярного выражения с входной строкой ("aababb") обработчик регулярных выражений выполняет указанные далее операции.

  1. Начинается с первого символа и успешно сопоставляет "a" с выражением (?<1>a). Теперь значение группы 1 будет равно "a".

  2. Перемещается ко второму символу и успешно сопоставляет строку "ab" с выражением \1b или "ab". Затем результат "ab" присваивается \1.

  3. Переходит к четвертому символу. Выражение (?<1>\1b)* должно выдать ноль или больше совпадений, поэтому он успешно сопоставляет строку "abb" с выражением \1b. Затем результат "abb" присваивается \1.

В этом примере * является циклическим квантификатором — он вычисляется многократно до тех пор, пока обработчик регулярных выражений не сможет обнаружить соответствие заданному им шаблону. Квантификаторы циклов не удаляют определения групп.

Если для группы не было найдено ни одной подстроки, то обратная ссылка на эту группу не определена и не работает. Это продемонстрировано в шаблоне регулярного выражения \b(\p{Lu}{2})(\d{2})?(\p{Lu}{2})\b, который определяется следующим образом:

Расписание Description
\b Сопоставление начинается на границе слова.
(\p{Lu}{2}) Совпадение с двумя прописными буквами. Это первая группа записи.
(\d{2})? Совпадение с нулевым или единичным вхождением двух десятичных цифр. Это вторая группа записи.
(\p{Lu}{2}) Совпадение с двумя прописными буквами. Это третья группа записи.
\b Сопоставление заканчивается на границе слова.

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

using System;
using System.Text.RegularExpressions;

public class Example
{
   public static void Main()
   {
      string pattern = @"\b(\p{Lu}{2})(\d{2})?(\p{Lu}{2})\b";
      string[] inputs = { "AA22ZZ", "AABB" };
      foreach (string input in inputs)
      {
         Match match = Regex.Match(input, pattern);
         if (match.Success)
         {
            Console.WriteLine("Match in {0}: {1}", input, match.Value);
            if (match.Groups.Count > 1)
            {
               for (int ctr = 1; ctr <= match.Groups.Count - 1; ctr++)
               {
                  if (match.Groups[ctr].Success)
                     Console.WriteLine("Group {0}: {1}",
                                       ctr, match.Groups[ctr].Value);
                  else
                     Console.WriteLine("Group {0}: <no match>", ctr);
               }
            }
         }
         Console.WriteLine();
      }
   }
}
// The example displays the following output:
//       Match in AA22ZZ: AA22ZZ
//       Group 1: AA
//       Group 2: 22
//       Group 3: ZZ
//
//       Match in AABB: AABB
//       Group 1: AA
//       Group 2: <no match>
//       Group 3: BB
Imports System.Text.RegularExpressions

Module Example
    Public Sub Main()
        Dim pattern As String = "\b(\p{Lu}{2})(\d{2})?(\p{Lu}{2})\b"
        Dim inputs() As String = {"AA22ZZ", "AABB"}
        For Each input As String In inputs
            Dim match As Match = Regex.Match(input, pattern)
            If match.Success Then
                Console.WriteLine("Match in {0}: {1}", input, match.Value)
                If match.Groups.Count > 1 Then
                    For ctr As Integer = 1 To match.Groups.Count - 1
                        If match.Groups(ctr).Success Then
                            Console.WriteLine("Group {0}: {1}", _
                                              ctr, match.Groups(ctr).Value)
                        Else
                            Console.WriteLine("Group {0}: <no match>", ctr)
                        End If
                    Next
                End If
            End If
            Console.WriteLine()
        Next
    End Sub
End Module
' The example displays the following output:
'       Match in AA22ZZ: AA22ZZ
'       Group 1: AA
'       Group 2: 22
'       Group 3: ZZ
'       
'       Match in AABB: AABB
'       Group 1: AA
'       Group 2: <no match>
'       Group 3: BB

См. также