Лаб. 6 ТАЯК
.docxЛабораторная работа №6
Разработка процессора языка разметки документа
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace eMarkProcessor
{
// Хранит информацию об ошибках валидации
public class ValidationError
{
public int Line { get; set; } // Строка с ошибкой
public int Column { get; set; } // Столбец с ошибкой
public string Message { get; set; } // Сообщение об ошибке
public ValidationError(int line, int column, string message)
{
Line = line;
Column = column;
Message = message;
}
public override string ToString()
{
return $"Строка:{Line}, Столбец:{Column} - {Message}";
}
}
// Преобразует коды цветов в цвета консоли
public class ConsoleColorConverter
{
private static readonly Dictionary<int, ConsoleColor> ColorMap = new Dictionary<int, ConsoleColor>
{
{0, ConsoleColor.Black},
{1, ConsoleColor.DarkBlue},
{2, ConsoleColor.DarkGreen},
{3, ConsoleColor.DarkCyan},
{4, ConsoleColor.DarkRed},
{5, ConsoleColor.DarkMagenta},
{6, ConsoleColor.DarkYellow},
{7, ConsoleColor.Gray},
{8, ConsoleColor.DarkGray},
{9, ConsoleColor.Blue},
{10, ConsoleColor.Green},
{11, ConsoleColor.Cyan},
{12, ConsoleColor.Red},
{13, ConsoleColor.Magenta},
{14, ConsoleColor.Yellow},
{15, ConsoleColor.White}
};
public static ConsoleColor GetConsoleColor(int colorCode)
{
return ColorMap.ContainsKey(colorCode) ? ColorMap[colorCode] : ConsoleColor.White;
}
public static bool IsValidColor(int colorCode)
{
return colorCode >= 0 && colorCode <= 15;
}
}
// Представляет элемент разметки (тег)
public class Tag
{
public string Name { get; set; } // Имя тега (block, row, column)
public Dictionary<string, string> Attributes { get; set; } = new Dictionary<string, string>(); // Атрибуты
public string TextContent { get; set; } = ""; // Текстовое содержимое
public List<Tag> Children { get; set; } = new List<Tag>(); // Дочерние элементы
public int Line { get; set; } // Позиция в исходном документе (строка)
public int Column { get; set; } // Позиция в исходном документе (столбец)
public Tag(string name, int line, int column)
{
Name = name;
Line = line;
Column = column;
}
}
// Представляет одну ячейку на экране
public class Cell
{
public char Character { get; set; } // Символ
public ConsoleColor Foreground { get; set; } // Цвет текста
public ConsoleColor Background { get; set; } // Цвет фона
public Cell(char ch, ConsoleColor fg, ConsoleColor bg)
{
Character = ch;
Foreground = fg;
Background = bg;
}
public Cell() : this(' ', ConsoleColor.White, ConsoleColor.Black)
{
}
}
// Основной класс для обработки документа
public class EMarkProcessor
{
private const int ConsoleWidth = 80;
private const int ConsoleHeight = 24;
private Cell[,] screen = new Cell[ConsoleHeight, ConsoleWidth];
private List<ValidationError> validationErrors = new List<ValidationError>();
public EMarkProcessor()
{
ClearScreen();
}
private void ClearScreen()
{
for (int i = 0; i < ConsoleHeight; i++)
for (int j = 0; j < ConsoleWidth; j++)
screen[i, j] = new Cell(' ', ConsoleColor.White, ConsoleColor.Black);
}
public void Process(string eMarkDocument)
{
try
{
validationErrors.Clear();
var rootTag = ParseDocument(eMarkDocument);
// Выводим все ошибки валидации
if (validationErrors.Count > 0)
{
Console.WriteLine("Найдены ошибки в документе:");
foreach (var error in validationErrors)
{
Console.WriteLine($" {error}");
}
return;
}
if (rootTag == null)
{
Console.WriteLine("Документ пуст.");
return;
}
if (rootTag.Name != "block")
{
AddValidationError(1, 1, "Корневой элемент должен быть <block>");
Console.WriteLine("Найдены ошибки в документе:");
foreach (var error in validationErrors)
{
Console.WriteLine($" {error}");
}
return;
}
// Валидация структуры документа
ValidateDocumentStructure(rootTag);
if (validationErrors.Count > 0)
{
Console.WriteLine("Найдены ошибки в документе:");
foreach (var error in validationErrors)
{
Console.WriteLine($" {error}");
}
return;
}
RenderBlock(rootTag, 0, 0, ConsoleWidth, ConsoleHeight);
DisplayScreen();
}
catch (Exception ex)
{
Console.WriteLine($"Критическая ошибка обработки документа: {ex.Message}");
}
}
// Парсинг документа
private Tag ParseDocument(string document)
{
var stack = new Stack<Tag>();
Tag root = null;
int index = 0;
int line = 1;
int column = 1;
while (index < document.Length)
{
char currentChar = document[index];
if (currentChar == '\n')
{
line++;
column = 1;
index++;
continue;
}
if (currentChar == '<')
{
int startLine = line;
int startColumn = column;
int endTagIndex = document.IndexOf('>', index);
if (endTagIndex == -1)
{
AddValidationError(line, column, "Незакрытый тег.");
return null;
}
string tagText = document.Substring(index + 1, endTagIndex - index - 1).Trim();
// Обновляем позицию
for (int i = index; i <= endTagIndex; i++)
{
if (document[i] == '\n')
{
line++;
column = 1;
}
else
{
column++;
}
}
index = endTagIndex + 1;
if (tagText.StartsWith("/"))
{
// Закрывающий тег
string tagName = tagText.Substring(1).Trim();
if (stack.Count == 0)
{
AddValidationError(startLine, startColumn, $"Неожиданный закрывающий тег </{tagName}>.");
}
else if (stack.Peek().Name != tagName)
{
AddValidationError(startLine, startColumn, $"Несоответствие тегов: ожидался закрывающий тег для <{stack.Peek()?.Name}>, получен </{tagName}>.");
}
else
{
var closedTag = stack.Pop();
if (stack.Count == 0)
root = closedTag;
else
stack.Peek().Children.Add(closedTag);
}
}
else
{
// Открывающий тег
var tag = ParseTag(tagText, startLine, startColumn);
// Валидация имени тега
if (!IsValidTagName(tag.Name))
{
AddValidationError(startLine, startColumn, $"Неизвестный тег: <{tag.Name}>");
}
else
{
// Валидация атрибутов тега
ValidateTagAttributes(tag);
}
// Пропускаем пробелы до начала текста
while (index < document.Length && char.IsWhiteSpace(document[index]))
{
if (document[index] == '\n')
{
line++;
column = 1;
}
else
{
column++;
}
index++;
}
// Проверяем, не начинается ли сразу следующий тег
if (index < document.Length && document[index] != '<')
{
// Извлекаем текст до следующего тега
int nextTagIndex = document.IndexOf('<', index);
if (nextTagIndex == -1) nextTagIndex = document.Length;
string textContent = document.Substring(index, nextTagIndex - index).Trim();
tag.TextContent = textContent;
// Обновляем позицию
for (int i = index; i < nextTagIndex; i++)
{
if (document[i] == '\n')
{
line++;
column = 1;
}
else
{
column++;
}
}
index = nextTagIndex;
}
stack.Push(tag);
}
}
else
{
// Пропускаем символы
if (currentChar == '\r')
{
// Игнорируем \r
}
else
{
column++;
}
index++;
}
}
if (stack.Count > 0)
{
var unclosedTag = stack.Peek();
AddValidationError(unclosedTag.Line, unclosedTag.Column, $"Не закрыт тег <{unclosedTag.Name}>.");
}
return root;
}
// Парсинг тегов
private Tag ParseTag(string tagText, int line, int column)
{
// Находим имя тега (первое слово до пробела или конца строки)
int firstSpace = tagText.IndexOf(' ');
string tagName;
string attributesStr;
if (firstSpace == -1)
{
tagName = tagText;
attributesStr = "";
}
else
{
tagName = tagText.Substring(0, firstSpace);
attributesStr = tagText.Substring(firstSpace + 1);
}
var tag = new Tag(tagName, line, column);
if (!string.IsNullOrEmpty(attributesStr))
{
// Регулярное выражение для извлечения атрибутов
var matches = Regex.Matches(attributesStr, @"(\w+)=([^\s""]+|""[^""]*"")");
foreach (Match match in matches)
{
if (match.Groups.Count >= 3)
{
string attrName = match.Groups[1].Value;
string attrValue = match.Groups[2].Value.Trim('"');
tag.Attributes[attrName] = attrValue;
}
}
// Проверяем, не остались ли нераспарсенные части (например, атрибуты без значения)
string cleaned = attributesStr;
foreach (Match match in matches)
{
cleaned = cleaned.Replace(match.Value, "");
}
cleaned = cleaned.Trim();
if (!string.IsNullOrEmpty(cleaned))
{
AddValidationError(line, column, $"Некорректный синтаксис атрибутов: '{cleaned}'");
}
}
return tag;
}
private bool IsValidTagName(string tagName)
{
return tagName == "block" || tagName == "row" || tagName == "column";
}
// Проверяет корректность атрибутов для каждого типа тега
private void ValidateTagAttributes(Tag tag)
{
foreach (var attr in tag.Attributes)
{
string attrName = attr.Key;
string attrValue = attr.Value;
switch (tag.Name)
{
case "block":
if (attrName != "rows" && attrName != "columns")
{
AddValidationError(tag.Line, tag.Column, $"Неизвестный атрибут '{attrName}' для тега <{tag.Name}>");
}
else if (attrName == "rows" || attrName == "columns")
{
if (!int.TryParse(attrValue, out int intValue) || intValue <= 0)
{
AddValidationError(tag.Line, tag.Column, $"Атрибут '{attrName}' должен быть положительным числом");
}
}
break;
case "row":
case "column":
if (attrName == "valign")
{
if (attrValue != "top" && attrValue != "center" && attrValue != "bottom")
{
AddValidationError(tag.Line, tag.Column, $"Некорректное значение '{attrValue}' для атрибута 'valign'. Допустимые значения: top, center, bottom");
}
}
else if (attrName == "halign")
{
if (attrValue != "left" && attrValue != "center" && attrValue != "right")
{
AddValidationError(tag.Line, tag.Column, $"Некорректное значение '{attrValue}' для атрибута 'halign'. Допустимые значения: left, center, right");
}
}
else if (attrName == "textcolor" || attrName == "bgcolor")
{
if (!int.TryParse(attrValue, out int colorValue) || !ConsoleColorConverter.IsValidColor(colorValue))
{
AddValidationError(tag.Line, tag.Column, $"Некорректное значение цвета '{attrValue}' для атрибута '{attrName}'. Допустимые значения: 0-15");
}
}
else if (attrName == "width" || attrName == "height")
{
if (!int.TryParse(attrValue, out int sizeValue) || sizeValue <= 0)
{
AddValidationError(tag.Line, tag.Column, $"Атрибут '{attrName}' должен быть положительным числом");
}
}
else
{
AddValidationError(tag.Line, tag.Column, $"Неизвестный атрибут '{attrName}' для тега <{tag.Name}>");
}
break;
}
}
// Проверяем обязательные атрибуты
if (tag.Name == "block")
{
if (!tag.Attributes.ContainsKey("rows"))
{
AddValidationError(tag.Line, tag.Column, "Отсутствует обязательный атрибут 'rows' для тега <block>");
}
if (!tag.Attributes.ContainsKey("columns"))
{
AddValidationError(tag.Line, tag.Column, "Отсутствует обязательный атрибут 'columns' для тега <block>");
}
}
}
// Проверяет структурную целостность документа
private void ValidateDocumentStructure(Tag root)
{
ValidateBlockStructure(root);
}
private void ValidateBlockStructure(Tag block)
{
if (block.Name != "block") return;
int rows = 1;
int columns = 1;
if (block.Attributes.ContainsKey("rows"))
int.TryParse(block.Attributes["rows"], out rows);
if (block.Attributes.ContainsKey("columns"))
int.TryParse(block.Attributes["columns"], out columns);
// Проверяем количество дочерних элементов
int expectedChildren = rows * columns;
if (block.Children.Count != expectedChildren)
{
AddValidationError(block.Line, block.Column,
$"Блок <block rows={rows} columns={columns}> должен содержать {expectedChildren} дочерних элементов, но содержит {block.Children.Count}");
}
// Проверяем структуру вложенных элементов
for (int i = 0; i < block.Children.Count; i++)
{
var child = block.Children[i];
if (child.Name == "block")
{
ValidateBlockStructure(child);
}
else if (child.Name == "row" || child.Name == "column")
{
// Валидация вложенных элементов строк и столбцов
foreach (var grandChild in child.Children)
{
if (grandChild.Name == "block")
{
ValidateBlockStructure(grandChild);
}
}
}
}
}
private void AddValidationError(int line, int column, string message)
{
validationErrors.Add(new ValidationError(line, column, message));
}
// Рендерит блок с сеткой
private void RenderBlock(Tag block, int startX, int startY, int width, int height)
{
if (block.Name != "block" || width <= 0 || height <= 0)
return;
int rows = 1;
int columns = 1;
if (block.Attributes.ContainsKey("rows"))
{
int.TryParse(block.Attributes["rows"], out rows);
rows = Math.Max(1, rows);
}
if (block.Attributes.ContainsKey("columns"))
{
int.TryParse(block.Attributes["columns"], out columns);
columns = Math.Max(1, columns);
}
// Сначала отображаем текстовое содержимое самого блока
if (!string.IsNullOrEmpty(block.TextContent))
{
int textColor = block.Attributes.ContainsKey("textcolor") ? int.Parse(block.Attributes["textcolor"]) : 15;
int bgColor = block.Attributes.ContainsKey("bgcolor") ? int.Parse(block.Attributes["bgcolor"]) : 0;
string halign = block.Attributes.ContainsKey("halign") ? block.Attributes["halign"] : "left";
string valign = block.Attributes.ContainsKey("valign") ? block.Attributes["valign"] : "top";
RenderText(block.TextContent, startX, startY, width, height, halign, valign, textColor, bgColor);
}
// Если есть дочерние элементы, рендерим их
if (block.Children.Count > 0)
{
// Рассчитываем ширины столбцов
int[] colWidths = new int[columns];
for (int i = 0; i < columns; i++)
{
colWidths[i] = -1;
}
int totalFixedWidth = 0;
int fixedColumns = 0;
// Собираем явно указанные ширины
for (int i = 0; i < Math.Min(block.Children.Count, rows * columns); i++)
{
var child = block.Children[i];
if (child.Name == "column" && child.Attributes.ContainsKey("width"))
{
int colIndex = i % columns;
int w = int.Parse(child.Attributes["width"]);
w = Math.Max(1, w);
if (colWidths[colIndex] == -1)
{
colWidths[colIndex] = w;
totalFixedWidth += w;
fixedColumns++;
}
}
}
// Распределяем оставшуюся ширину
int remainingWidth = width - totalFixedWidth;
remainingWidth = Math.Max(0, remainingWidth);
int autoColumns = columns - fixedColumns;
if (autoColumns > 0)
{
int baseWidth = remainingWidth / autoColumns;
baseWidth = Math.Max(1, baseWidth);
int extra = remainingWidth % autoColumns;
for (int i = 0; i < columns; i++)
{
if (colWidths[i] == -1)
{
colWidths[i] = baseWidth;
if (extra > 0)
{
colWidths[i]++;
extra--;
}
}
}
}
// Рассчитываем высоты строк
int[] rowHeights = new int[rows];
int totalFixedHeight = 0;
int rowsWithExplicitHeight = 0;
for (int i = 0; i < rows; i++)
{
rowHeights[i] = -1;
}
// Собираем явно указанные высоты
for (int i = 0; i < Math.Min(block.Children.Count, rows * columns); i++)
{
var child = block.Children[i];
if (child.Name == "row" && child.Attributes.ContainsKey("height"))
{
int rowIndex = i / columns;
int h = int.Parse(child.Attributes["height"]);
h = Math.Max(1, h);
if (rowHeights[rowIndex] == -1)
{
rowHeights[rowIndex] = h;
totalFixedHeight += h;
rowsWithExplicitHeight++;
}
}
}
// Распределяем оставшуюся высоту
int remainingHeight = height - totalFixedHeight;
remainingHeight = Math.Max(0, remainingHeight);
// Правило: если только последняя строка не имеет явной высоты,
// она получает все оставшееся пространство
if (rowsWithExplicitHeight == rows - 1)
{
for (int i = 0; i < rows; i++)
{
if (rowHeights[i] == -1)
{
rowHeights[i] = remainingHeight;
}
}
}
else
{
// Равномерно распределяем между строками без явной высоты
int autoRows = rows - rowsWithExplicitHeight;
if (autoRows > 0)
{
int baseHeight = remainingHeight / autoRows;
baseHeight = Math.Max(1, baseHeight);
int extra = remainingHeight % autoRows;
for (int i = 0; i < rows; i++)
{
if (rowHeights[i] == -1)
{
rowHeights[i] = baseHeight;
if (extra > 0)
{
rowHeights[i]++;
extra--;
}
}
}
}
}
// Проверяем, что сумма высот равна доступной высоте
int totalHeight = 0;
for (int i = 0; i < rows; i++)
{
totalHeight += rowHeights[i];
}
// Корректируем последнюю строку, если есть расхождение
if (totalHeight != height && rows > 0)
{
int diff = height - totalHeight;
if (diff != 0)
{
rowHeights[rows - 1] += diff;
if (rowHeights[rows - 1] < 1) rowHeights[rows - 1] = 1;
}
}
// Рендерим детей в соответствии с сеткой
int childIndex = 0;
int currentY = startY;
for (int r = 0; r < rows && currentY < ConsoleHeight; r++)
{
int currentX = startX;
for (int c = 0; c < columns && currentX < ConsoleWidth; c++)
{
if (childIndex < block.Children.Count)
{
var child = block.Children[childIndex];
int cellWidth = Math.Max(1, colWidths[c]);
int cellHeight = Math.Max(1, rowHeights[r]);
if (currentX + cellWidth > ConsoleWidth)
cellWidth = ConsoleWidth - currentX;
if (currentY + cellHeight > ConsoleHeight)
cellHeight = ConsoleHeight - currentY;
if (cellWidth > 0 && cellHeight > 0)
{
if (child.Name == "row")
{
RenderRow(child, currentX, currentY, cellWidth, cellHeight);
}
else if (child.Name == "column")
{
RenderColumn(child, currentX, currentY, cellWidth, cellHeight);
}
else if (child.Name == "block")
{
RenderBlock(child, currentX, currentY, cellWidth, cellHeight);
}
}
childIndex++;
}
currentX += colWidths[c];
}
currentY += rowHeights[r];
}
}
}
private void RenderRow(Tag row, int startX, int startY, int width, int height)
{
if (width <= 0 || height <= 0) return;
