1 //
2 // Author: Lorenzo Bettini <http://www.lorenzobettini.it>, (C) 2004-2008
3 //
4 // Copyright: See COPYING file that comes with this distribution
5 //
6 
7 #ifdef HAVE_CONFIG_H
8 #include "config.h"
9 #endif
10 
11 #include "sourcehighlighter.h"
12 #include "highlighttoken.h"
13 #include "matchingparameters.h"
14 #include "highlightrule.h"
15 #include "formattermanager.h"
16 #include "highlightevent.h"
17 #include "highlighteventlistener.h"
18 #include "formatterparams.h"
19 
20 using namespace std;
21 
22 namespace srchilite {
23 
24 /// represents highlighting as default
25 static HighlightToken defaultHighlightToken;
26 static HighlightEvent defaultHighlightEvent(defaultHighlightToken,
27         HighlightEvent::FORMATDEFAULT);
28 
29 /// avoid generate an event if there's no one listening
30 #define GENERATE_EVENT(token, type) if (hasListeners()) notify(HighlightEvent(token, type));
31 
32 #define GENERATE_DEFAULT_EVENT(s) \
33     if (hasListeners()) { \
34         defaultHighlightToken.clearMatched(); \
35         defaultHighlightToken.addMatched("default", s); \
36         notify(defaultHighlightEvent); \
37     }
38 
39 #define UPDATE_START_IN_FORMATTER_PARAMS(start_index) if (formatterParams) formatterParams->start = start_index;
40 
SourceHighlighter(HighlightStatePtr mainState)41 SourceHighlighter::SourceHighlighter(HighlightStatePtr mainState) :
42     mainHighlightState(mainState), currentHighlightState(mainState),
43             stateStack(HighlightStateStackPtr(new HighlightStateStack)),
44             formatterManager(0), optimize(false), suspended(false),
45             formatterParams(0) {
46 }
47 
~SourceHighlighter()48 SourceHighlighter::~SourceHighlighter() {
49 }
50 
highlightParagraph(const std::string & paragraph)51 void SourceHighlighter::highlightParagraph(const std::string &paragraph) {
52     std::string::const_iterator start = paragraph.begin();
53     std::string::const_iterator end = paragraph.end();
54     bool matched = true;
55     HighlightToken token;
56     MatchingParameters params;
57 
58     // we're at the beginning
59     UPDATE_START_IN_FORMATTER_PARAMS(0);
60 
61     // note that we go on even if the string is empty, since it is crucial
62     // to try to match also the end of buffer (some rules rely on that)
63     while (matched) {
64         matched = currentHighlightState->findBestMatch(start, end, token,
65                 params);
66 
67         if (matched) {
68             if (token.prefix.size()) {
69                 // this is the index in the paragraph of the matched part
70                 UPDATE_START_IN_FORMATTER_PARAMS((std::distance(paragraph.begin(), start)));
71                 // format non matched part with the current state's default element
72                 format(currentHighlightState->getDefaultElement(), token.prefix);
73                 GENERATE_DEFAULT_EVENT(token.prefix);
74             }
75 
76             // the length of the previous matched string in the matched elem list
77             int prevLen = 0;
78             // now format the matched strings
79             for (MatchedElements::const_iterator it = token.matched.begin(); it
80                     != token.matched.end(); ++it) {
81                 // this is the index in the paragraph of the matched part
82                 UPDATE_START_IN_FORMATTER_PARAMS((std::distance(paragraph.begin(), start) + token.prefix.size() + prevLen));
83                 format(it->first, it->second);
84                 GENERATE_EVENT(token, HighlightEvent::FORMAT);
85                 prevLen += it->second.size();
86             }
87 
88             // now we're not at the beginning of line anymore, if we matched some chars
89             if (token.matchedSize)
90                 params.beginningOfLine = false;
91 
92             // check whether we must enter a new state
93             HighlightStatePtr nextState = getNextState(token);
94 
95             if (nextState.get()) {
96                 enterState(nextState);
97                 GENERATE_EVENT(token, HighlightEvent::ENTERSTATE);
98             } else if (token.rule->getExitLevel()) {
99                 // the rule requires to exit some states
100                 if (token.rule->getExitLevel() < 0) {
101                     exitAll();
102                 } else {
103                     exitState(token.rule->getExitLevel());
104                 }
105                 GENERATE_EVENT(token, HighlightEvent::EXITSTATE);
106             }
107 
108             // advance in the string, so that the part not matched
109             // can be highlighted in the next loop
110             start += (token.prefix.size() + token.matchedSize);
111         } else {
112             // no rule matched, so we highlight it with the current state's default element
113             // provided the string is not empty (if it is empty this is really useless)
114             if (start != end) {
115                 // this is the index in the paragraph of the matched part
116                 UPDATE_START_IN_FORMATTER_PARAMS((std::distance(paragraph.begin(), start)));
117                 const string s(start, end);
118                 format(currentHighlightState->getDefaultElement(), s);
119                 GENERATE_DEFAULT_EVENT(s);
120             }
121         }
122     }
123 
124     if (optimize)
125         flush(); // flush what we have buffered
126 }
127 
getNextState(const HighlightToken & token)128 HighlightStatePtr SourceHighlighter::getNextState(const HighlightToken &token) {
129     HighlightStatePtr nextState = token.rule->getNextState();
130 
131     if (token.rule->isNested()) {
132         // we must enter another instance of the current state
133         nextState = currentHighlightState;
134     }
135 
136     if (nextState.get() && nextState->getNeedsReferenceReplacement()) {
137         // perform replacement for the next state
138         // in case use the original state
139         if (nextState->getOriginalState().get()) {
140             // in case we had already performed replacements on the next state
141             nextState = nextState->getOriginalState();
142         }
143 
144         HighlightStatePtr copyState = HighlightStatePtr(new HighlightState(
145                 *nextState));
146         copyState->setOriginalState(nextState);
147         copyState->replaceReferences(token.matchedSubExps);
148         return copyState;
149     }
150 
151     return nextState;
152 }
153 
enterState(HighlightStatePtr state)154 void SourceHighlighter::enterState(HighlightStatePtr state) {
155     stateStack->push(currentHighlightState);
156     currentHighlightState = state;
157 }
158 
159 /**
160  * Exits level states (-1 means exit all states)
161  * @param level
162  */
exitState(int level)163 void SourceHighlighter::exitState(int level) {
164     // remove additional levels
165     for (int l = 1; l < level; ++l)
166         stateStack->pop();
167 
168     currentHighlightState = stateStack->top();
169     stateStack->pop();
170 }
171 
exitAll()172 void SourceHighlighter::exitAll() {
173     currentHighlightState = mainHighlightState;
174     clearStateStack();
175 }
176 
clearStateStack()177 void SourceHighlighter::clearStateStack() {
178     while (!stateStack->empty())
179         stateStack->pop();
180 }
181 
format(const std::string & elem,const std::string & s)182 void SourceHighlighter::format(const std::string &elem, const std::string &s) {
183     if (suspended)
184         return;
185 
186     if (!s.size())
187         return;
188 
189     // the formatter is allowed to be null
190     if (formatterManager) {
191         if (!optimize) {
192             formatterManager->getFormatter(elem)->format(s, formatterParams);
193         } else {
194             // otherwise we optmize output generation: delay formatting a specific
195             // element until we deal with another element; this way strings that belong
196             // to the same element are formatted using only one tag: e.g.,
197             // <comment>/* mycomment */</comment>
198             // instead of
199             // <comment>/*</comment><comment>mycomment</comment><comment>*/</comment>
200             if (elem != currentElement) {
201                 if (currentElement.size())
202                     flush();
203             }
204 
205             currentElement = elem;
206             currentElementBuffer << s;
207         }
208     }
209 }
210 
flush()211 void SourceHighlighter::flush() {
212     if (formatterManager) {
213 
214         // flush the buffer for the current element
215         formatterManager->getFormatter(currentElement)->format(
216                 currentElementBuffer.str(), formatterParams);
217 
218         // reset current element information
219         currentElement = "";
220         currentElementBuffer.str("");
221     }
222 }
223 
224 }
225