Подтвердить что ты не робот

В С#, каков наилучший способ разобрать эту разметку WIKI?

Мне нужно взять данные, которые я читаю на странице разметки WIKI, и хранить их как структуру таблицы. Я пытаюсь выяснить, как правильно разобрать синтаксис ниже разметки в структуру данных таблицы в С#

Вот пример таблицы:

 || Owner || Action || Status || Comments ||
 | Bill | Fix the lobby | In Progress | This is easy |
 | Joe | Fix the bathroom | In Progress | Plumbing \\
 \\
  Electric \\
 \\
 Painting \\
 \\
 \\ | 
 | Scott | Fix the roof | Complete | This is expensive |

и вот как это происходит непосредственно:

|| Owner|| Action || Status || Comments || | Bill\\ | fix the lobby |In Progress | This is eary| | Joe\\ |fix the bathroom\\ | In progress| plumbing  \\Electric \\Painting \\ \\ | | Scott \\ | fix the roof \\ | Complete | this is expensive| 

Итак, как вы можете видеть:

  • Заголовки столбцов имеют "||" как разделитель
  • В столбцах строк есть разделитель или "|"
  • Строка может охватывать несколько строк (как и во втором примере строки данных выше), поэтому мне пришлось бы продолжать чтение, пока я не нажму на такое же число "|" (cols), которые у меня есть в строке заголовка.

Я пробовал читать строки за строкой, а затем конкатенировал строки с "\" между ними, но это выглядело немного взломанным.

Я также пробовал просто читать в виде полной строки, а затем просто анализировать на "||" сначала, а затем продолжайте читать, пока я не нажму на такое же число "|" а затем перейдите к следующей строке. Казалось, что это работает, но кажется, что может быть более элегантный способ использования регулярных выражений или чего-то подобного.

Может ли кто-нибудь предложить правильный способ анализа этих данных?

4b9b3361

Ответ 1

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

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

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

public class TableParser
{
    const StringSplitOptions SplitOpts = StringSplitOptions.None;
    const string RowColSep = "|";
    static readonly string[] HeaderColSplit = { "||" };
    static readonly string[] RowColSplit = { RowColSep };
    static readonly string[] MLColSplit = { @"\\" };

    public class TableRow
    {
        public List<string[]> Cells;
    }

    public class Table
    {
        public string[] Header;
        public TableRow[] Rows;
    }

    public static Table Parse(string text)
    {
        // Isolate the header columns and rows remainder.
        var headerSplit = text.Split(HeaderColSplit, SplitOpts);
        Ensure(headerSplit.Length > 1, "At least 1 header column is required in the input");

        // Need to check whether there are any rows.
        var hasRows = headerSplit.Last().IndexOf(RowColSep) >= 0;
        var header = headerSplit.Skip(1)
            .Take(headerSplit.Length - (hasRows ? 2 : 1))
            .Select(c => c.Trim())
            .ToArray();

        if (!hasRows) // If no rows for this table, we are done.
            return new Table() { Header = header, Rows = new TableRow[0] };

        // Get all row columns from the remainder.
        var rowsCols = headerSplit.Last().Split(RowColSplit, SplitOpts);

        // Require same amount of columns for a row as the header.
        Ensure((rowsCols.Length % (header.Length + 1)) == 1, 
            "The number of row colums does not match the number of header columns");
        var rows = new TableRow[(rowsCols.Length - 1) / (header.Length + 1)];

        // Fill rows by sequentially taking # header column cells 
        for (int ri = 0, start = 1; ri < rows.Length; ri++, start += header.Length + 1)
        {
            rows[ri] = new TableRow() { 
                Cells = rowsCols.Skip(start).Take(header.Length)
                    .Select(c => c.Split(MLColSplit, SplitOpts).Select(p => p.Trim()).ToArray())
                    .ToList()
            };
        };

        return new Table { Header = header, Rows = rows };
    }

    private static void Ensure(bool check, string errorMsg)
    {
        if (!check)
            throw new InvalidDataException(errorMsg);
    }
}

При использовании следующим образом:

