Должно ли регулярное выражение, используемое для разбиения строк, учитывать проблему unix / dos? - PullRequest
1 голос
/ 16 декабря 2011

Мне не хотелось использовать XML для входного файла моего T4, поэтому я создал этот фрагмент, который разбивает документ на куски, разделенные пустой строкой.

Правильно ли я здесь возвращаю возврат каретки?

string s = @"Default
Default

CurrencyConversion
Details of currency conversions.

BudgetReportCache
Indicates wheather the budget report is taken from query results or cache.";

string oneLine = @"[\r]\n";
string twoLines = @"[\r]\n[\r]\n";

var chunks = Regex.Split(s, twoLines, RegexOptions.Multiline);

var items = chunks.Select(c=>Regex.Split(c, oneLine, RegexOptions.Multiline)).ToDictionary(c=>c[0], c=>c[1]);

Примечание: я бы никогда не подумал об этом, но с тех пор, как я начал использовать Git, я видел, как он «говорил» вещи, которые напоминали мне о проблемах unix2dos, что, в свою очередь, заставляло меня думать о Mono и, наконец, если бы мне было нужно иметь дело с мобильностью (при условии, что цель - совершенство).

Ответы [ 3 ]

2 голосов
/ 16 декабря 2011

Да, вы должны использовать разные разделители строк, но это не так. Квадратные скобки не делают их содержимое необязательным, и вы не учитываете старый стиль Mac \r. Я бы использовал эти регулярные выражения:

string oneLine = @"\r\n|[\r\n]";
string twoLines = @"(?:\r\n|[\r\n]){2}";

Это «возврат каретки + перевод строки ИЛИ возврат каретки ИЛИ перевод строки».

Кроме того, вам не нужна опция Multiline. Это только меняет значение якорей ^ и $, которые вы не используете (и не должны использовать).

2 голосов
/ 16 декабря 2011

Ваши регулярные выражения не делают то, что вы думаете, что они делают. Помещение \r в набор ничего не дает; выражение [\r]\n означает то же самое, что и \r\n.

Вы можете выполнить работу, используя оператор ?:

string oneLine = @"\r?\n";
string twoLines = @"\r?\n\r?\n";

Однако я бы предложил использовать обычный метод String.Split вместо регулярных выражений:

string[] oneLine = { @"\r\n", @"\n" };
string[] twoLines = { @"\r\n\r\n", @"\n\n" };

var chunks = s.Split(twoLines, StringSplitOptions.None);

var items =
  chunks.Select(c => c.Split(oneLine, StringSplitOptions.None))
  .ToDictionary(c => c[0], c => c[1]);
1 голос
/ 16 декабря 2011

Если вы хотите полностью разобраться с переносимостью (и да, я только добавляю этот ответ в ответ на упоминание Аланом старого Mac-стиля \ r), тогда вы хотите охватить:

* nixСтиль: \n

Стиль DOS / Windows: \r\n

Старый стиль Mac: \r

Стиль EBCDIC: \u0085 (возможно, немного более актуальный -я бы предпочел использовать его раньше, чем старый Mac).

Символ форматирования разделителя строк: \u2028

Символ форматирования разделителя абзацев: \u2029

Давайте простоне останавливаться на точной семантике \u000B и \u000C и превратить это в нечто разумное (в конце концов).Если бы мы были , чтобы попытаться разобраться со всем этим.Как бы мы это сделали?

С 6 различными переносами строк, один из которых является комбинацией двух других, но которые не должны рассматриваться как два переноса строк, рассматривая это в регистре.Сам ex может быть неприятным.

Гораздо лучше было бы отфильтровать их все в обертке TextReader:

public class LineBreakNormaliser : TextReader
{
  private readonly TextReader _source;
  private bool isNewLine(int charAsInt)
  {
    switch(charAsInt)
    {
      case '\n': case '\r':
      case '\u0085': case '\u2028': case '\u2029':
      case '\u000B': case '\u000C':
        return true;
      default:
        return false;
    }
  }
  public LineBreakNormaliser(TextReader source)
  {
    _source = source;
  }
  public override void Close()
  {
    _source.Close();
    base.Close();
  }
  protected override void Dispose(bool disposing)
  {
    if(disposing)
      _source.Dispose();
    base.Dispose(disposing);
  }
  public override int Peek()
  {
    int i = _source.Peek();
    if(i == -1)
      return -1;
    if(isNewLine(i))
      return '\n';
    return i;
  }
  public override int Read()
  {
    int i = _source.Read();
    if(i == -1)
      return -1;
    if(i == '\r')
    {
      if(_source.Peek() == '\n')
        _source.Read(); //eat next half of CRLF pair.
      return i;
    }
    if(isNewLine(i))
      return '\n';
    return i;
  }
  public override int Read(char[] buffer, int index, int count)
  {
    //We take advantage of the fact that we are allowed to return fewer than requested.
    //ReadBlock does the work for us for those who need the full amount:
    char[] tmpBuffer = new char[count];
    int cChars = count = _source.Read(tmpBuffer, 0, count);
    if(cChars == 0)
      return 0;
    for(int i = 0; i != cChars; ++i)
    {
      char cur = tmpBuffer[i];
      if(cur == '\r')
      {
        if(i == cChars -1)
        {
          if(_source.Peek() == '\n')
          {
            _source.Read(); //eat second half of CRLF
            --count;
          }
        }
        else if(tmpBuffer[i + 1] == '\r')
        {
          ++i;
          --count;
        }
        buffer[index++] = '\n';
      }
      else if(isNewLine(cur))
        buffer[index++] = '\n';
      else
        buffer[index++] = '\n';
    }
    return count;
  }
}

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

Это сделано, регулярное выражение может быть на самом деле проще, чем когда-либо, и вы, хотя и полностью излишне для этого единственного случая (и только написанопотому что после упоминания Алана об OS9 и более ранней идее поддержки машин IBM EBCDIC меня позабавила), она может использоваться повторно для всех других случаев, в которых контекст на самом деле не является чрезмерным уничтожением, потому что она становится «просто использовать проверенную линиюНормализатор, чтобы сделать вещи проще ".(После того, как это хорошо проверено, я не проверял ничего из вышеперечисленного).

...