Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Лаб. 6 ТАЯК

.docx
Скачиваний:
0
Добавлен:
25.01.2026
Размер:
167.1 Кб
Скачать

Лабораторная работа №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;

Соседние файлы в предмете Теория алгоритмических языков и компиляторов