public static void Main(params string[] args)
{
        var wikiLine = @"|| Owner|| Action || Status || Comments || | Bill\\ | fix the lobby |In Progress | This is eary| | Joe\\ |fix the bathroom\\ | In progress| plumbing  \\Electric \\Painting \\ \\ | | Scott \\ | fix the roof \\ | Complete | this is expensive|";
        var table = TableParser.Parse(wikiLine);

        Console.WriteLine(string.Join(", ", table.Header));
        foreach (var r in table.Rows)
            Console.WriteLine(string.Join(", ", r.Cells.Select(c => string.Join(Environment.NewLine + "\t# ", c))));
}

Он выдаст следующий результат:

output

Где "\t# " представляет новую строку, вызванную наличием \\ на входе.

Ответ 2

Здесь находится решение, которое заполняет DataTable. Для этого требуется небольшая часть массива данных (Trim), но основным анализом является Splits и Linq.

var str = @"|| Owner|| Action || Status || Comments || | Bill\\ | fix the lobby |In Progress | This is eary| | Joe\\ |fix the bathroom\\ | In progress| plumbing  \\Electric \\Painting \\ \\ | | Scott \\ | fix the roof \\ | Complete | this is expensive|";

var headerStop = str.LastIndexOf("||");
var headers = str.Substring(0, headerStop).Split(new string[1] { "||" }, StringSplitOptions.None).Skip(1).ToList();
var records = str.Substring(headerStop + 4).TrimEnd(new char[2] { ' ', '|' }).Split(new string[1] { "| |" }, StringSplitOptions.None).ToList();

var tbl = new DataTable();
headers.ForEach(h => tbl.Columns.Add(h.Trim()));
records.ForEach(r =>  tbl.Rows.Add(r.Split('|')));

Ответ 3

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

List<List<string>> table = new List<List<string>>();


var match = Regex.Match(raw, @"(?:(?:\|\|([^|]*))*\n)?");
if (match.Success)
{
    var headersWithExtra = match.Groups[1].Captures.Cast<Capture>().Select(c=>c.Value);
    List<String> headerRow = headersWithExtra.Take(headersWithExtra.Count()-1).ToList();
    if (headerRow.Count > 0)
    {
        table.Add(headerRow);
    }
}

match = Regex.Match(raw + "\r\n", @"[^\n]*\n" + @"(?:\|([^|]*))*");
var cellsWithExtra = match.Groups[1].Captures.Cast<Capture>().Select(c=>c.Value);

List<string> row = new List<string>();
foreach (string cell in cellsWithExtra)
{
    if (cell.Trim(' ', '\t') == "\r\n")
    {
        if (!table.Contains(row) && row.Count > 0)
        {
            table.Add(row);
        }
        row = new List<string>();
    }
    else
    {

        row.Add(cell);
    }
}

Ответ 4

Это очень похоже на ответ Джона Тирьяна, хотя он сокращает LINQ до одного утверждения (код для замены последнего был ужасно уродливым) и немного более расширяем. Например, он заменит строку Confluence break \\ на строку по вашему выбору, вы можете выбрать обрезку или не обрезать пробелы вокруг элементов и т.д.

private void ParseWikiTable(string input, string newLineReplacement = " ")
{
    string separatorHeader = "||";
    string separatorRow = "| |";
    string separatorElement = "|";

    input = Regex.Replace(input, @"[ \\]{2,}", newLineReplacement);

    string inputHeader = input.Substring(0, input.LastIndexOf(separatorHeader));
    string inputContent = input.Substring(input.LastIndexOf(separatorHeader) + separatorHeader.Length);

    string[] headerArray = SimpleSplit(inputHeader, separatorHeader);
    string[][] rowArray = SimpleSplit(inputContent, separatorRow).Select(r => SimpleSplit(r, separatorElement)).ToArray();

    // do something with output data
    TestPrint(headerArray);
    foreach (var r in rowArray) { TestPrint(r); }
}

private string[] SimpleSplit(string input, string separator, bool trimWhitespace = true)
{
    input = input.Trim();
    if (input.StartsWith(separator)) { input = input.Substring(separator.Length); }
    if (input.EndsWith(separator)) { input = input.Substring(0, input.Length - separator.Length); }

    string[] segments = input.Split(new string[] { separator }, StringSplitOptions.None);
    if (trimWhitespace)
    {
        for (int i = 0; i < segments.Length; i++)
        {
            segments[i] = segments[i].Trim();
        }
    }

    return segments;
}

private void TestPrint(string[] lst)
{
    string joined = "[" + String.Join("::", lst) + "]";
    Console.WriteLine(joined);
}

