/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "highlightdefinitionhandler.h" #include "highlightdefinition.h" #include "highlighterexception.h" #include "specificrules.h" #include "itemdata.h" #include "keywordlist.h" #include "context.h" #include "manager.h" #include #include #include using namespace TextEditor; using namespace Internal; namespace { static const QLatin1String kName("name"); static const QLatin1String kList("list"); static const QLatin1String kItem("item"); static const QLatin1String kContext("context"); static const QLatin1String kAttribute("attribute"); static const QLatin1String kDynamic("dynamic"); static const QLatin1String kFallthrough("fallthrough"); static const QLatin1String kLineEndContext("lineEndContext"); static const QLatin1String kLineEmptyContext("lineEmptyContext"); static const QLatin1String kLineBeginContext("lineBeginContext"); static const QLatin1String kFallthroughContext("fallthroughContext"); static const QLatin1String kBeginRegion("beginRegion"); static const QLatin1String kEndRegion("endRegion"); static const QLatin1String kLookAhead("lookAhead"); static const QLatin1String kFirstNonSpace("firstNonSpace"); static const QLatin1String kColumn("column"); static const QLatin1String kItemData("itemData"); static const QLatin1String kDefStyleNum("defStyleNum"); static const QLatin1String kColor("color"); static const QLatin1String kSelColor("selColor"); static const QLatin1String kItalic("italic"); static const QLatin1String kBold("bold"); static const QLatin1String kUnderline("underline"); static const QLatin1String kStrikeout("strikeout"); static const QLatin1String kChar("char"); static const QLatin1String kChar1("char1"); static const QLatin1String kString("String"); static const QLatin1String kInsensitive("insensitive"); static const QLatin1String kMinimal("minimal"); static const QLatin1String kKeywords("keywords"); static const QLatin1String kCaseSensitive("casesensitive"); static const QLatin1String kWeakDeliminator("weakDeliminator"); static const QLatin1String kAdditionalDeliminator("additionalDeliminator"); static const QLatin1String kComment("comment"); static const QLatin1String kPosition("position"); static const QLatin1String kSingleLine("singleline"); static const QLatin1String kMultiLine("multiline"); static const QLatin1String kStart("start"); static const QLatin1String kEnd("end"); static const QLatin1String kRegion("region"); static const QLatin1String kDetectChar("DetectChar"); static const QLatin1String kDetect2Chars("Detect2Chars"); static const QLatin1String kAnyChar("AnyChar"); static const QLatin1String kStringDetect("StringDetect"); static const QLatin1String kWordDetect("WordDetect"); static const QLatin1String kRegExpr("RegExpr"); static const QLatin1String kKeyword("keyword"); static const QLatin1String kInt("Int"); static const QLatin1String kFloat("Float"); static const QLatin1String kHlCOct("HlCOct"); static const QLatin1String kHlCHex("HlCHex"); static const QLatin1String kHlCStringChar("HlCStringChar"); static const QLatin1String kHlCChar("HlCChar"); static const QLatin1String kRangeDetect("RangeDetect"); static const QLatin1String kLineContinue("LineContinue"); static const QLatin1String kIncludeRules("IncludeRules"); static const QLatin1String kDetectSpaces("DetectSpaces"); static const QLatin1String kDetectIdentifier("DetectIdentifier"); static const QLatin1String kIncludeAttrib("includeAttrib"); static const QLatin1String kFolding("folding"); static const QLatin1String kIndentationSensitive("indentationsensitive"); static const QLatin1String kHash("#"); static const QLatin1String kDoubleHash("##"); } HighlightDefinitionHandler:: HighlightDefinitionHandler(const QSharedPointer &definition) : m_definition(definition), m_processingKeyword(false), m_initialContext(true) {} HighlightDefinitionHandler::~HighlightDefinitionHandler() {} bool HighlightDefinitionHandler::startDocument() { return true; } bool HighlightDefinitionHandler::endDocument() { processIncludeRules(); return true; } bool HighlightDefinitionHandler::startElement(const QString &, const QString &, const QString &qName, const QXmlAttributes &atts) { if (qName == kList) listElementStarted(atts); else if (qName == kItem) itemElementStarted(); else if (qName == kContext) contextElementStarted(atts); else if (qName == kItemData) itemDataElementStarted(atts); else if (qName == kComment) commentElementStarted(atts); else if (qName == kKeywords) keywordsElementStarted(atts); else if (qName == kFolding) foldingElementStarted(atts); else if (qName == kDetectChar) detectCharStarted(atts); else if (qName == kDetect2Chars) detect2CharsStarted(atts); else if (qName == kAnyChar) anyCharStarted(atts); else if (qName == kStringDetect) stringDetectedStarted(atts); else if (qName == kWordDetect) wordDetectStarted(atts); else if (qName == kRegExpr) regExprStarted(atts); else if (qName == kKeyword) keywordStarted(atts); else if (qName == kInt) intStarted(atts); else if (qName == kFloat) floatStarted(atts); else if (qName == kHlCOct) hlCOctStarted(atts); else if (qName == kHlCHex) hlCHexStarted(atts); else if (qName == kHlCStringChar) hlCStringCharStarted(atts); else if (qName == kHlCChar) hlCCharStarted(atts); else if (qName == kRangeDetect) rangeDetectStarted(atts); else if (qName == kLineContinue) lineContinue(atts); else if (qName == kIncludeRules) includeRulesStarted(atts); else if (qName == kDetectSpaces) detectSpacesStarted(atts); else if (qName == kDetectIdentifier) detectIdentifier(atts); return true; } bool HighlightDefinitionHandler::endElement(const QString &, const QString &, const QString &qName) { if (qName == kItem) { m_currentList->addKeyword(m_currentKeyword.trimmed()); m_processingKeyword = false; } else if (qName == kDetectChar || qName == kDetect2Chars || qName == kAnyChar || qName == kStringDetect || qName == kWordDetect || qName == kRegExpr || qName == kKeyword || qName == kInt || qName == kFloat || qName == kHlCOct || qName == kHlCHex || qName == kHlCStringChar || qName == kHlCChar || qName == kRangeDetect || qName == kLineContinue || qName == kDetectSpaces || qName == kDetectIdentifier) { m_currentRule.pop(); } return true; } bool HighlightDefinitionHandler::characters(const QString& ch) { // Character data of an element may be reported in more than one chunk. if (m_processingKeyword) m_currentKeyword.append(ch); return true; } void HighlightDefinitionHandler::listElementStarted(const QXmlAttributes &atts) { m_currentList = m_definition->createKeywordList(atts.value(kName)); } void HighlightDefinitionHandler::itemElementStarted() { m_currentKeyword.clear(); m_processingKeyword = true; } void HighlightDefinitionHandler::contextElementStarted(const QXmlAttributes &atts) { m_currentContext = m_definition->createContext(atts.value(kName), m_initialContext); m_currentContext->setDefinition(m_definition); m_currentContext->setItemData(atts.value(kAttribute)); m_currentContext->setDynamic(atts.value(kDynamic)); m_currentContext->setFallthrough(atts.value(kFallthrough)); m_currentContext->setFallthroughContext(atts.value(kFallthroughContext)); m_currentContext->setLineBeginContext(atts.value(kLineBeginContext)); m_currentContext->setLineEndContext(atts.value(kLineEndContext)); m_currentContext->setLineEmptyContext(atts.value(kLineEmptyContext)); m_initialContext = false; } void HighlightDefinitionHandler::ruleElementStarted(const QXmlAttributes &atts, const QSharedPointer &rule) { const QString context = atts.value(kContext); // The definition of a rule is not necessarily the same of its enclosing context because of // externally included rules. rule->setDefinition(m_definition); rule->setItemData(atts.value(kAttribute)); rule->setContext(context); rule->setBeginRegion(atts.value(kBeginRegion)); rule->setEndRegion(atts.value(kEndRegion)); rule->setLookAhead(atts.value(kLookAhead)); rule->setFirstNonSpace(atts.value(kFirstNonSpace)); rule->setColumn(atts.value(kColumn)); if (context.contains(kDoubleHash)) { IncludeRulesInstruction includeInstruction(context, m_currentContext->rules().size(), QString()); m_currentContext->addIncludeRulesInstruction(includeInstruction); } if (m_currentRule.isEmpty()) m_currentContext->addRule(rule); else m_currentRule.top()->addChild(rule); m_currentRule.push(rule); } void HighlightDefinitionHandler::itemDataElementStarted(const QXmlAttributes &atts) const { QSharedPointer itemData = m_definition->createItemData(atts.value(kName)); itemData->setStyle(atts.value(kDefStyleNum)); itemData->setColor(atts.value(kColor)); itemData->setSelectionColor(atts.value(kSelColor)); itemData->setItalic(atts.value(kItalic)); itemData->setBold(atts.value(kBold)); itemData->setUnderlined(atts.value(kUnderline)); itemData->setStrikeOut(atts.value(kStrikeout)); } void HighlightDefinitionHandler::commentElementStarted(const QXmlAttributes &atts) const { const QString &commentType = atts.value(kName); if (commentType.compare(kSingleLine, Qt::CaseInsensitive) == 0) { m_definition->setSingleLineComment(atts.value(kStart)); m_definition->setCommentAfterWhitespaces(atts.value(kPosition)); } else if (commentType.compare(kMultiLine, Qt::CaseInsensitive) == 0) { m_definition->setMultiLineCommentStart(atts.value(kStart)); m_definition->setMultiLineCommentEnd(atts.value(kEnd)); m_definition->setMultiLineCommentRegion(atts.value(kRegion)); } } void HighlightDefinitionHandler::keywordsElementStarted(const QXmlAttributes &atts) const { // Global case sensitivity appears last in the document (required by dtd) and is set here. m_definition->setKeywordsSensitive(atts.value(kCaseSensitive)); m_definition->removeDelimiters(atts.value(kWeakDeliminator)); m_definition->addDelimiters(atts.value(kAdditionalDeliminator)); //@todo: wordWrapDelimiters? } void HighlightDefinitionHandler::foldingElementStarted(const QXmlAttributes &atts) const { m_definition->setIndentationBasedFolding(atts.value(kIndentationSensitive)); } void HighlightDefinitionHandler::detectCharStarted(const QXmlAttributes &atts) { auto rule = new DetectCharRule; rule->setChar(atts.value(kChar)); rule->setActive(atts.value(kDynamic)); ruleElementStarted(atts, QSharedPointer(rule)); } void HighlightDefinitionHandler::detect2CharsStarted(const QXmlAttributes &atts) { auto rule = new Detect2CharsRule; rule->setChar(atts.value(kChar)); rule->setChar1(atts.value(kChar1)); rule->setActive(atts.value(kDynamic)); ruleElementStarted(atts, QSharedPointer(rule)); } void HighlightDefinitionHandler::anyCharStarted(const QXmlAttributes &atts) { auto rule = new AnyCharRule; rule->setCharacterSet(atts.value(kString)); ruleElementStarted(atts, QSharedPointer(rule)); } void HighlightDefinitionHandler::stringDetectedStarted(const QXmlAttributes &atts) { auto rule = new StringDetectRule; rule->setString(atts.value(kString)); rule->setInsensitive(atts.value(kInsensitive)); rule->setActive(atts.value(kDynamic)); ruleElementStarted(atts, QSharedPointer(rule)); } void HighlightDefinitionHandler::wordDetectStarted(const QXmlAttributes &atts) { auto rule = new WordDetectRule; rule->setString(atts.value(kString)); rule->setInsensitive(atts.value(kInsensitive)); rule->setActive(atts.value(kDynamic)); ruleElementStarted(atts, QSharedPointer(rule)); } void HighlightDefinitionHandler::regExprStarted(const QXmlAttributes &atts) { auto rule = new RegExprRule; rule->setPattern(atts.value(kString)); rule->setMinimal(atts.value(kMinimal)); rule->setInsensitive(atts.value(kInsensitive)); rule->setActive(atts.value(kDynamic)); ruleElementStarted(atts, QSharedPointer(rule)); } void HighlightDefinitionHandler::keywordStarted(const QXmlAttributes &atts) { auto rule = new KeywordRule(m_definition); try { rule->setList(atts.value(kString)); } catch (const HighlighterException &e) { // Handle broken files. makefile.xml references an invalid list. Core::MessageManager::write( QCoreApplication::translate("GenericHighlighter", "Generic highlighter warning: %1") .arg(e.message())); } rule->setInsensitive(atts.value(kInsensitive)); ruleElementStarted(atts, QSharedPointer(rule)); } void HighlightDefinitionHandler::intStarted(const QXmlAttributes &atts) { ruleElementStarted(atts, QSharedPointer(new IntRule)); } void HighlightDefinitionHandler::floatStarted(const QXmlAttributes &atts) { ruleElementStarted(atts, QSharedPointer(new FloatRule)); } void HighlightDefinitionHandler::hlCOctStarted(const QXmlAttributes &atts) { ruleElementStarted(atts, QSharedPointer(new HlCOctRule)); } void HighlightDefinitionHandler::hlCHexStarted(const QXmlAttributes &atts) { ruleElementStarted(atts, QSharedPointer(new HlCHexRule)); } void HighlightDefinitionHandler::hlCStringCharStarted(const QXmlAttributes &atts) { ruleElementStarted(atts, QSharedPointer(new HlCStringCharRule)); } void HighlightDefinitionHandler::hlCCharStarted(const QXmlAttributes &atts) { ruleElementStarted(atts, QSharedPointer(new HlCCharRule)); } void HighlightDefinitionHandler::rangeDetectStarted(const QXmlAttributes &atts) { auto rule = new RangeDetectRule; rule->setChar(atts.value(kChar)); rule->setChar1(atts.value(kChar1)); ruleElementStarted(atts, QSharedPointer(rule)); } void HighlightDefinitionHandler::lineContinue(const QXmlAttributes &atts) { ruleElementStarted(atts, QSharedPointer(new LineContinueRule)); } void HighlightDefinitionHandler::includeRulesStarted(const QXmlAttributes &atts) { // Include rules are treated as instructions for latter processing. IncludeRulesInstruction instruction(atts.value(kContext), m_currentContext->rules().size(), atts.value(kIncludeAttrib)); // Include rules (as many others) are not allowed as a child. m_currentContext->addIncludeRulesInstruction(instruction); } void HighlightDefinitionHandler::detectSpacesStarted(const QXmlAttributes &atts) { ruleElementStarted(atts, QSharedPointer(new DetectSpacesRule)); } void HighlightDefinitionHandler::detectIdentifier(const QXmlAttributes &atts) { ruleElementStarted(atts, QSharedPointer(new DetectIdentifierRule)); } void HighlightDefinitionHandler::processIncludeRules() const { const QHash > &allContexts = m_definition->contexts(); foreach (const QSharedPointer &context, allContexts) processIncludeRules(context); } void HighlightDefinitionHandler::processIncludeRules(const QSharedPointer &context) const { if (context->includeRulesInstructions().isEmpty()) return; int rulesIncluded = 0; const QList &instructions = context->includeRulesInstructions(); foreach (const IncludeRulesInstruction &instruction, instructions) { QSharedPointer sourceContext; const QString &sourceName = instruction.sourceContext(); if (sourceName.contains(kDoubleHash)) { // This refers to an external definition. Context can be specified before the double // hash (e.g. Normal##Javascript). If it isn't, the rules included are the ones from its // initial context. Others contexts and rules from the external definition will work // transparently to the highlighter. This is because contexts and rules know the // definition they are from. const QStringList values = sourceName.split(kDoubleHash); if (values.count() != 2) return; const QString externalContext = values.at(0); const QString externalName = values.at(1); const QString &id = Manager::instance()->definitionIdByName(externalName); // If there is an incorrect circular dependency among definitions this is skipped. if (Manager::instance()->isBuildingDefinition(id)) continue; const QSharedPointer &externalDefinition = Manager::instance()->definition(id); if (externalDefinition.isNull() || !externalDefinition->isValid()) continue; if (externalContext.isEmpty()) sourceContext = externalDefinition->initialContext(); else sourceContext = externalDefinition->context(externalContext); } else if (!sourceName.startsWith(kHash)) { sourceContext = m_definition->context(sourceName); // Recursion is done only for context direct rules. Child rules are not processed // because they cannot be include rules. processIncludeRules(sourceContext); } else { continue; } if (instruction.replaceItemData()) { context->setItemData(sourceContext->itemData()); context->setDefinition(sourceContext->definition()); } foreach (QSharedPointer rule, sourceContext->rules()) { context->addRule(rule, instruction.indexHint() + rulesIncluded); ++rulesIncluded; } } context->clearIncludeRulesInstructions(); }