1 /*******************************************************************
2 
3 Part of the Fritzing project - http://fritzing.org
4 Copyright (c) 2007-2014 Fachhochschule Potsdam - http://fh-potsdam.de
5 
6 Fritzing is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10 
11 Fritzing is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with Fritzing.  If not, see <http://www.gnu.org/licenses/>.
18 
19 ********************************************************************
20 
21 $Revision: 6112 $:
22 $Author: cohen@irascible.com $:
23 $Date: 2012-06-28 00:18:10 +0200 (Do, 28. Jun 2012) $
24 
25 ********************************************************************/
26 
27 #include "syntaxer.h"
28 #include "../debugdialog.h"
29 #include "../utils/textutils.h"
30 
31 #include <QRegExp>
32 #include <QXmlStreamReader>
33 
34 QHash<QString, QString> Syntaxer::m_listsToFormats;
35 
Syntaxer()36 Syntaxer::Syntaxer() : QObject() {
37 	m_trieRoot = NULL;
38 }
39 
~Syntaxer()40 Syntaxer::~Syntaxer() {
41 	if (m_trieRoot) {
42 		delete m_trieRoot;
43 	}
44 }
45 
loadSyntax(const QString & filename)46 bool Syntaxer::loadSyntax(const QString &filename)
47  {
48 	QFile file(filename);
49 
50 	QString errorStr;
51 	int errorLine;
52 	int errorColumn;
53 
54 	QDomDocument domDocument;
55 	if (!domDocument.setContent(&file, true, &errorStr, &errorLine, &errorColumn)) {
56 		return false;
57 	}
58 
59 	QDomElement root = domDocument.documentElement();
60 	if (root.isNull()) return false;
61 	if (root.tagName() != "language") return false;
62 
63 	QDomElement highlighting = root.firstChildElement("highlighting");
64 	if (highlighting.isNull()) return false;
65 
66 	QDomElement general = root.firstChildElement("general");
67 	if (general.isNull()) return false;
68 
69 	QDomElement contexts = highlighting.firstChildElement("contexts");
70 	if (contexts.isNull()) return false;
71 
72 	m_hlCStringChar = false;
73 	QDomElement context = contexts.firstChildElement("context");
74 	while (!context.isNull()) {
75 		if (context.attribute("attribute").compare("Normal Text") == 0) {
76 			m_stringDelimiter = getStringDelimiter(context);
77 			initListsToFormats(context);
78 		}
79 		else if (context.attribute("attribute").compare("String") == 0) {
80 			QDomElement HlCStringChar = context.firstChildElement("HlCStringChar");
81 			if (!HlCStringChar.isNull()) {
82 				m_hlCStringChar = true;
83 			}
84 		}
85 		context = context.nextSiblingElement("context");
86 	}
87 
88     m_canProgram = root.attribute("canProgram", "").compare("true", Qt::CaseInsensitive) == 0;
89 	m_name = root.attribute("name");
90 	QStringList extensions = root.attribute("extensions").split(";", QString::SkipEmptyParts);
91 	if (extensions.count() > 0) {
92 		m_extensionString = m_name + " " + QObject::tr("files") + " (";
93 		foreach (QString ext, extensions) {
94 			m_extensionString += ext + " ";
95 			int ix = ext.indexOf(".");
96 			if (ix > 0) {
97 				ext.remove(0, ix);
98 			}
99 			m_extensions.append(ext);
100 		}
101 		m_extensionString.chop(1);
102 		m_extensionString += ")";
103 	}
104 
105 	m_trieRoot = new TrieNode('\0');
106 
107 	QDomElement list = highlighting.firstChildElement("list");
108 	while (!list.isNull()) {
109 		loadList(list);
110 		list = list.nextSiblingElement("list");
111 	}
112 
113 	QDomElement comments = general.firstChildElement("comments");
114 	if (!comments.isNull()) {
115 		Qt::CaseSensitivity caseSensitivity = comments.attribute("casesensitive").compare("1") == 0 ? Qt::CaseSensitive : Qt::CaseInsensitive;
116 		QDomElement comment = comments.firstChildElement("comment");
117 		while (!comment.isNull()) {
118 			CommentInfo * commentInfo = new CommentInfo(comment.attribute("start"), comment.attribute("end"), caseSensitivity);
119 			commentInfo->m_index = m_commentInfo.count();
120 			m_commentInfo.append(commentInfo);
121 			comment = comment.nextSiblingElement("comment");
122 		}
123 	}
124 
125 	return true;
126 }
127 
parseForName(const QString & filename)128 QString Syntaxer::parseForName(const QString & filename)
129 {
130 	QFile file(filename);
131 	file.open(QFile::ReadOnly);
132 	QXmlStreamReader xml(&file);
133     xml.setNamespaceProcessing(false);
134 
135 	while (!xml.atEnd()) {
136         switch (xml.readNext()) {
137 			case QXmlStreamReader::StartElement:
138 				if (xml.name().toString().compare("language") == 0) {
139 					return xml.attributes().value("name").toString();
140 				}
141 				break;
142 			default:
143 				break;
144 		}
145 	}
146 
147 	return "";
148 }
149 
loadList(QDomElement & list)150 void Syntaxer::loadList(QDomElement & list) {
151 	QString name = list.attribute("name");
152 	SyntaxerTrieLeaf * stf = new SyntaxerTrieLeaf(name);
153 	QDomElement item = list.firstChildElement("item");
154 	while (!item.isNull()) {
155 		QString text;
156 		if (TextUtils::findText(item, text)) {
157             QString s = text.trimmed();
158             m_trieRoot->addString(s, false, stf);
159 		}
160 		item = item.nextSiblingElement("item");
161 	}
162 }
163 
matches(const QString & string,TrieLeaf * & leaf)164 bool Syntaxer::matches(const QString & string, TrieLeaf * & leaf) {
165 	if (m_trieRoot == NULL) return false;
166 
167 	QString temp = string;
168 	return m_trieRoot->matches(temp, leaf);
169 }
170 
getCommentInfo(int ix)171 const CommentInfo * Syntaxer::getCommentInfo(int ix) {
172 	return m_commentInfo.at(ix);
173 }
174 
matchCommentStart(const QString & text,int offset,int & result,const CommentInfo * & resultCommentInfo)175 bool Syntaxer::matchCommentStart(const QString & text, int offset, int & result, const CommentInfo * & resultCommentInfo) {
176 	result = -1;
177 	foreach (CommentInfo * commentInfo, m_commentInfo) {
178 		int si = text.indexOf(commentInfo->m_start, offset, commentInfo->m_caseSensitive);
179 		if (si >= 0 && (result < 0 || si < result)) {
180 			result = si;
181 			resultCommentInfo = commentInfo;
182 		}
183 	}
184 
185 	return (result >= offset);
186 }
187 
matchStringStart(const QString & text,int offset)188 int Syntaxer::matchStringStart(const QString & text, int offset) {
189 	if (m_stringDelimiter.isNull()) return -1;
190 
191 	return text.indexOf(m_stringDelimiter, offset);
192 }
193 
matchStringEnd(const QString & text,int offset)194 int Syntaxer::matchStringEnd(const QString & text, int offset) {
195 	return matchStringStart(text, offset);
196 }
197 
extensions()198 const QStringList & Syntaxer::extensions() {
199 	return m_extensions;
200 }
201 
extensionString()202 const QString & Syntaxer::extensionString() {
203 	return m_extensionString;
204 }
205 
getStringDelimiter(QDomElement & context)206 QChar Syntaxer::getStringDelimiter(QDomElement & context) {
207 	QDomElement detectChar = context.firstChildElement("DetectChar");
208 	while (!detectChar.isNull()) {
209 		if (detectChar.attribute("attribute").compare("String") == 0) {
210 			QString c = detectChar.attribute("char");
211 			if (c.length() > 0) {
212 				return c.at(0);
213 			}
214 			return QChar();
215 		}
216 		detectChar = detectChar.nextSiblingElement("DetectChar");
217 	}
218 
219 	return QChar();
220 }
221 
initListsToFormats(QDomElement & context)222 void Syntaxer::initListsToFormats(QDomElement & context) {
223 	QDomElement keyword = context.firstChildElement("keyword");
224 	while (!keyword.isNull()) {
225 		QString format = keyword.attribute("attribute");
226 		QString list = keyword.attribute("String");
227 		if (!format.isEmpty() && !list.isEmpty()) {
228 			m_listsToFormats.insert(list, format);
229 		}
230 		keyword = keyword.nextSiblingElement("keyword");
231 	}
232 }
233 
formatFromList(const QString & list)234 QString Syntaxer::formatFromList(const QString & list) {
235 	return m_listsToFormats.value(list, ___emptyString___);
236 }
237 
hlCStringChar()238 bool Syntaxer::hlCStringChar() {
239 	return m_hlCStringChar;
240 }
241 
242 
canProgram()243 bool Syntaxer::canProgram() {
244     return m_canProgram;
245 }
246 
247 
248 //////////////////////////////////////////////
249 
SyntaxerTrieLeaf(QString name)250 SyntaxerTrieLeaf::SyntaxerTrieLeaf(QString name) {
251 	m_name = name;
252 }
253 
~SyntaxerTrieLeaf()254 SyntaxerTrieLeaf::~SyntaxerTrieLeaf()
255 {
256 }
257 
name()258 const QString & SyntaxerTrieLeaf::name()
259 {
260 	return m_name;
261 }
262 
263 //////////////////////////////////////////////
264 
CommentInfo(const QString & start,const QString & end,Qt::CaseSensitivity caseSensitive)265 CommentInfo::CommentInfo(const QString & start, const QString & end, Qt::CaseSensitivity caseSensitive) {
266 	m_start = start;
267 	m_end = end;
268 	m_multiLine = !end.isEmpty();
269 	m_caseSensitive = caseSensitive;
270 }
271