Консоль выводится из строки прямого ввода:

[Владелец:: Действие:: Статус:: Комментарии]

[Bill:: исправить лобби:: In Progress:: This is eary]

[Joe:: fix the bathroom:: In progress:: сантехника Electric Painting]

[Скотт:: исправьте крышу:: Complete:: это дорого]

Ответ 5

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

           var text = @"|| Owner|| Action || Status || Comments || | Bill\\ | fix the lobby |In Progress | This is eary| | Joe\\ |fix the bathroom\\ | In progress| plumbing  \\Electric \\Painting \\ \\ | | Scott \\ | fix the roof \\ | Complete | this is expensive|";

        // Get Headers
        var regHeaders = new Regex(@"\|\|\s*(\w[^\|]+)", RegexOptions.Compiled);
        var headers = regHeaders.Matches(text);

        //Get Rows, based on number of headers columns
        var regLinhas = new Regex(String.Format(@"(?:\|\s*(\w[^\|]+)){{{0}}}", headers.Count));
        var rows = regLinhas.Matches(text);

        var tbl = new DataTable();

        foreach (Match header in headers)
        {
            tbl.Columns.Add(header.Groups[1].Value);
        }

        foreach (Match row in rows)
        {
            tbl.Rows.Add(row.Groups[1].Captures.OfType<Capture>().Select(col => col.Value).ToArray());
        }

Ответ 6

Здесь существует решение с регулярными выражениями. Он принимает одну строку в качестве входных данных и возвращает список заголовков и список > строк/столбцов. Он также выравнивает пустое пространство, которое может или не может быть желательным поведением, поэтому имейте это в виду. Он даже хорошо печатает вещи:)

enter image description here

using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;

namespace parseWiki
{
    class Program
    {
        static void Main(string[] args)
        {
            string content = @"|| Owner || Action || Status || Comments || | Bill\\ | fix the lobby |In Progress | This is eary| | Joe\\ |fix the bathroom\\ | In progress| plumbing  \\Electric \\Painting \\ \\ | | Scott \\ | fix the roof \\ | Complete | this is expensive|";
            content = content.Replace(@"\\", "");
            string headerContent = content.Substring(0, content.LastIndexOf("||") + 2);
            string cellContent = content.Substring(content.LastIndexOf("||") + 2);
            MatchCollection headerMatches = new Regex(@"\|\|([^|]*)(?=\|\|)", RegexOptions.Singleline).Matches(headerContent);
            MatchCollection cellMatches = new Regex(@"\|([^|]*)(?=\|)", RegexOptions.Singleline).Matches(cellContent);

            List<string> headers = new List<string>();
            foreach (Match match in headerMatches)
            {
                if (match.Groups.Count > 1)
                {
                    headers.Add(match.Groups[1].Value.Trim());
                }
            }

            List<List<string>> body = new List<List<string>>();
            List<string> newRow = new List<string>();
            foreach (Match match in cellMatches)
            {
                if (newRow.Count > 0 && newRow.Count % headers.Count == 0)
                {
                    body.Add(newRow);
                    newRow = new List<string>();
                }
                else
                {
                    newRow.Add(match.Groups[1].Value.Trim());
                }
            }
            body.Add(newRow);

            print(headers, body);
        }

        static void print(List<string> headers, List<List<string>> body)
        {
            var CELL_SIZE = 20;

            for (int i = 0; i < headers.Count; i++)
            {
                Console.Write(headers[i].Truncate(CELL_SIZE).PadRight(CELL_SIZE) + "  ");
            }
            Console.WriteLine("\n" + "".PadRight( (CELL_SIZE + 2) * headers.Count, '-'));

            for (int r = 0; r < body.Count; r++)
            {
                List<string> row = body[r];
                for (int c = 0; c < row.Count; c++)
                {
                    Console.Write(row[c].Truncate(CELL_SIZE).PadRight(CELL_SIZE) + "  ");
                }
                Console.WriteLine("");
            }

            Console.WriteLine("\n\n\n");
            Console.ReadKey(false);
        }
    }

    public static class StringExt
    {
        public static string Truncate(this string value, int maxLength)
        {
            if (string.IsNullOrEmpty(value) || value.Length <= maxLength) return value;
            return value.Substring(0, maxLength - 3) + "...";

        }
    }
}

Ответ 7

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