Последний пример в данном разделе представляет собой модель, которая показывает дерево грамматического разбора заданного регулярного выражения. Регулярное выражение состоит из одного или нескольких термов, разделяемых символами '|'. Так, регулярное выражение «alpha|bravo|charlie» содержит три терма. Каждый терм представляет собой последовательность из одного или нескольких факторов: например, терм «bravo» состоит из пяти факторов (каждая буква является фактором). Факторы могут состоять из атома и необязательного квантификатора (quantifier), например '*', '+' и '?'. Поскольку регулярные выражения могут иметь подвыражения, заключенные в скобки, они могут быть представлены рекурсивными деревьями грамматического разбора.
Регулярное выражение, показанное на рис. 10.14, «ab|(cd)?e» означает, что за 'a' следует 'b' или допускается два варианта: за 'c' идет 'd' и затем 'e' или просто имеется 'e'. Поэтому подойдут строки «ab» и «cde», но не подойдут строки «bc» или «cd».
Рис. 10.14. Приложение Парсер регулярных выражений.
Приложение Парсер регулярных выражений (Regexp Parser) состоит из четырех классов:
• RegExpWindow — окно, которое позволяет пользователю вводить регулярное выражение и показывает соответствующее дерево грамматического разбора;
• RegExpParser формирует дерево грамматического разбора для заданного регулярного выражения;
• RegExpModel — модель дерева, используемая деревом грамматического разбора;
• Node (вершина) представляет один элемент в дереве грамматического разбора.
Давайте начнем с класса Node:
01 class Node {
02 public:
03 enum Type { RegExp, Expression, Term, Factor, Atom, Terminal };
04 Node(Type type, const QString &str = "");
05 ~Node();
06 Type type;
07 QString str;
08 Node *parent;
09 QList<Node *> children;
10 };
Каждая вершина имеет тип, строку (которая может быть пустой), ссылку на родительский элемент (которая может быть нулевой) и список дочерних вершин (который может быть пустым).
01 Node::Node(Type type, const QString &str)
02 {
03 this->type = type;
04 this->str = str;
05 parent = 0;
06 }
Конструктор просто инициализирует тип и строку вершины. Поскольку все данные открыты, в программном коде, использующим Node, можно непосредственно манипулировать типом, строкой, родительским элементом и дочерними элементами.
01 Node::~Node()
02 {
03 qDeleteAll(children);
04 }
Функция qDeleteAll() проходит no всем указателям контейнера и вызывает оператор delete для каждого из них. Она не устанавливает указатели в 0, поэтому, если она используется вне деструктора, обычно за ней следует вызов функции clear() для контейнера, содержащего указатели.
Теперь, когда мы определили элементы наших данных (представленные вершиной Node), мы готовы создать модель:
01 class RegExpModel : public QAbstractItemModel
02 {
03 public:
04 RegExpModel(QObject *parent = 0);
05 ~RegExpModel();
06 void setRootNode(Node *node);
07 QModelIndex index(int row, int column,
08 const QModelIndex &parent) const;
09 QModelIndex parent(const QModelIndex &child) const;
10 int rowCount(const QModelIndex &parent) const;
11 int columnCount(const QModelIndex &parent) const;
12 QVariant data(const QModelIndex &index, int role) const;
13 QVariant headerData(int section,
14 Qt::Orientation Orientation, int role) const;
15 private:
16 Node *nodeFromIndex(const QModelIndex &index) const;
17 Node *rootNode;
18 };
На этот раз мы построили подкласс на основе класса QAbstractItemModel, а не на основе его удобного подкласса QAbstractTableModel, потому что мы хотим создать иерархическую модель. Нам необходимо переопределить те же самые функции и, кроме того, требуется реализовать функции index() и parent(). Для установки данных модели предусмотрена функция setRootNode(), при вызове которой должна задаваться корневая вершина дерева грамматического разбора.
01 RegExpModeclass="underline" :RegExpModel(QObject *parent)
02 : QAbstractItemModel(parent)
03 {
04 rootNode = 0;
05 }
В конструкторе модели нам надо просто задать корневой вершине безопасное нулевое значение и передать указатель parent базовому классу.
01 RegExpModeclass="underline" :~RegExpModel()
02 {
03 delete rootNode;
04 }
В деструкторе мы удаляем корневую вершину. Если корневая вершина имеет дочерние вершины, то каждая из них удаляется и эта процедура повторяется рекурсивно деструктором Node.