#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#define TAG_MAX_LEN 32 // Максимальная длина имени тега
// Виды лексем
typedef enum {
T_TAG_BEG, // Открывающий тег
T_TAG_END, // Закрывающий тег
T_TAG_SINGLE, // Одиночный тег
T_DATA, // "Данные" (то есть, не тег)
T_END // Конец потока лексем
} token_type;
// Структура для токена
struct {
token_type type; // Вид лексемы
// Дополнительные данные лексемы
// Выбирается одно из двух
union {
char tag_name[TAG_MAX_LEN]; // Имя тега
size_t data_length; // Длина данных
} value;
} Token;
// Здесь хранится текущий символ
struct {
int val; // Сам символ
size_t pos; // Позиция в строке
size_t line; // Номер строки
} Character;
size_t tabulation;
const size_t TAB_STEP = 4;
static void init();
static void token_next();
//----------------------------------------------------------------------------
int main() {
init();
for (token_next(); Token.type != T_END; token_next()) {
switch (Token.type) {
case T_TAG_BEG:
printf("%*c%s\n", tabulation
, '{', Token.
value.
tag_name); tabulation += TAB_STEP;
break;
case T_TAG_END:
tabulation -= TAB_STEP;
printf("%*c\n", tabulation
, '}'); break;
case T_TAG_SINGLE:
printf("%*c%s}\n", tabulation
, '{', Token.
value.
tag_name); break;
case T_DATA:
printf("%*cdata length: %u]\n", tabulation
, '[', Token.
value.
data_length); break;
default:
fprintf(stderr
, "%s\n", "Runtime error"); }
}
if (tabulation) {
fprintf(stderr
, "Warning! %s\n", "Unmatched tag"); }
}
//----------------------------------------------------------------------------
static void error(const char *message) {
stderr,
"Error[%u:%u] %s\n",
Character.line + 1,
Character.pos,
message);
}
//----------------------------------------------------------------------------
static void char_next() {
if (Character.val == '\n') {
Character.pos = 0;
Character.line += 1;
} else {
Character.pos += 1;
}
}
//----------------------------------------------------------------------------
static void init() {
char_next();
}
//----------------------------------------------------------------------------
static void scan_tag();
static void scan_data();
static void token_next() {
char_next();
switch (Character.val) {
case EOF:
Token.type = T_END;
break;
case '<':
scan_tag();
break;
default:
scan_data();
}
}
//----------------------------------------------------------------------------
static void skip_comment() {
char_next();
if (Character.val != '-') error("Unexpected symbol");
char_next();
if (Character.val != '-') error("Unexpected symbol");
_Bool comment_flag = true;
while ((Character.val != EOF) && comment_flag) {
char_next();
if (Character.val == '-') {
char_next();
if (Character.val == '-') {
char_next();
if (Character.val == '>') comment_flag = false;
}
}
}
char_next();
}
//----------------------------------------------------------------------------
static void tag_get_name() {
int i = 0;
while ((i
< (TAG_MAX_LEN
- 1)) && isalnum(Character.
val)) { Token.value.tag_name[i] = Character.val;
char_next();
i += 1;
}
// Завершающий ноль для строки
Token.value.tag_name[i] = 0;
}
//----------------------------------------------------------------------------
static void scan_tag() {
char_next();
if (Character.val == '!') {
skip_comment();
token_next();
return;
}
else if (Character.val == '/') {
Token.type = T_TAG_END;
char_next();
}
else Token.type = T_TAG_BEG;
// Запоминаем имя тега
tag_get_name();
// Детектим пустые теги
if (!strlen(Token.
value.
tag_name)) error
("Empty tag");
// Ищем конец тега
while (Character.val != '>') {
if (Character.val == EOF) error("`>' expected");
if (Character.val == '/') {
char_next();
if (Character.val == '>') {
if (Token.type == T_TAG_BEG) {
Token.type = T_TAG_SINGLE;
break;
} else error("Unexpected symbol");
} else error("`/>' expected");
}
char_next();
}
char_next();
}
//----------------------------------------------------------------------------
static void scan_data() {
Token.type = T_DATA;
Token.value.data_length = 0;
do {
Token.value.data_length += 1;
char_next();
} while ((Character.val != EOF) && (Character.val != '<'));
}