src/parser.cpp

changeset 176
cd9d6bf6f649
parent 152
03f8e6d42e13
child 183
97b591813c8b
equal deleted inserted replaced
174:3016b494685c 176:cd9d6bf6f649
16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */ 17 */
18 18
19 #include "model.h" 19 #include "model.h"
20 #include "parser.h" 20 #include "parser.h"
21 #include "linetypes/comment.h"
22 #include "linetypes/conditionaledge.h" 21 #include "linetypes/conditionaledge.h"
23 #include "linetypes/edge.h" 22 #include "linetypes/edge.h"
24 #include "linetypes/errorline.h" 23 #include "linetypes/errorline.h"
25 #include "linetypes/metacommand.h" 24 #include "linetypes/metacommand.h"
26 #include "linetypes/object.h" 25 #include "linetypes/object.h"
46 QString Parser::readLine() 45 QString Parser::readLine()
47 { 46 {
48 return QString::fromUtf8(this->device.readLine()).trimmed(); 47 return QString::fromUtf8(this->device.readLine()).trimmed();
49 } 48 }
50 49
51 /*
52 * Parses a single line of the header.
53 * Possible parse results:
54 * · ParseSuccess: the header line was parsed successfully.
55 * · ParseFailure: the header line was parsed incorrectly and needs to be handled otherwise.
56 * · StopParsing: the line does not belong in the header and header parsing needs to stop.
57 */
58 Parser::HeaderParseResult Parser::parseHeaderLine(
59 LDHeader& header,
60 Winding& winding,
61 const QString& line
62 ) {
63 if (line.isEmpty())
64 {
65 return ParseSuccess;
66 }
67 else if (not line.startsWith("0") or line.startsWith("0 //"))
68 {
69 return StopParsing;
70 }
71 else if (line.startsWith("0 !LDRAW_ORG "))
72 {
73 QStringList tokens = line
74 .mid(strlen("0 !LDRAW_ORG "))
75 .split(" ", Qt::SkipEmptyParts);
76 if (not tokens.isEmpty())
77 {
78 QString partTypeString = tokens[0];
79 // Anything that enters LDForge becomes unofficial in any case if saved.
80 // Therefore we don't need to give the Unofficial type any special
81 // consideration.
82 if (partTypeString.startsWith("Unofficial_"))
83 partTypeString = partTypeString.mid(strlen("Unofficial_"));
84 header.type = headerTypeFromString(partTypeString);
85 header.qualfiers = {};
86 if (tokens.contains("Alias"))
87 header.qualfiers |= LDHeader::Alias;
88 if (tokens.contains("Physical_Color"))
89 header.qualfiers |= LDHeader::PhysicalColour;
90 if (tokens.contains("Flexible_Section"))
91 header.qualfiers |= LDHeader::FlexibleSection;
92 return ParseSuccess;
93 }
94 else
95 {
96 return ParseFailure;
97 }
98 }
99 else if (line == "0 BFC CERTIFY CCW")
100 {
101 winding = Anticlockwise;
102 return ParseSuccess;
103 }
104 else if (line == "0 BFC CERTIFY CW")
105 {
106 winding = Clockwise;
107 return ParseSuccess;
108 }
109 else if (line == "0 BFC NOCERTIFY")
110 {
111 winding = NoWinding;
112 return ParseSuccess;
113 }
114 else if (line.startsWith("0 !HISTORY "))
115 {
116 static const QRegExp historyRegexp {
117 R"(0 !HISTORY\s+(\d{4}-\d{2}-\d{2})\s+)"
118 R"((\{[^}]+|\[[^]]+)[\]}]\s+(.+))"
119 };
120 if (historyRegexp.exactMatch(line))
121 {
122 QString dateString = historyRegexp.capturedTexts().value(1);
123 QString authorWithPrefix = historyRegexp.capturedTexts().value(2);
124 QString description = historyRegexp.capturedTexts().value(3);
125 LDHeader::HistoryEntry historyEntry;
126 historyEntry.date = QDate::fromString(dateString, Qt::ISODate);
127 historyEntry.description = description;
128
129 if (authorWithPrefix[0] == '{')
130 historyEntry.author = authorWithPrefix + "}";
131 else
132 historyEntry.author = authorWithPrefix.mid(1);
133
134 header.history.append(historyEntry);
135 return ParseSuccess;
136 }
137 else
138 {
139 return ParseFailure;
140 }
141 }
142 else if (line.startsWith("0 Author: "))
143 {
144 header.author = line.mid(strlen("0 Author: "));
145 return ParseSuccess;
146 }
147 else if (line.startsWith("0 Name: "))
148 {
149 header.name = line.mid(strlen("0 Name: "));
150 return ParseSuccess;
151 }
152 else if (line.startsWith("0 !HELP "))
153 {
154 if (not header.help.isEmpty())
155 header.help += "\n";
156 header.help += line.mid(strlen("0 !HELP "));
157 return ParseSuccess;
158 }
159 else if (line.startsWith("0 !KEYWORDS "))
160 {
161 if (not header.keywords.isEmpty())
162 header.keywords += "\n";
163 header.keywords += line.mid(strlen("0 !KEYWORDS "));
164 return ParseSuccess;
165 }
166 else if (line.startsWith("0 !CATEGORY "))
167 {
168 header.category = line.mid(strlen("0 !CATEGORY "));
169 return ParseSuccess;
170 }
171 else if (line.startsWith("0 !CMDLINE "))
172 {
173 header.cmdline = line.mid(strlen("0 !CMDLINE "));
174 return ParseSuccess;
175 }
176 else if (line.startsWith(LDHeader::caLicenseString))
177 {
178 header.license = LDHeader::CaLicense;
179 return ParseSuccess;
180 }
181 else if (line.startsWith(LDHeader::nonCaLicenseString))
182 {
183 header.license = LDHeader::NonCaLicense;
184 return ParseSuccess;
185 }
186 else
187 {
188 return ParseFailure;
189 }
190 }
191
192 /*
193 * Parses the header from the device given at construction and returns
194 * the resulting header structure.
195 */
196 LDHeader Parser::parseHeader(Winding& winding)
197 {
198 LDHeader header = {};
199 if (not this->device.atEnd())
200 {
201 // Parse the description
202 QString descriptionLine = this->readLine();
203 if (descriptionLine.startsWith("0 "))
204 {
205 header.description = descriptionLine.mid(strlen("0 ")).trimmed();
206 // Parse the rest of the header
207 while (not this->device.atEnd())
208 {
209 const QString& line = this->readLine();
210 auto result = parseHeaderLine(header, winding, line);
211 if (result == ParseFailure)
212 {
213 // Failed to parse this header line, add it as a comment into the body later.
214 this->bag.append(line);
215 }
216 else if (result == StopParsing)
217 {
218 // Header parsing stops, add this line to the body.
219 this->bag.append(line);
220 break;
221 }
222 }
223 }
224 else
225 {
226 this->bag.append(descriptionLine);
227 }
228 }
229 return header;
230 }
231
232 /** 50 /**
233 * @brief Parses the model body into the given model. 51 * @brief Parses the model body into the given model.
234 * @param editor Handle to model edit context 52 * @param editor Handle to model edit context
235 */ 53 */
236 void Parser::parseBody(Model& model) 54 void Parser::parseBody(Model& model)
237 { 55 {
238 bool invertNext = false; 56 bool invertNext = false;
239 while (not this->device.atEnd()) 57 while (not this->device.atEnd())
240 this->bag.append(this->readLine());
241 for (const QString& line : this->bag)
242 { 58 {
243 // Some LDraw parts such as 53588.dat can contain "BFC INVERTNEXT" with multiple inner whitespaces. 59 // Some LDraw parts such as 53588.dat can contain "BFC INVERTNEXT" with multiple inner whitespaces.
244 // So we need to pass the string through QString::simplified to catch these cases. 60 // So we need to pass the string through QString::simplified to catch these cases.
245 const QString simplifiedLine = line.simplified(); 61 const QString line = this->readLine().simplified();
246 if (simplifiedLine == "0 BFC INVERTNEXT" or simplifiedLine == "0 BFC CERTIFY INVERTNEXT") 62 if (line == "0 BFC INVERTNEXT" or line == "0 BFC CERTIFY INVERTNEXT")
247 { 63 {
248 invertNext = true; 64 invertNext = true;
249 continue; 65 continue;
250 } 66 }
251 std::unique_ptr<ldraw::Object> object = parseFromString(line); 67 model.append(parseFromString(line));
252 model.append(std::move(object));
253 if (invertNext) 68 if (invertNext)
254 { 69 {
255 model[model.size() - 1]->invert(); 70 model[model.size() - 1]->invert();
256 } 71 }
257 invertNext = false; 72 invertNext = false;
258 } 73 }
259 /*
260 // Test quadrilateral splitting by splitting all the quadrilaterals
261 QVector<ldraw::quadrilateralid_t> quadrilateral_ids;
262 for (int i = 0; i < editor.model().size(); i += 1)
263 {
264 const ldraw::id_t id = editor.model().resolve(editor.model().index(i));
265 const ldraw::quadrilateralid_t quad_id = editor.model().checkType<ldraw::Quadrilateral>(id);
266 if (not(quad_id == ldraw::NULL_ID))
267 {
268 quadrilateral_ids.push_back(quad_id);
269 }
270 }
271 for (const ldraw::quadrilateralid_t id : quadrilateral_ids)
272 {
273 ldraw::splitQuadrilateral(editor, id);
274 }
275 */
276 } 74 }
277 75
278 static ldraw::Color colorFromString(const QString& colorString) 76 static ldraw::Color colorFromString(const QString& colorString)
279 { 77 {
280 bool colorSucceeded; 78 bool colorSucceeded;
345 static std::unique_ptr<ldraw::Object> parseType0Line( 143 static std::unique_ptr<ldraw::Object> parseType0Line(
346 const QString& line, 144 const QString& line,
347 const QStringList& tokens) 145 const QStringList& tokens)
348 { 146 {
349 Q_UNUSED(tokens) 147 Q_UNUSED(tokens)
350 if (line.startsWith("0 //")) 148 return std::make_unique<ldraw::MetaCommand>(line.mid(1).trimmed());
351 {
352 // lol wut
353 return std::make_unique<ldraw::Comment>(line.mid(std::strlen("0 //")).trimmed());
354 }
355 else
356 {
357 return std::make_unique<ldraw::MetaCommand>(line.mid(1).trimmed());
358 }
359 } 149 }
360 150
361 static std::unique_ptr<ldraw::SubfileReference> parseType1Line( 151 static std::unique_ptr<ldraw::SubfileReference> parseType1Line(
362 const QString& line, 152 const QString& line,
363 const QStringList& tokens) 153 const QStringList& tokens)

mercurial