Доброго времени суток всем! Есть такая задача. Имеется некий синтаксис разметки, определяющий таблицы. Синтаксис очень простой, он использует два символа (символ # для определения заголовочных ячеек таблицы и символ | для ограничения обычных ячеек) и символ перевода строки (показывающий окончание строки таблицы). Покажу на примере:
# заголовок1 # заголовок2 # заголовок 3 #
| ячейка1 стр1| ячейка2 стр1| ячейка3 стр1|
| ячейка1 стр2| ячейка2 стр2| ячейка3 стр2|
Эту нехитрую конструкцию надо распарсить в такую html-размекту:
<table>
<tr>
<th>заголовок1</th>
<th>заголовок2</th>
<th>заголовок3</th>
</tr>
<tr>
<td>ячейка1 стр1</td>
<td>ячейка3 стр1</td>
<td>ячейка4 стр1</td>
</tr>
<tr>
<td>ячейка1 стр2</td>
<td>ячейка3 стр2</td>
<td>ячейка4 стр2</td>
</tr>
</table>
Я не силен в подобных вещах, поэтому хотел бы спросить у знающих людей, как это можно сделать и куда вообще копать? К сожалению использование сторонних библиотек невозможно (таковы условия задачи). Какие тут вообще могут быть применены алгоритмы и средства? Также хотел бы уточнить, что сами тексты с этиой разметкой могут быть довольно объемными, поэтому производительность тоже важна. Заранее спасибо!
В качестве альтернативного решения можно воспользоваться заменой с помощью регулярного выражение
C# позволяет использовать именованные группы
Регулярное выражение может быть таким:
(?<startline>^)?((?<border>\|)?(?<header>#)?(?<cellvalue>[^#|]+))?(?<endline>[#|\r\n]+$)?
В качестве обработчика совпадений можно использовать следующую функцию:
m =>{
var replaced = new StringBuilder();
if (m.Index == 0) // если в самом начале строки - добавляем тег table
replaced.AppendLine("<table>");
if (m.Groups["startline"].Success) // если попалась новая строка - добавляем тег tr
replaced.AppendLine("<tr>");
if (m.Groups["border"].Success) // если нашли границу ячейки - вставляем значение обернутое в теги td
replaced.AppendLine($"<td>{m.Groups["cellvalue"].Value.Trim()}</td>");
else if (m.Groups["header"].Success) // если нашли границу ячейки заголовка - вставляем значение обернутое в th
replaced.AppendLine($"<th>{m.Groups["cellvalue"].Value.Trim()}</th>");
if (m.Groups["endline"].Success) // если дошли до конца строки - закрываем тег tr
replaced.AppendLine("</tr>");
if (m.Index == table.Length) // если дошли до самого конца - закрываем тег table
replaced.AppendLine("</table>");
return replaced.ToString(); // возвращаем результат
}
При запуске с флагом RegexOptions.Multiline буде получен следующий результат:
<table>
<tr>
<th>заголовок1</th>
<th>заголовок2</th>
<th>заголовок 3</th>
</tr>
<tr>
<td>ячейка1 стр1</td>
<td>ячейка2 стр1</td>
<td>ячейка3 стр1</td>
</tr>
<tr>
<td>ячейка1 стр2</td>
<td>ячейка2 стр2</td>
<td>ячейка3 стр2</td>
</tr>
Полагаю, как-то так будет быстрее всего:
string Convert(string str){
var sb = new StringBuilder();
sb.Append("<table>\n <tr>\n");
var header = false;
for (int l = -1, i = 0; i < str.Length; i++){
switch (str[i]){
case '#':
case '|':
if (l > 0){
sb.Append(header ? " <th>" : " <td>");
sb.Append(str.Substring(l, i - l).Trim());
sb.Append(header ? "</th>\n" : "</td>\n");
}
l = i + 1;
header = str[i] == '#';
break;
case '\n':
l = -1;
sb.Append(" </tr>\n <tr>\n");
break;
}
}
sb.Append(" <tr>\n</table>");
return sb.ToString();
}
Если нужно больше скорости, можно попробовать заранее рассчитывать capacity для StringBuilder (скажем, прикинув, какая в среднем длина у ячейки) и избавиться от Trim() (не уверен, что сильно поможет, но мало ли).
Кстати, если вдруг возникнет задача экранировать «|» и «#», можно добавить (и тогда уж заменить .Trim() на некий ConvertValue(), обрезающий лишнее и разъэкранирующий значение):
case '\\':
i++;
break;
Продвижение своими сайтами как стратегия роста и независимости