/**************************************************************************** ** ** Copyright (C) 2008-2010 Nokia Corporation and/or its subsidiary(-ies). ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the doxygen2qthelp project on Trolltech Labs. ** ** This file may be used under the terms of the GNU General Public ** License version 2.0 or 3.0 as published by the Free Software Foundation ** and appearing in the file LICENSE.GPL included in the packaging of ** this file. Please review the following information to ensure GNU ** General Public Licensing requirements will be met: ** http://www.fsf.org/licensing/licenses/info/GPLv2.html and ** http://www.gnu.org/copyleft/gpl.html. ** ** If you are unsure which license is appropriate for your use, please ** contact the sales department at qt-sales@nokia.com. ** ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. ** ****************************************************************************/ #include "htmlhelpparser_p.h" #include "simplehtmlparser_p.h" #include "qhelpdatainterface_p.h" #include #include #include #include #include #ifdef HELP_LIB_DATA_DEBUG # include #endif QT_BEGIN_NAMESPACE class ContainerNode; class LinkNode; class HtmlHelpNode { public: virtual ~HtmlHelpNode() { } virtual bool isContainer() const = 0; virtual ContainerNode * toContainer() const = 0; virtual const LinkNode * toLink() const = 0; #ifdef HELP_LIB_DATA_DEBUG virtual void dump(int level) const = 0; #endif }; class ContainerNode : public HtmlHelpNode { public: QList m_children; ~ContainerNode() { foreach(const HtmlHelpNode * node, m_children) { delete node; } } static void insertItem(QHelpDataIndexItem &item, QList &dest, QSet &pastEntries, QSet &pastIds); QHelpDataContentItem * convertAndStealChildren( QHelpDataContentItem * parent, const QString &name = QString(), const QString &href = QString()); QList convertAndStealChildren(int level, const QString &rootLabel, QSet &pastEntries, QSet &pastIds); bool isContainer() const { return true; } ContainerNode * toContainer() const { return const_cast(this); } const LinkNode * toLink() const { return NULL; } #ifdef HELP_LIB_DATA_DEBUG void dump(int level = 0) const { QString indent; for (int i = 0; i < level; i++) { indent.append(" _"); } foreach(const HtmlHelpNode * node, m_children) { Q_ASSERT(node != NULL); node->dump(level + 1); } } #endif }; class LinkNode : public HtmlHelpNode { public: QString m_name; QString m_subName; QString m_href; LinkNode(const QString &name, const QString &subName, const QString &href) : m_name(name), m_subName(subName), m_href(href) { } QHelpDataContentItem * convert(QHelpDataContentItem * parent) const; QHelpDataIndexItem convert() const; bool isContainer() const { return false; } ContainerNode * toContainer() const { return NULL; } const LinkNode * toLink() const { return this; } #ifdef HELP_LIB_DATA_DEBUG void dump(int level = 0) const { QString indent; for (int i = 0; i < level; i++) { indent.append(" _"); } qDebug() << indent << m_name << m_subName << m_href; } #endif }; QHelpDataContentItem * ContainerNode::convertAndStealChildren( QHelpDataContentItem * parent, const QString &name, const QString &href) { QHelpDataContentItem * const res = new QHelpDataContentItem(parent, name, href); const int childCount = m_children.count(); for (int i = 0; i < childCount; i++) { QHelpDataContentItem * item = NULL; HtmlHelpNode * const node = m_children[i]; Q_ASSERT(node != NULL); if (node->isContainer()) { // No Name node before, item = node->toContainer()->convertAndStealChildren(res); } else { const LinkNode * const linkNode = node->toLink(); if ((i < childCount - 1) && (m_children[i + 1]->isContainer())) { // Not last node, check successor HtmlHelpNode * const succ = m_children[i + 1]; item = succ->toContainer()->convertAndStealChildren( res, linkNode->m_name, linkNode->m_href); delete succ; // Skip container node i++; } else { // Successor (if existing) is a link node too // Delete link to the 'main page' so we don't have two of them if ((parent != NULL) || (i != 0) || (linkNode->m_href != href)) { if (!linkNode->m_href.isEmpty()) { item = linkNode->convert(res); } } } } delete node; } m_children.clear(); return res; } static uint qHash(const QHelpDataIndexItem &key) { return qHash(key.name) ^ qHash(key.reference); } /*static*/ void ContainerNode::insertItem(QHelpDataIndexItem &item, QList &dest, QSet &pastEntries, QSet &pastIds) { const QString &id = item.identifier; const bool nameRefPairFound = pastEntries.contains(item); const bool idFound = pastIds.contains(id); if (!(nameRefPairFound && idFound)) { if (nameRefPairFound) { if (idFound) { // (1) Name with exact same link already there // (2) ID already taken // \--> don't add Q_ASSERT(false); } else { // (1) Name with exact same link already there // (2) ID not taken // \--> add as ("", id, ref) item.name = QString(); } } else { if (idFound) { // (1) Name not linking to this reference yet // (2) ID already taken // \--> add as (name, "", ref) item.identifier = QString(); } else { // (1) Name not linking to this reference yet // (2) ID not taken // \--> add unmodified } } #ifdef HELP_LIB_DATA_DEBUG item.dump(); #endif pastEntries.insert(item); pastIds.insert(id); dest << item; } } QList ContainerNode::convertAndStealChildren( int level, const QString &rootLabel, QSet &pastEntries, QSet &pastIds) { QList res; QString workLabel(rootLabel); foreach (HtmlHelpNode * node, m_children) { if (node->isContainer()) { res += node->toContainer()->convertAndStealChildren( level + 1, workLabel, pastEntries, pastIds); } else { QHelpDataIndexItem item = node->toLink()->convert(); insertItem(item, res, pastEntries, pastIds); if (level == 0) { workLabel = item.name; } else { // -- Sample input index tree // clone {#1, #2} // * Chicken {#3} // * Salad {#4} // // Chicken {#3} // * clone {#1} // // Salad {#4} // * clone {#2} // // -- Created index entries // NAME | ID | REF // --------+----------------+-------- // clone | clone | 1 // clone | | 2 // Chicken | Chicken | 3 // | clone::Chicken | 3 // | Chicken::clone | 1 // Salad | Salad | 4 // | clone::Salad | 4 // | Salad::clone | 2 const QString id = rootLabel + QLatin1String("::") + item.name; QHelpDataIndexItem extraItem = QHelpDataIndexItem(QString(), id, item.reference); insertItem(extraItem, res, pastEntries, pastIds); } } delete node; } m_children.clear(); return res; } QHelpDataContentItem * LinkNode::convert(QHelpDataContentItem * parent) const { return new QHelpDataContentItem(parent, m_name, m_href); } QHelpDataIndexItem LinkNode::convert() const { return QHelpDataIndexItem(m_name, m_name, m_href); } HtmlHelpParser::HtmlHelpParser() : m_state(WAIT_NAME_OR_LOCAL), m_tree(new ContainerNode) { m_nodeStack.push(m_tree); } HtmlHelpParser::~HtmlHelpParser() { delete m_tree; } void HtmlHelpParser::parseFile(const QString &fileName) { QFile file(fileName); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { return; } QString content; while (!file.atEnd()) { content.append(file.readLine()); } file.close(); parseString(content); } void HtmlHelpParser::parseString(const QString &text) { SimpleHtmlParser parser; parser.parse(text, this); } const QList HtmlHelpParser::stealIndex() { QSet noDupEntries; QSet noDupIds; return m_tree->convertAndStealChildren(0, QString(), noDupEntries, noDupIds); } const QList HtmlHelpParser::stealToc( const QString &rootName, const QString &rootHref) { Q_ASSERT(m_tree != NULL); QHelpDataContentItem * const convertedTree = m_tree->convertAndStealChildren(NULL, rootName, rootHref); Q_ASSERT(convertedTree != NULL); #ifdef HELP_LIB_DATA_DEBUG convertedTree->dump(); #endif QList res; res.append(convertedTree); return res; } void HtmlHelpParser::onTagOpen(const QString &tagName, const QStringList &attributes) { #ifdef HELP_LIB_DATA_DEBUG qDebug() << "OPEN" << tagName << "ATTS" << attributes; #endif if (tagName == QString("ul")) { ContainerNode * const top = m_nodeStack.top(); Q_ASSERT(top != NULL); if (!((top == m_tree) && (m_tree->m_children.isEmpty()))) { ContainerNode * const node = new ContainerNode; Q_ASSERT(node != NULL); top->m_children.append(node); m_nodeStack.push(node); } } else if (tagName == QString("param")) { const QString name = findAttribute(attributes, "name"); if (name.isEmpty()) { return; } const QString value = findAttribute(attributes, "value"); if (value.isEmpty()) { return; } if (name == QString("Name")) { switch (m_state) { case WAIT_NAME_OR_LOCAL: // First entry is a name, nothing fixed yet m_name = value; m_state = WAIT_NAME_NAME_OR_NAME_LOCAL; break; case WAIT_NAME_NAME_OR_NAME_LOCAL: // Second entry is a name too -> must be a list m_subName = value; m_state = WAIT_NAME_NAME_LOCAL; break; case WAIT_NAME_NAME_LOCAL_NAME: m_subName = value; m_state = WAIT_NAME_NAME_LOCAL; break; case WAIT_LOCAL_NAME: { ContainerNode * const top = m_nodeStack.top(); Q_ASSERT(top != NULL); top->m_children.append(new LinkNode(value, "", m_href)); } m_state = WAIT_NAME_OR_LOCAL; break; default: ; // NOOP } } else if (name == QString("Local")) { switch (m_state) { case WAIT_NAME_OR_LOCAL: // First entry is a local -> must be a single link, name follows m_href = value; m_state = WAIT_LOCAL_NAME; break; case WAIT_NAME_NAME_OR_NAME_LOCAL: // Second entry is a href -> must be a single link { ContainerNode * const top = m_nodeStack.top(); Q_ASSERT(top != NULL); top->m_children.append(new LinkNode(m_name, "", value)); } m_state = WAIT_NAME_OR_LOCAL; break; case WAIT_NAME_NAME_LOCAL: { ContainerNode * const top = m_nodeStack.top(); Q_ASSERT(top != NULL); top->m_children.append(new LinkNode(m_name, m_subName, value)); } m_state = WAIT_NAME_NAME_LOCAL_NAME; break; default: ; // NOOP } } } } void HtmlHelpParser::onTagClose(const QString &tagName) { #ifdef HELP_LIB_DATA_DEBUG qDebug() << "CLOSE" << tagName; #endif if (tagName == QString("ul")) { if (m_nodeStack.size() > 1) { m_nodeStack.pop(); } } else if (tagName == QString("object")) { switch (m_state) { case WAIT_NAME_NAME_OR_NAME_LOCAL: // Name only, no href { ContainerNode * const top = m_nodeStack.top(); Q_ASSERT(top != NULL); top->m_children.append(new LinkNode(m_name, "", "")); } break; default: ; // NOOP } m_state = WAIT_NAME_OR_LOCAL; m_name.clear(); m_subName.clear(); } } void HtmlHelpParser::onTextChunk(const QString & /*text*/) { // NOOP } void HtmlHelpParser::onStop() { m_nodeStack.clear(); #ifdef HELP_LIB_DATA_DEBUG m_tree->dump(); #endif } /*static*/ QString HtmlHelpParser::findAttribute(const QStringList &list, const QString &key) { const int count = list.count(); for (int i = 0; i < count - 1; i += 2) { if (list.at(i) == key) { return list.at(i + 1); } } return QString(); } QT_END_NAMESPACE