C++ парсинг тегов в std::string

146
01 апреля 2019, 06:00

Есть std::string например:

<rectangle left="100" top="100" width="200" height="200">
  <rectangle left="100" top="100" width="200" height="200">
  </rectangle>
  <rectangle left="100" top="100" width="200" height="200"> 
  </rectangle>
</rectangle>

нужно его распарсить в дерево объектов со списком свойств каждый. т.е. получить rectangle со всеми его свойствами внутри которого еще 2 rectangle со свойствами.

У меня парсить пока не получается. Используя find_first_of и find_last_of могу проверить главный rectangle а сделать сдвиг не получаеться.

P.S.: знаю что XML и мне не нужны библиотеки, меня интересует сам процесс парсинга, реализация.

Answer 1

Процесс парсинга, в простейшем случае, можно представить в виде трех этапов:

  1. Разделение XML на список элементов (элементами являются теги и текстовые фрагменты).

  2. Преобразование списка элементов в дерево узлов XML.

  3. Выделение отдельных свойств (атрибутов) для каждого узла и разэкранирование спецсимволов.

Первый этап достаточно просто делается с помощью find_first_of и substr (ну, или регулярок, для их любителей). То, что внутри скобок - теги, то, что снаружи - текстовые фрагменты.

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

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

Вот пример кода для 1 и 2 этапов:

#include <stdio.h>
#include <stdlib.h>
#include <exception>
#include <iostream>
#include <string>
#include <list>
enum TokenType { 
    TT_TAG = 1, 
    TT_TEXT = 2 
};
enum TagType { 
    TAG_OPEN = 1, 
    TAG_CLOSE = 2, 
    TAG_SELFCLOSING = 3
};
//Представляет элемент XML (тэг или текстовый фрагмент)
struct XmlToken {
    std::string text;
    TokenType type;
};
//Представляет узел в дереве XML
struct XmlNode {
    std::string name; //имя тега
    std::string properties; //свойства
    std::string value; //значение узла
    std::list<XmlNode*> children; //дочерние узлы
};

