overte-thingvellir/libraries/octree/src/OctreeEntitiesFileParser.cpp

353 lines
10 KiB
C++

//
// OctreeEntititesFileParser.cpp
// libraries/octree/src
//
// Created by Simon Walton on Oct 15, 2018.
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "OctreeEntitiesFileParser.h"
#include <sstream>
#include <cctype>
#include <QUuid>
#include <QJsonDocument>
#include <QJsonObject>
using std::string;
std::string OctreeEntitiesFileParser::getErrorString() const {
std::ostringstream err;
if (_errorString.size() != 0) {
err << "Error: Line " << _line << ", byte position " << _position << ": " << _errorString;
};
return err.str();
}
void OctreeEntitiesFileParser::setEntitiesString(const QByteArray& entitiesContents) {
_entitiesContents = entitiesContents;
_entitiesLength = _entitiesContents.length();
_position = 0;
_line = 1;
}
bool OctreeEntitiesFileParser::parseEntities(QVariantMap& parsedEntities) {
if (nextToken() != '{') {
_errorString = "Text before start of object";
return false;
}
bool gotDataVersion = false;
bool gotEntities = false;
bool gotId = false;
bool gotVersion = false;
int token = nextToken();
while (true) {
if (token == '}') {
break;
}
else if (token != '"') {
_errorString = "Incorrect key string";
return false;
}
string key = readString();
if (key.size() == 0) {
_errorString = "Missing object key";
return false;
}
if (nextToken() != ':') {
_errorString = "Ill-formed id/value entry";
return false;
}
if (key == "DataVersion") {
if (gotDataVersion) {
_errorString = "Duplicate DataVersion entries";
return false;
}
int dataVersionValue = readInteger();
parsedEntities["DataVersion"] = dataVersionValue;
gotDataVersion = true;
} else if (key == "Entities") {
if (gotEntities) {
_errorString = "Duplicate Entities entries";
return false;
}
QVariantList entitiesValue;
if (!readEntitiesArray(entitiesValue)) {
return false;
}
parsedEntities["Entities"] = std::move(entitiesValue);
gotEntities = true;
} else if (key == "Id") {
if (gotId) {
_errorString = "Duplicate Id entries";
return false;
}
gotId = true;
if (nextToken() != '"') {
_errorString = "Invalid Id value";
return false;
};
string idString = readString();
if (idString.size() == 0) {
_errorString = "Invalid Id string";
return false;
}
// some older archives may have a null string id, so
// return success without setting parsedEntities,
// which will result in a new uuid for the restored
// archive. (not parsing and using isNull as parsing
// results in null if there is a corrupt string)
if (idString != "{00000000-0000-0000-0000-000000000000}") {
QUuid idValue = QUuid::fromString(QLatin1String(idString.c_str()) );
if (idValue.isNull()) {
_errorString = "Id value invalid UUID string: " + idString;
return false;
}
parsedEntities["Id"] = idValue;
}
} else if (key == "Version") {
if (gotVersion) {
_errorString = "Duplicate Version entries";
return false;
}
int versionValue = readInteger();
parsedEntities["Version"] = versionValue;
gotVersion = true;
} else if (key == "Paths") {
// Serverless JSON has optional Paths entry.
if (nextToken() != '{') {
_errorString = "Paths item is not an object";
return false;
}
int matchingBrace = findMatchingBrace();
if (matchingBrace < 0) {
_errorString = "Unterminated entity object";
return false;
}
QByteArray jsonObject = _entitiesContents.mid(_position - 1, matchingBrace - _position + 1);
QJsonDocument pathsObject = QJsonDocument::fromJson(jsonObject);
if (pathsObject.isNull()) {
_errorString = "Ill-formed paths entry";
return false;
}
parsedEntities["Paths"] = pathsObject.object();
_position = matchingBrace;
} else {
_errorString = "Unrecognized key name: " + key;
return false;
}
token = nextToken();
if (token == ',') {
token = nextToken();
}
}
if (nextToken() != -1) {
_errorString = "Ill-formed end of object";
return false;
}
return true;
}
int OctreeEntitiesFileParser::nextToken() {
while (_position < _entitiesLength) {
char c = _entitiesContents[_position++];
if (c != ' ' && c != '\t' && c != '\n' && c != '\r') {
return c;
}
if (c == '\n') {
++_line;
}
}
return -1;
}
string OctreeEntitiesFileParser::readString() {
string returnString;
while (_position < _entitiesLength) {
char c = _entitiesContents[_position++];
if (c == '"') {
break;
} else {
returnString.push_back(c);
}
}
return returnString;
}
int OctreeEntitiesFileParser::readInteger() {
const char* currentPosition = _entitiesContents.constData() + _position;
int i = std::atoi(currentPosition);
int token;
do {
token = nextToken();
} while (token == '-' || token == '+' || std::isdigit(token));
--_position;
return i;
}
bool OctreeEntitiesFileParser::readEntitiesArray(QVariantList& entitiesArray) {
if (nextToken() != '[') {
_errorString = "Entities entry is not an array";
return false;
}
while (true) {
if (nextToken() != '{') {
_errorString = "Entity array item is not an object";
return false;
}
int matchingBrace = findMatchingBrace();
if (matchingBrace < 0) {
_errorString = "Unterminated entity object";
return false;
}
QByteArray jsonEntity = _entitiesContents.mid(_position - 1, matchingBrace - _position + 1);
QJsonDocument entity = QJsonDocument::fromJson(jsonEntity);
if (entity.isNull()) {
_errorString = "Ill-formed entity";
return false;
}
QJsonObject entityObject = entity.object();
// resolve urls starting with ./ or ../
if (relativeURL.isEmpty() == false) {
bool isDirty = false;
const QStringList urlKeys {
// model
"modelURL",
"animation.url",
// image
"imageURL",
// web
"sourceUrl",
"scriptURL",
// zone
"ambientLight.ambientURL",
"skybox.url",
// particles
"textures",
// materials
"materialURL",
// ...shared
"href",
"script",
"serverScripts",
"collisionSoundURL",
"compoundShapeURL",
// TODO: deal with materialData and userData
};
for (const QString& key : urlKeys) {
if (key.contains('.')) {
// url is inside another object
const QStringList keyPair = key.split('.');
const QString entityKey = keyPair[0];
const QString childKey = keyPair[1];
if (entityObject.contains(entityKey) && entityObject[entityKey].isObject()) {
QJsonObject childObject = entityObject[entityKey].toObject();
if (childObject.contains(childKey) && childObject[childKey].isString()) {
const QString url = childObject[childKey].toString();
if (url.startsWith("./") || url.startsWith("../")) {
childObject[childKey] = relativeURL.resolved(url).toString();
entityObject[entityKey] = childObject;
isDirty = true;
}
}
}
} else {
if (entityObject.contains(key) && entityObject[key].isString()) {
const QString url = entityObject[key].toString();
if (url.startsWith("./") || url.startsWith("../")) {
entityObject[key] = relativeURL.resolved(url).toString();
isDirty = true;
}
}
}
}
if (isDirty) {
entity.setObject(entityObject);
}
}
entitiesArray.append(entityObject);
_position = matchingBrace;
char c = nextToken();
if (c == ']') {
return true;
} else if (c != ',') {
_errorString = "Entity array item incorrectly terminated";
return false;
}
}
return true;
}
int OctreeEntitiesFileParser::findMatchingBrace() const {
int index = _position;
int nestCount = 1;
while (index < _entitiesLength && nestCount != 0) {
switch (_entitiesContents[index++]) {
case '{':
++nestCount;
break;
case '}':
--nestCount;
break;
case '"':
// Skip string
while (index < _entitiesLength) {
if (_entitiesContents[index] == '"') {
++index;
break;
} else if (_entitiesContents[index] == '\\' && _entitiesContents[++index] == 'u') {
index += 4;
}
++index;
}
break;
default:
break;
}
}
return nestCount == 0 ? index : -1;
}