src/parser.cpp

changeset 3
55a55a9ec2c2
child 4
68988ebc2a68
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/parser.cpp	Sun Sep 22 11:51:41 2019 +0300
@@ -0,0 +1,270 @@
+/*
+ *  LDForge: LDraw parts authoring CAD
+ *  Copyright (C) 2013 - 2018 Teemu Piippo
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "model.h"
+#include "parser.h"
+#include "objecttypes/comment.h"
+#include "objecttypes/conditionaledge.h"
+#include "objecttypes/edge.h"
+#include "objecttypes/errorline.h"
+#include "objecttypes/modelobject.h"
+#include "objecttypes/polygon.h"
+#include "objecttypes/subfilereference.h"
+
+/*
+ * Constructs an LDraw parser
+ */
+Parser::Parser(QIODevice& device, QObject* parent) :
+	QObject {parent},
+	device {device} {}
+
+/*
+ * Reads a single line from the device.
+ */
+QString Parser::readLine()
+{
+	return QString::fromUtf8(this->device.readLine()).trimmed();
+}
+
+const QMap<QString, decltype(LDHeader::type)> Parser::typeStrings {
+	{"Part", LDHeader::Part},
+	{"Subpart", LDHeader::Subpart},
+	{"Shortcut", LDHeader::Shortcut},
+	{"Primitive", LDHeader::Primitive},
+	{"8_Primitive", LDHeader::Primitive_8},
+	{"48_Primitive", LDHeader::Primitive_48},
+	{"Configuration", LDHeader::Configuration},
+};
+
+/*
+ * Parses a single line of the header.
+ * Possible parse results:
+ *   · ParseSuccess: the header line was parsed successfully.
+ *   · ParseFailure: the header line was parsed incorrectly and needs to be handled otherwise.
+ *   · StopParsing: the line does not belong in the header and header parsing needs to stop.
+ */
+Parser::HeaderParseResult Parser::parseHeaderLine(
+	LDHeader& header,
+	Winding& winding,
+	const QString& line
+) {
+	if (line.isEmpty())
+	{
+		return ParseSuccess;
+	}
+	else if (not line.startsWith("0") or line.startsWith("0 //"))
+	{
+		return StopParsing;
+	}
+	else if (line.startsWith("0 !LDRAW_ORG "))
+	{
+		QStringList tokens = line
+			.mid(strlen("0 !LDRAW_ORG "))
+			.split(" ", QString::SkipEmptyParts);
+
+		if (not tokens.isEmpty())
+		{
+			QString partTypeString = tokens[0];
+			// Anything that enters LDForge becomes unofficial in any case if saved.
+			// Therefore we don't need to give the Unofficial type any special
+			// consideration.
+			if (partTypeString.startsWith("Unofficial_"))
+				partTypeString = partTypeString.mid(strlen("Unofficial_"));
+			header.type = Parser::typeStrings.value(partTypeString, LDHeader::Part);
+			header.qualfiers = 0;
+			if (tokens.contains("Alias"))
+				header.qualfiers |= LDHeader::Alias;
+			if (tokens.contains("Physical_Color"))
+				header.qualfiers |= LDHeader::Physical_Color;
+			if (tokens.contains("Flexible_Section"))
+				header.qualfiers |= LDHeader::Flexible_Section;
+			return ParseSuccess;
+		}
+		else
+		{
+			return ParseFailure;
+		}
+	}
+	else if (line == "0 BFC CERTIFY CCW")
+	{
+		winding = CounterClockwise;
+		return ParseSuccess;
+	}
+	else if (line == "0 BFC CERTIFY CW")
+	{
+		winding = Clockwise;
+		return ParseSuccess;
+	}
+	else if (line == "0 BFC NOCERTIFY")
+	{
+		winding = NoWinding;
+		return ParseSuccess;
+	}
+	else if (line.startsWith("0 !HISTORY "))
+	{
+		static const QRegExp historyRegexp {
+			R"(0 !HISTORY\s+(\d{4}-\d{2}-\d{2})\s+)"
+			R"((\{[^}]+|\[[^]]+)[\]}]\s+(.+))"
+		};
+		if (historyRegexp.exactMatch(line))
+		{
+			QString dateString = historyRegexp.capturedTexts().value(1);
+			QString authorWithPrefix = historyRegexp.capturedTexts().value(2);
+			QString description = historyRegexp.capturedTexts().value(3);
+			LDHeader::HistoryEntry historyEntry;
+			historyEntry.date = QDate::fromString(dateString, Qt::ISODate);
+			historyEntry.description = description;
+
+			if (authorWithPrefix[0] == '{')
+				historyEntry.author = authorWithPrefix + "}";
+			else
+				historyEntry.author = authorWithPrefix.mid(1);
+
+			header.history.append(historyEntry);
+			return ParseSuccess;
+		}
+		else
+		{
+			return ParseFailure;
+		}
+	}
+	else if (line.startsWith("0 Author: "))
+	{
+		header.author = line.mid(strlen("0 Author: "));
+		return ParseSuccess;
+	}
+	else if (line.startsWith("0 Name: "))
+	{
+		header.name = line.mid(strlen("0 Name: "));
+		return ParseSuccess;
+	}
+	else if (line.startsWith("0 !HELP "))
+	{
+		if (not header.help.isEmpty())
+			header.help += "\n";
+		header.help += line.mid(strlen("0 !HELP "));
+		return ParseSuccess;
+	}
+	else if (line.startsWith("0 !KEYWORDS "))
+	{
+		if (not header.keywords.isEmpty())
+			header.keywords += "\n";
+		header.keywords += line.mid(strlen("0 !KEYWORDS "));
+		return ParseSuccess;
+	}
+	else if (line.startsWith("0 !CATEGORY "))
+	{
+		header.category = line.mid(strlen("0 !CATEGORY "));
+		return ParseSuccess;
+	}
+	else if (line.startsWith("0 !CMDLINE "))
+	{
+		header.cmdline = line.mid(strlen("0 !CMDLINE "));
+		return ParseSuccess;
+	}
+	else if (line.startsWith("0 !LICENSE Redistributable under CCAL version 2.0"))
+	{
+		header.license = LDHeader::CaLicense;
+		return ParseSuccess;
+	}
+	else if (line.startsWith("0 !LICENSE Not redistributable"))
+	{
+		header.license = LDHeader::NonCaLicense;
+		return ParseSuccess;
+	}
+	else
+	{
+		return ParseFailure;
+	}
+}
+
+/*
+ * Parses the header from the device given at construction and returns
+ * the resulting header structure.
+ */
+LDHeader Parser::parseHeader(Winding& winding)
+{
+	LDHeader header = {};
+
+	if (not this->device.atEnd())
+	{
+		// Parse the description
+		QString descriptionLine = this->readLine();
+		if (descriptionLine.startsWith("0 "))
+		{
+			header.description = descriptionLine.mid(strlen("0 ")).trimmed();
+
+			// Parse the rest of the header
+			while (not this->device.atEnd())
+			{
+				const QString& line = this->readLine();
+				auto result = parseHeaderLine(header, winding, line);
+
+				if (result == ParseFailure)
+				{
+					// Failed to parse this header line, add it as a comment into the body later.
+					this->bag.append(line);
+				}
+				else if (result == StopParsing)
+				{
+					// Header parsing stops, add this line to the body.
+					this->bag.append(line);
+					break;
+				}
+			}
+		}
+		else
+		{
+			this->bag.append(descriptionLine);
+		}
+	}
+
+	return header;
+}
+
+/**
+ * @brief Parses the model body into the given model.
+ * @param editor Handle to model edit context
+ */
+void Parser::parseBody(Model::EditContext& editor)
+{
+	bool invertNext = false;
+	while (not this->device.atEnd())
+		this->bag.append(this->readLine());
+	for (const QString& line : this->bag)
+	{
+		if (line == "0 BFC INVERTNEXT" or line == "0 BFC CERTIFY INVERTNEXT")
+		{
+			invertNext = true;
+			continue;
+		}
+		modelobjects::BaseObject* object = parseFromString(editor, line);
+		if (invertNext)
+		{
+			editor.setObjectProperty(object, modelobjects::Property::IsInverted, true);
+		}
+		invertNext = false;
+	}
+}
+
+modelobjects::BaseObject* Parser::parseFromString(
+	Model::EditContext& editor,
+	const QString& line)
+{
+	return editor.append<modelobjects::Comment>(line);
+}

mercurial