//Преобразует строку XML в последовательность элементов
std::list<XmlToken> XmlToTokens(const std::string& xml) {
    std::list<XmlToken> tokens;
    XmlNode result;
    size_t pos=0;
    size_t startindex, endindex;
    std::string token_text;
    XmlToken token;
    while (true) {
        startindex = xml.find_first_of('<', pos);
        endindex = xml.find_first_of('>', startindex);
        if (startindex == std::string::npos || endindex == std::string::npos) {
            break;
        }
        if (startindex > pos) {
            token.text = xml.substr(pos, startindex - pos);
            token.type = TT_TEXT;
            tokens.push_back(token);
        }
        token.text = xml.substr(startindex+1, endindex - (startindex+1));
        token.type = TT_TAG;
        tokens.push_back(token);
        pos = endindex + 1;
        if (pos >= xml.length())break;
    }   
    return tokens;
}
//возвращает тип тега
TagType GetTagType(const XmlToken& token) {
    if (token.type != TT_TAG || token.text.length()==0) return (TagType)0;

    char first = token.text.at(0);
    char last = token.text.at(token.text.length() - 1);
    if (first == '/' && last != '/') return TAG_CLOSE;
    if (first != '/' && last == '/') return TAG_SELFCLOSING;
    if (first != '/' && last != '/') return TAG_OPEN;
    else return (TagType)0;
}
//возвращает имя тега
std::string GetTagName(const XmlToken& token) {
    std::string res;
    if (token.type != TT_TAG || token.text.length() == 0) return res;
    size_t pos = 0;
    char first = token.text.at(0);
    if (first == '/')pos = 1;
    size_t index = token.text.find_first_of(" /", pos);
    if (index == std::string::npos) index = token.text.length();
    return token.text.substr(pos, index - pos);
}
//возвращает свойства тега
std::string GetTagProperties(const XmlToken& token) {
    std::string res;
    if (token.type != TT_TAG || token.text.length() == 0) return res;
    size_t pos = 0;
    size_t len = token.text.length();
    char first = token.text.at(0);
    if (first == '/')pos = 1;
    char last = token.text.at(len-1);
    if (last == '/')len--;
    size_t index = token.text.find_first_of(" ", pos);
    if (index == std::string::npos) index = len - 1;
    return token.text.substr(index+1, len - (index + 1));
}
//возвращает текст элемента без пробелов
std::string GetTrimmedText(const XmlToken& token) {
    std::string res;
    if (token.type != TT_TEXT || token.text.length() == 0) return res;
    for (size_t i = 0; i < token.text.length(); i++) {
        char c = token.text.at(i);
        if (iswspace(c) == 0 && c!='\n' && c!='\r') res += c;
    }
    return res;
}
//выводит дерево XML на экран
void PrintXml(const XmlNode& node, int depth = 0) {
    for (int i = 0; i < depth;i++) putc('-', stdout);
    printf("%s Properties: [%s]; Value: [%s]; Children: [%d]\n", 
        node.name.c_str(), node.properties.c_str(),node.value.c_str(),(int)node.children.size()
    );
    for (auto x : node.children) PrintXml(*x, depth + 1);
}
//**********************************
int main(int argc, char **argv)
{   
    try {
        std::string xml = "<rectangle left=\"100\" top=\"100\" width=\"200\" height=\"200\">\
<rectangle left = \"100\" top = \"100\" width = \"200\" height = \"200\"></rectangle>\
<rectangle left = \"100\" top = \"100\" width = \"200\" height = \"200\"></rectangle>\
</rectangle>";      
        printf("\n****** Source XML: ****** \n");
        puts(xml.c_str());
        printf("*********************** \n");
        //разбивка на элементы
        std::list<XmlToken> tokens = XmlToTokens(xml);
        printf("\n****** Tokens: ****** \n");
        for (XmlToken& x : tokens) {
            printf("%s\n", x.text.c_str());
        }
        printf("*********************** \n");
        //создаем корневой узел
        XmlNode root;
        root.name = std::string("(XML root)");
        root.properties = std::string("");
        root.value = std::string("");
        std::list<XmlNode*> currentpath; //текущий путь в иерархии XML
        bool between_tags = false;
        std::string tagname;
        std::string trimmed;
        TagType type;
        XmlNode* node = &root;
        XmlNode* new_node;
        //строим дерево XML...
        for (XmlToken& x : tokens) {
            switch (x.type) {
            case TT_TEXT: //текстовый фрагмент
                trimmed = GetTrimmedText(x);
                if (trimmed.length() == 0) { continue; }
                if (!between_tags) {
                    printf("Parse error: Unexpected text outside of XML tags.\n");
                    throw std::exception();
                }
                new_node = currentpath.back();
                if (new_node->children.size() > 0) {
                    printf("Parse error: Element cannot contain both text and child elements\n");
                    throw std::exception();
                }
                new_node->value = x.text; //устанавливаем значение узла                             
                break;
            case TT_TAG: //тег
                type = GetTagType(x);
                tagname = GetTagName(x);
                if (tagname.length() == 0) { 
                    printf("Parse error: Tag name empty\n");
                    throw std::exception();
                }
                switch (type) {
                case TAG_OPEN: //открывающий тег
                    //создаем новый узел
                    new_node = new XmlNode();
                    new_node->name = tagname;
                    new_node->properties = GetTagProperties(x);
                    new_node->value = std::string("");
                    //добавляем узел в текущий путь
                    between_tags = true;
                    currentpath.push_back(new_node);
                    break;
                case TAG_CLOSE: //закрывающий тег
                    if (currentpath.size() == 0) {
                        printf("Parse error: Unexpected closing tag\n");
                        throw std::exception();
                    }
                    new_node = currentpath.back();
                    if (tagname != new_node->name) {
                        printf("Parse error: Closing tag does not match opening tag.\n");
                        throw std::exception();
                    }
                    between_tags = false;
                    currentpath.pop_back();
                    //находим родительский узел
                    if (currentpath.size() > 0)
                        node = currentpath.back();
                    else
                        node = &root;
                    //добавляем дочерний узел
                    if (node->value.length() > 0) {
                        printf("Parse error: Element cannot contain both text and child elements\n");
                        throw std::exception();
                    }
                    node->children.push_back(new_node);
                    break;
                case TAG_SELFCLOSING: //самозакрывающийся
                    //создаем новый узел
                    new_node = new XmlNode();
                    new_node->name = tagname;
                    new_node->properties = GetTagProperties(x);
                    new_node->value = std::string("");
                    //находим родительский узел
                    if (currentpath.size() > 0)
                        node = currentpath.back();
                    else
                        node = &root;
                    //добавляем дочерний узел
                    if (node->value.length() > 0) {
                        printf("Parse error: Element cannot contain both text and child elements\n");
                        throw std::exception();
                    }
                    node->children.push_back(new_node);
                    break;
                }
                break;
            }//end switch
        }//end for
        if (currentpath.size() > 0) {
            printf("Parse error: Not all tags are closed\n");
            throw std::exception();
        }
        //выводим результат
        printf("\n****** XML Tree: ****** \n");
        PrintXml(root, 0);
        printf("*********************** \n");
    }
    catch (std::exception) {
        printf("Parsing failed!\n");
    }
    getchar();
    return 0;
}
/* Вывод:
****** Source XML: ******
<rectangle left="100" top="100" width="200" height="200"><rectangle left = "100" top = "100" width = "200" height = "200"></rectangle><rectangle left = "100" top = "100" width = "200" height = "200"></rectangle></rectangle>
***********************
****** Tokens: ******
rectangle left="100" top="100" width="200" height="200"
rectangle left = "100" top = "100" width = "200" height = "200"
/rectangle
rectangle left = "100" top = "100" width = "200" height = "200"
/rectangle
/rectangle
***********************
****** XML Tree: ******
(XML root) Properties: []; Value: []; Children: [1]
-rectangle Properties: [left="100" top="100" width="200" height="200"]; Value: []; Children: [2]
--rectangle Properties: [left = "100" top = "100" width = "200" height = "200"]; Value: []; Children: [0]
--rectangle Properties: [left = "100" top = "100" width = "200" height = "200"]; Value: []; Children: [0]
***********************
*/
READ ALSO
не активен QMenuBar [закрыт]

не активен QMenuBar [закрыт]

вот тут лежит мой проект (в src лежат исходники, а в mainProject файл notepad6d11 это собранная версия), проблема: верхняя панель (там где файл поле) не нажимается,...

140
Ошибки с массивом вещественных чисел

Ошибки с массивом вещественных чисел

Выполнял лабораторную работу:

153
QMap&lt;QString, Class*&gt; SIGSEGV при вставке значения

QMap<QString, Class*> SIGSEGV при вставке значения

В проекте создается экземпляр класса DatabaseManagerВ нем я создаю экземпляры класса Database в функции setupNewDatabase

141