src/parser.cpp

changeset 3
55a55a9ec2c2
child 4
68988ebc2a68
equal deleted inserted replaced
2:2bdc3ac5e77c 3:55a55a9ec2c2
1 /*
2 * LDForge: LDraw parts authoring CAD
3 * Copyright (C) 2013 - 2018 Teemu Piippo
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "model.h"
20 #include "parser.h"
21 #include "objecttypes/comment.h"
22 #include "objecttypes/conditionaledge.h"
23 #include "objecttypes/edge.h"
24 #include "objecttypes/errorline.h"
25 #include "objecttypes/modelobject.h"
26 #include "objecttypes/polygon.h"
27 #include "objecttypes/subfilereference.h"
28
29 /*
30 * Constructs an LDraw parser
31 */
32 Parser::Parser(QIODevice& device, QObject* parent) :
33 QObject {parent},
34 device {device} {}
35
36 /*
37 * Reads a single line from the device.
38 */
39 QString Parser::readLine()
40 {
41 return QString::fromUtf8(this->device.readLine()).trimmed();
42 }
43
44 const QMap<QString, decltype(LDHeader::type)> Parser::typeStrings {
45 {"Part", LDHeader::Part},
46 {"Subpart", LDHeader::Subpart},
47 {"Shortcut", LDHeader::Shortcut},
48 {"Primitive", LDHeader::Primitive},
49 {"8_Primitive", LDHeader::Primitive_8},
50 {"48_Primitive", LDHeader::Primitive_48},
51 {"Configuration", LDHeader::Configuration},
52 };
53
54 /*
55 * Parses a single line of the header.
56 * Possible parse results:
57 * · ParseSuccess: the header line was parsed successfully.
58 * · ParseFailure: the header line was parsed incorrectly and needs to be handled otherwise.
59 * · StopParsing: the line does not belong in the header and header parsing needs to stop.
60 */
61 Parser::HeaderParseResult Parser::parseHeaderLine(
62 LDHeader& header,
63 Winding& winding,
64 const QString& line
65 ) {
66 if (line.isEmpty())
67 {
68 return ParseSuccess;
69 }
70 else if (not line.startsWith("0") or line.startsWith("0 //"))
71 {
72 return StopParsing;
73 }
74 else if (line.startsWith("0 !LDRAW_ORG "))
75 {
76 QStringList tokens = line
77 .mid(strlen("0 !LDRAW_ORG "))
78 .split(" ", QString::SkipEmptyParts);
79
80 if (not tokens.isEmpty())
81 {
82 QString partTypeString = tokens[0];
83 // Anything that enters LDForge becomes unofficial in any case if saved.
84 // Therefore we don't need to give the Unofficial type any special
85 // consideration.
86 if (partTypeString.startsWith("Unofficial_"))
87 partTypeString = partTypeString.mid(strlen("Unofficial_"));
88 header.type = Parser::typeStrings.value(partTypeString, LDHeader::Part);
89 header.qualfiers = 0;
90 if (tokens.contains("Alias"))
91 header.qualfiers |= LDHeader::Alias;
92 if (tokens.contains("Physical_Color"))
93 header.qualfiers |= LDHeader::Physical_Color;
94 if (tokens.contains("Flexible_Section"))
95 header.qualfiers |= LDHeader::Flexible_Section;
96 return ParseSuccess;
97 }
98 else
99 {
100 return ParseFailure;
101 }
102 }
103 else if (line == "0 BFC CERTIFY CCW")
104 {
105 winding = CounterClockwise;
106 return ParseSuccess;
107 }
108 else if (line == "0 BFC CERTIFY CW")
109 {
110 winding = Clockwise;
111 return ParseSuccess;
112 }
113 else if (line == "0 BFC NOCERTIFY")
114 {
115 winding = NoWinding;
116 return ParseSuccess;
117 }
118 else if (line.startsWith("0 !HISTORY "))
119 {
120 static const QRegExp historyRegexp {
121 R"(0 !HISTORY\s+(\d{4}-\d{2}-\d{2})\s+)"
122 R"((\{[^}]+|\[[^]]+)[\]}]\s+(.+))"
123 };
124 if (historyRegexp.exactMatch(line))
125 {
126 QString dateString = historyRegexp.capturedTexts().value(1);
127 QString authorWithPrefix = historyRegexp.capturedTexts().value(2);
128 QString description = historyRegexp.capturedTexts().value(3);
129 LDHeader::HistoryEntry historyEntry;
130 historyEntry.date = QDate::fromString(dateString, Qt::ISODate);
131 historyEntry.description = description;
132
133 if (authorWithPrefix[0] == '{')
134 historyEntry.author = authorWithPrefix + "}";
135 else
136 historyEntry.author = authorWithPrefix.mid(1);
137
138 header.history.append(historyEntry);
139 return ParseSuccess;
140 }
141 else
142 {
143 return ParseFailure;
144 }
145 }
146 else if (line.startsWith("0 Author: "))
147 {
148 header.author = line.mid(strlen("0 Author: "));
149 return ParseSuccess;
150 }
151 else if (line.startsWith("0 Name: "))
152 {
153 header.name = line.mid(strlen("0 Name: "));
154 return ParseSuccess;
155 }
156 else if (line.startsWith("0 !HELP "))
157 {
158 if (not header.help.isEmpty())
159 header.help += "\n";
160 header.help += line.mid(strlen("0 !HELP "));
161 return ParseSuccess;
162 }
163 else if (line.startsWith("0 !KEYWORDS "))
164 {
165 if (not header.keywords.isEmpty())
166 header.keywords += "\n";
167 header.keywords += line.mid(strlen("0 !KEYWORDS "));
168 return ParseSuccess;
169 }
170 else if (line.startsWith("0 !CATEGORY "))
171 {
172 header.category = line.mid(strlen("0 !CATEGORY "));
173 return ParseSuccess;
174 }
175 else if (line.startsWith("0 !CMDLINE "))
176 {
177 header.cmdline = line.mid(strlen("0 !CMDLINE "));
178 return ParseSuccess;
179 }
180 else if (line.startsWith("0 !LICENSE Redistributable under CCAL version 2.0"))
181 {
182 header.license = LDHeader::CaLicense;
183 return ParseSuccess;
184 }
185 else if (line.startsWith("0 !LICENSE Not redistributable"))
186 {
187 header.license = LDHeader::NonCaLicense;
188 return ParseSuccess;
189 }
190 else
191 {
192 return ParseFailure;
193 }
194 }
195
196 /*
197 * Parses the header from the device given at construction and returns
198 * the resulting header structure.
199 */
200 LDHeader Parser::parseHeader(Winding& winding)
201 {
202 LDHeader header = {};
203
204 if (not this->device.atEnd())
205 {
206 // Parse the description
207 QString descriptionLine = this->readLine();
208 if (descriptionLine.startsWith("0 "))
209 {
210 header.description = descriptionLine.mid(strlen("0 ")).trimmed();
211
212 // Parse the rest of the header
213 while (not this->device.atEnd())
214 {
215 const QString& line = this->readLine();
216 auto result = parseHeaderLine(header, winding, line);
217
218 if (result == ParseFailure)
219 {
220 // Failed to parse this header line, add it as a comment into the body later.
221 this->bag.append(line);
222 }
223 else if (result == StopParsing)
224 {
225 // Header parsing stops, add this line to the body.
226 this->bag.append(line);
227 break;
228 }
229 }
230 }
231 else
232 {
233 this->bag.append(descriptionLine);
234 }
235 }
236
237 return header;
238 }
239
240 /**
241 * @brief Parses the model body into the given model.
242 * @param editor Handle to model edit context
243 */
244 void Parser::parseBody(Model::EditContext& editor)
245 {
246 bool invertNext = false;
247 while (not this->device.atEnd())
248 this->bag.append(this->readLine());
249 for (const QString& line : this->bag)
250 {
251 if (line == "0 BFC INVERTNEXT" or line == "0 BFC CERTIFY INVERTNEXT")
252 {
253 invertNext = true;
254 continue;
255 }
256 modelobjects::BaseObject* object = parseFromString(editor, line);
257 if (invertNext)
258 {
259 editor.setObjectProperty(object, modelobjects::Property::IsInverted, true);
260 }
261 invertNext = false;
262 }
263 }
264
265 modelobjects::BaseObject* Parser::parseFromString(
266 Model::EditContext& editor,
267 const QString& line)
268 {
269 return editor.append<modelobjects::Comment>(line);
270 }

mercurial