1 /**************************************************************************
2 **
3 ** This file is part of Qt Creator
4 **
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
6 **
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
8 **
9 ** No Commercial Usage
10 **
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 **
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file.  Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 **
25 ** In addition, as a special exception, Nokia gives you certain additional
26 ** rights.  These rights are described in the Nokia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 **
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
31 **
32 **************************************************************************/
33 
34 #include "highlightdefinitionhandler.h"
35 #include "highlightdefinition.h"
36 #include "specificrules.h"
37 #include "itemdata.h"
38 #include "keywordlist.h"
39 #include "context.h"
40 #include "reuse.h"
41 #include "manager2.h"
42 #include "highlighterexception.h"
43 #include <QDebug>
44 
45 #include <QtCore/QLatin1String>
46 
47 using namespace TextEditor;
48 using namespace Internal;
49 
50 namespace {
51     static const QLatin1String kName("name");
52     static const QLatin1String kList("list");
53     static const QLatin1String kItem("item");
54     static const QLatin1String kContext("context");
55     static const QLatin1String kAttribute("attribute");
56     static const QLatin1String kDynamic("dynamic");
57     static const QLatin1String kFallthrough("fallthrough");
58     static const QLatin1String kLineEndContext("lineEndContext");
59     static const QLatin1String kLineBeginContext("lineBeginContext");
60     static const QLatin1String kFallthroughContext("fallthroughContext");
61     static const QLatin1String kBeginRegion("beginRegion");
62     static const QLatin1String kEndRegion("endRegion");
63     static const QLatin1String kLookAhead("lookAhead");
64     static const QLatin1String kFirstNonSpace("firstNonSpace");
65     static const QLatin1String kColumn("column");
66     static const QLatin1String kItemData("itemData");
67     static const QLatin1String kDefStyleNum("defStyleNum");
68     static const QLatin1String kColor("color");
69     static const QLatin1String kSelColor("selColor");
70     static const QLatin1String kItalic("italic");
71     static const QLatin1String kBold("bold");
72     static const QLatin1String kUnderline("underline");
73     static const QLatin1String kStrikeout("strikeOut");
74     static const QLatin1String kSpellChecking("spellChecking");
75     static const QLatin1String kChar("char");
76     static const QLatin1String kChar1("char1");
77     static const QLatin1String kString("String");
78     static const QLatin1String kInsensitive("insensitive");
79     static const QLatin1String kMinimal("minimal");
80     static const QLatin1String kKeywords("keywords");
81     static const QLatin1String kCaseSensitive("casesensitive");
82     static const QLatin1String kWeakDeliminator("weakDeliminator");
83     static const QLatin1String kAdditionalDeliminator("additionalDeliminator");
84     static const QLatin1String kWordWrapDeliminator("wordWrapDeliminator");
85     static const QLatin1String kComment("comment");
86     static const QLatin1String kPosition("position");
87     static const QLatin1String kSingleLine("singleline");
88     static const QLatin1String kMultiLine("multiline");
89     static const QLatin1String kStart("start");
90     static const QLatin1String kEnd("end");
91     static const QLatin1String kRegion("region");
92     static const QLatin1String kDetectChar("DetectChar");
93     static const QLatin1String kDetect2Chars("Detect2Chars");
94     static const QLatin1String kAnyChar("AnyChar");
95     static const QLatin1String kStringDetect("StringDetect");
96     static const QLatin1String kRegExpr("RegExpr");
97     static const QLatin1String kKeyword("keyword");
98     static const QLatin1String kInt("Int");
99     static const QLatin1String kFloat("Float");
100     static const QLatin1String kHlCOct("HlCOct");
101     static const QLatin1String kHlCHex("HlCHex");
102     static const QLatin1String kHlCStringChar("HlCStringChar");
103     static const QLatin1String kHlCChar("HlCChar");
104     static const QLatin1String kRangeDetect("RangeDetect");
105     static const QLatin1String kLineContinue("LineContinue");
106     static const QLatin1String kIncludeRules("IncludeRules");
107     static const QLatin1String kDetectSpaces("DetectSpaces");
108     static const QLatin1String kDetectIdentifier("DetectIdentifier");
109     static const QLatin1String kLanguage("language");
110     static const QLatin1String kExtensions("extensions");
111     static const QLatin1String kIncludeAttrib("includeAttrib");
112     static const QLatin1String kFolding("folding");
113     static const QLatin1String kIndentationSensitive("indentationsensitive");
114     static const QLatin1String kHash("#");
115     static const QLatin1String kDoubleHash("##");
116 }
117 
118 HighlightDefinitionHandler::
HighlightDefinitionHandler(const QSharedPointer<HighlightDefinition> & definition)119 HighlightDefinitionHandler(const QSharedPointer<HighlightDefinition> &definition) :
120     m_definition(definition),
121     m_processingKeyword(false),
122     m_initialContext(true)
123 {}
124 
~HighlightDefinitionHandler()125 HighlightDefinitionHandler::~HighlightDefinitionHandler()
126 {}
127 
startDocument()128 bool HighlightDefinitionHandler::startDocument()
129 {
130     return true;
131 }
132 
endDocument()133 bool HighlightDefinitionHandler::endDocument()
134 {
135     processIncludeRules();
136     return true;
137 }
138 
startElement(const QString &,const QString &,const QString & qName,const QXmlAttributes & atts)139 bool HighlightDefinitionHandler::startElement(const QString &,
140                                               const QString &,
141                                               const QString &qName,
142                                               const QXmlAttributes &atts)
143 {
144     if (qName == kList) {
145         listElementStarted(atts);
146     } else if (qName == kItem) {
147         itemElementStarted();
148     } else if (qName == kContext) {
149         contextElementStarted(atts);
150     } else if (qName == kItemData) {
151         itemDataElementStarted(atts);
152     } else if (qName == kComment) {
153         commentElementStarted(atts);
154     } else if (qName == kKeywords) {
155         keywordsElementStarted(atts);
156     } else if (qName == kFolding) {
157         foldingElementStarted(atts);
158     } else if (qName == kDetectChar) {
159         detectCharStarted(atts);
160     } else if (qName == kDetect2Chars) {
161         detect2CharsStarted(atts);
162     } else if (qName == kAnyChar) {
163         anyCharStarted(atts);
164     } else if (qName == kStringDetect) {
165         stringDetectedStarted(atts);
166     } else if (qName == kRegExpr) {
167         regExprStarted(atts);
168     } else if (qName == kKeyword) {
169         keywordStarted(atts);
170     } else if (qName == kInt) {
171         intStarted(atts);
172     } else if (qName == kFloat) {
173         floatStarted(atts);
174     } else if (qName == kHlCOct) {
175         hlCOctStarted(atts);
176     } else if (qName == kHlCHex) {
177         hlCHexStarted(atts);
178     } else if (qName == kHlCStringChar) {
179         hlCStringCharStarted(atts);
180     } else if (qName == kHlCChar) {
181         hlCCharStarted(atts);
182     } else if (qName == kRangeDetect) {
183         rangeDetectStarted(atts);
184     } else if (qName == kLineContinue) {
185         lineContinue(atts);
186     } else if (qName == kIncludeRules) {
187         includeRulesStarted(atts);
188     } else if (qName == kDetectSpaces) {
189         detectSpacesStarted(atts);
190     } else if (qName == kDetectIdentifier) {
191         detectIdentifier(atts);
192     }
193 
194     return true;
195 }
196 
endElement(const QString &,const QString &,const QString & qName)197 bool HighlightDefinitionHandler::endElement(const QString &, const QString &, const QString &qName)
198 {
199     if (qName == kItem) {
200         m_currentList->addKeyword(m_currentKeyword.trimmed());
201         m_processingKeyword = false;
202     } else if (qName == kDetectChar || qName == kDetect2Chars || qName == kAnyChar ||
203                qName == kStringDetect || qName == kRegExpr || qName == kKeyword || qName == kInt ||
204                qName == kFloat || qName == kHlCOct || qName == kHlCHex || qName == kHlCStringChar ||
205                qName == kHlCChar || qName == kRangeDetect || qName == kLineContinue ||
206                qName == kDetectSpaces || qName == kDetectIdentifier) {
207         m_currentRule.pop();
208     }
209 
210     return true;
211 }
212 
characters(const QString & ch)213 bool HighlightDefinitionHandler::characters(const QString& ch)
214 {
215     // Character data of an element may be reported in more than one chunk.
216     if (m_processingKeyword)
217         m_currentKeyword.append(ch);
218 
219     return true;
220 }
221 
listElementStarted(const QXmlAttributes & atts)222 void HighlightDefinitionHandler::listElementStarted(const QXmlAttributes &atts)
223 {
224     m_currentList = m_definition->createKeywordList(atts.value(kName));
225 }
226 
itemElementStarted()227 void HighlightDefinitionHandler::itemElementStarted()
228 {
229     m_currentKeyword.clear();
230     m_processingKeyword = true;
231 }
232 
contextElementStarted(const QXmlAttributes & atts)233 void HighlightDefinitionHandler::contextElementStarted(const QXmlAttributes &atts)
234 {
235     m_currentContext = m_definition->createContext(atts.value(kName), m_initialContext);
236     m_currentContext->setDefinition(m_definition);
237     m_currentContext->setItemData(atts.value(kAttribute));
238     m_currentContext->setDynamic(atts.value(kDynamic));
239     m_currentContext->setFallthrough(atts.value(kFallthrough));
240     m_currentContext->setFallthroughContext(atts.value(kFallthroughContext));
241     m_currentContext->setLineBeginContext(atts.value(kLineBeginContext));
242     m_currentContext->setLineEndContext(atts.value(kLineEndContext));
243 
244     m_initialContext = false;
245 }
246 
ruleElementStarted(const QXmlAttributes & atts,const QSharedPointer<Rule> & rule)247 void HighlightDefinitionHandler::ruleElementStarted(const QXmlAttributes &atts,
248                                                     const QSharedPointer<Rule> &rule)
249 {
250     // The definition of a rule is not necessarily the same of its enclosing context because of
251     // externally included rules.
252     rule->setDefinition(m_definition);
253     rule->setItemData(atts.value(kAttribute));
254     rule->setContext(atts.value(kContext));
255     rule->setBeginRegion(atts.value(kBeginRegion));
256     rule->setEndRegion(atts.value(kEndRegion));
257     rule->setLookAhead(atts.value(kLookAhead));
258     rule->setFirstNonSpace(atts.value(kFirstNonSpace));
259     rule->setColumn(atts.value(kColumn));
260 
261     if (m_currentRule.isEmpty())
262         m_currentContext->addRule(rule);
263     else
264         m_currentRule.top()->addChild(rule);
265 
266     m_currentRule.push(rule);
267 }
268 
itemDataElementStarted(const QXmlAttributes & atts) const269 void HighlightDefinitionHandler::itemDataElementStarted(const QXmlAttributes &atts) const
270 {
271     QSharedPointer<ItemData> itemData = m_definition->createItemData(atts.value(kName));
272     itemData->setStyle(atts.value(kDefStyleNum));
273     itemData->setColor(atts.value(kColor));
274     itemData->setSelectionColor(atts.value(kSelColor));
275     itemData->setItalic(atts.value(kItalic));
276     itemData->setBold(atts.value(kBold));
277     itemData->setUnderlined(atts.value(kUnderline));
278     itemData->setStrikeOut(atts.value(kStrikeout));
279     itemData->setSpellChecking(atts.value(kSpellChecking));
280 }
281 
commentElementStarted(const QXmlAttributes & atts) const282 void HighlightDefinitionHandler::commentElementStarted(const QXmlAttributes &atts) const
283 {
284     const QString &commentType = atts.value(kName);
285     if (commentType.compare(kSingleLine, Qt::CaseInsensitive) == 0) {
286         m_definition->setSingleLineComment(atts.value(kStart));
287         m_definition->setCommentAfterWhitespaces(atts.value(kPosition));
288     } else if (commentType.compare(kMultiLine, Qt::CaseInsensitive) == 0) {
289         m_definition->setMultiLineCommentStart(atts.value(kStart));
290         m_definition->setMultiLineCommentEnd(atts.value(kEnd));
291         m_definition->setMultiLineCommentRegion(atts.value(kRegion));
292     }
293 }
294 
keywordsElementStarted(const QXmlAttributes & atts) const295 void HighlightDefinitionHandler::keywordsElementStarted(const QXmlAttributes &atts) const
296 {
297     // Global case sensitivity appears last in the document (required by dtd) and is set here.
298     m_definition->setKeywordsSensitive(atts.value(kCaseSensitive));
299     m_definition->removeDelimiters(atts.value(kWeakDeliminator));
300     m_definition->addDelimiters(atts.value(kAdditionalDeliminator));
301     //@todo: wordWrapDelimiters?
302 }
303 
foldingElementStarted(const QXmlAttributes & atts) const304 void HighlightDefinitionHandler::foldingElementStarted(const QXmlAttributes &atts) const
305 {
306     m_definition->setIndentationBasedFolding(atts.value(kIndentationSensitive));
307 }
308 
detectCharStarted(const QXmlAttributes & atts)309 void HighlightDefinitionHandler::detectCharStarted(const QXmlAttributes &atts)
310 {
311     DetectCharRule *rule = new DetectCharRule;
312     rule->setChar(atts.value(kChar));
313     rule->setActive(atts.value(kDynamic));
314     ruleElementStarted(atts, QSharedPointer<Rule>(rule));
315 }
316 
detect2CharsStarted(const QXmlAttributes & atts)317 void HighlightDefinitionHandler::detect2CharsStarted(const QXmlAttributes &atts)
318 {
319     Detect2CharsRule *rule = new Detect2CharsRule;
320     rule->setChar(atts.value(kChar));
321     rule->setChar1(atts.value(kChar1));
322     rule->setActive(atts.value(kDynamic));
323     ruleElementStarted(atts, QSharedPointer<Rule>(rule));
324 }
325 
anyCharStarted(const QXmlAttributes & atts)326 void HighlightDefinitionHandler::anyCharStarted(const QXmlAttributes &atts)
327 {
328     AnyCharRule *rule = new AnyCharRule;
329     rule->setCharacterSet(atts.value(kString));
330     ruleElementStarted(atts, QSharedPointer<Rule>(rule));
331 }
332 
stringDetectedStarted(const QXmlAttributes & atts)333 void HighlightDefinitionHandler::stringDetectedStarted(const QXmlAttributes &atts)
334 {
335     StringDetectRule *rule = new StringDetectRule;
336     rule->setString(atts.value(kString));
337     rule->setInsensitive(atts.value(kInsensitive));
338     rule->setActive(atts.value(kDynamic));
339     ruleElementStarted(atts, QSharedPointer<Rule>(rule));
340 }
341 
regExprStarted(const QXmlAttributes & atts)342 void HighlightDefinitionHandler::regExprStarted(const QXmlAttributes &atts)
343 {
344     RegExprRule *rule = new RegExprRule;
345     rule->setPattern(atts.value(kString));
346     rule->setMinimal(atts.value(kMinimal));
347     rule->setInsensitive(atts.value(kInsensitive));
348     rule->setActive(atts.value(kDynamic));
349     ruleElementStarted(atts, QSharedPointer<Rule>(rule));
350 }
351 
keywordStarted(const QXmlAttributes & atts)352 void HighlightDefinitionHandler::keywordStarted(const QXmlAttributes &atts)
353 {
354     KeywordRule *rule = new KeywordRule(m_definition);
355     rule->setList(atts.value(kString));
356     rule->setInsensitive(atts.value(kInsensitive));
357     ruleElementStarted(atts, QSharedPointer<Rule>(rule));
358 }
359 
intStarted(const QXmlAttributes & atts)360 void HighlightDefinitionHandler::intStarted(const QXmlAttributes &atts)
361 {
362     ruleElementStarted(atts, QSharedPointer<Rule>(new IntRule));
363 }
364 
floatStarted(const QXmlAttributes & atts)365 void HighlightDefinitionHandler::floatStarted(const QXmlAttributes &atts)
366 {
367     ruleElementStarted(atts, QSharedPointer<Rule>(new FloatRule));
368 }
369 
hlCOctStarted(const QXmlAttributes & atts)370 void HighlightDefinitionHandler::hlCOctStarted(const QXmlAttributes &atts)
371 {
372     ruleElementStarted(atts, QSharedPointer<Rule>(new HlCOctRule));
373 }
374 
hlCHexStarted(const QXmlAttributes & atts)375 void HighlightDefinitionHandler::hlCHexStarted(const QXmlAttributes &atts)
376 {
377     ruleElementStarted(atts, QSharedPointer<Rule>(new HlCHexRule));
378 }
379 
hlCStringCharStarted(const QXmlAttributes & atts)380 void HighlightDefinitionHandler::hlCStringCharStarted(const QXmlAttributes &atts)
381 {
382     ruleElementStarted(atts, QSharedPointer<Rule>(new HlCStringCharRule));
383 }
384 
hlCCharStarted(const QXmlAttributes & atts)385 void HighlightDefinitionHandler::hlCCharStarted(const QXmlAttributes &atts)
386 {
387     ruleElementStarted(atts, QSharedPointer<Rule>(new HlCCharRule));
388 }
389 
rangeDetectStarted(const QXmlAttributes & atts)390 void HighlightDefinitionHandler::rangeDetectStarted(const QXmlAttributes &atts)
391 {
392     RangeDetectRule *rule = new RangeDetectRule;
393     rule->setChar(atts.value(kChar));
394     rule->setChar1(atts.value(kChar1));
395     ruleElementStarted(atts, QSharedPointer<Rule>(rule));
396 }
397 
lineContinue(const QXmlAttributes & atts)398 void HighlightDefinitionHandler::lineContinue(const QXmlAttributes &atts)
399 {
400     ruleElementStarted(atts, QSharedPointer<Rule>(new LineContinueRule));
401 }
402 
includeRulesStarted(const QXmlAttributes & atts)403 void HighlightDefinitionHandler::includeRulesStarted(const QXmlAttributes &atts)
404 {
405     // Include rules are treated as instructions for latter processing.
406     IncludeRulesInstruction instruction(atts.value(kContext), m_currentContext->rules().size(),
407                                         atts.value(kIncludeAttrib));
408 
409     // Include rules (as many others) are not allowed as a child.
410     m_currentContext->addIncludeRulesInstruction(instruction);
411 }
412 
detectSpacesStarted(const QXmlAttributes & atts)413 void HighlightDefinitionHandler::detectSpacesStarted(const QXmlAttributes &atts)
414 {
415     ruleElementStarted(atts, QSharedPointer<Rule>(new DetectSpacesRule));
416 }
417 
detectIdentifier(const QXmlAttributes & atts)418 void HighlightDefinitionHandler::detectIdentifier(const QXmlAttributes &atts)
419 {
420     ruleElementStarted(atts, QSharedPointer<Rule>(new DetectIdentifierRule));
421 }
422 
processIncludeRules() const423 void HighlightDefinitionHandler::processIncludeRules() const
424 {
425     const QHash<QString, QSharedPointer<Context> > &allContexts = m_definition->contexts();
426     foreach (const QSharedPointer<Context> &context, allContexts)
427         processIncludeRules(context);
428 }
429 
processIncludeRules(const QSharedPointer<Context> & context) const430 void HighlightDefinitionHandler::processIncludeRules(const QSharedPointer<Context> &context) const
431 {
432     if (context->includeRulesInstructions().isEmpty())
433         return;
434 
435     int rulesIncluded = 0;
436     const QList<IncludeRulesInstruction> &instructions = context->includeRulesInstructions();
437     foreach (const IncludeRulesInstruction &instruction, instructions) {
438 
439         QSharedPointer<Context> sourceContext;
440         const QString &sourceName = instruction.sourceContext();
441         if (sourceName.startsWith(kDoubleHash)) {
442             // This refers to an external definition. The rules included are the ones from its
443             // initial context. Others contexts and rules from the external definition will work
444             // transparently to the highlighter. This is because contexts and rules know the
445             // definition they are from.
446             QString externalName = QString::fromRawData(sourceName.unicode() + 2,
447                                                         sourceName.length() - 2);
448             const QString &id = Manager2::instance()->definitionIdByName(externalName);
449 
450             // If there is an incorrect circular dependency among definitions this is skipped.
451             if (Manager2::instance()->isBuildingDefinition(id))
452                 continue;
453 
454             const QSharedPointer<HighlightDefinition> &externalDefinition =
455                 Manager2::instance()->definition(id);
456             if (externalDefinition.isNull())
457                 continue;
458 
459             sourceContext = externalDefinition->initialContext();
460         } else if (!sourceName.startsWith(kHash)) {
461             sourceContext = m_definition->context(sourceName);
462 
463             // Recursion is done only for context direct rules. Child rules are not processed
464             // because they cannot be include rules.
465             processIncludeRules(sourceContext);
466         } else {
467             continue;
468         }
469 
470         if (instruction.replaceItemData()) {
471             context->setItemData(sourceContext->itemData());
472             context->setDefinition(sourceContext->definition());
473         }
474 
475         foreach (QSharedPointer<Rule> rule, sourceContext->rules()) {
476             context->addRule(rule, instruction.indexHint() + rulesIncluded);
477             ++rulesIncluded;
478         }
479     }
480     context->clearIncludeRulesInstructions();
481 }
482