1 #include "Macros.h" 2 #include "DaeValidator.h" 3 #include "PathUtil.h" 4 #include "Strings.h" 5 #include "StringUtil.h" 6 #include <cmath> 7 #include "no_warning_iomanip" 8 #include "no_warning_iostream" 9 #include <set> 10 #include "no_warning_sstream" 11 12 using namespace std; 13 14 namespace std 15 { 16 template<> 17 struct less<tuple<size_t, string>> 18 { operator ()std::less19 bool operator () (const tuple<size_t, string>& a, const tuple<size_t, string>& b) const 20 { 21 return get<1>(a) < get<1>(b); 22 } 23 }; 24 } 25 26 namespace opencollada 27 { DaeValidator(const list<string> & daePaths)28 DaeValidator::DaeValidator(const list<string> & daePaths) 29 { 30 mDaePaths.reserve(daePaths.size()); 31 mDaePaths.insert(mDaePaths.end(), daePaths.begin(), daePaths.end()); 32 } 33 34 #if IS_MSVC_AND_MSVC_VERSION_LT(1900) 35 typedef unsigned long long uint64_t; 36 #endif 37 static const vector<tuple<uint64_t, string>> table = 38 { 39 make_tuple(1, "B"), 40 make_tuple(1024, "kB"), 41 make_tuple(1048576, "MB"), 42 make_tuple(1073741824, "GB"), 43 make_tuple(1099511627776, "TB") 44 }; 45 46 class Size 47 { 48 public: Size(size_t size)49 Size(size_t size) 50 : mSize(size) 51 {} 52 str() const53 string str() const 54 { 55 stringstream s; 56 for (const auto & entry : table) 57 { 58 if (mSize < (get<0>(entry) * 1024)) 59 { 60 s << round(mSize / static_cast<double>(get<0>(entry))) << get<1>(entry); 61 break; 62 } 63 } 64 return s.str(); 65 } 66 67 private: 68 size_t mSize = 0; 69 }; 70 for_each_dae(const function<int (const Dae &)> & task) const71 int DaeValidator::for_each_dae(const function<int(const Dae &)> & task) const 72 { 73 int result = 0; 74 size_t count = 1; 75 for (const auto & daePath : mDaePaths) 76 { 77 if (mDaePaths.size() > 1) 78 { 79 cout << "[" << count << "/" << mDaePaths.size() << " " << static_cast<size_t>(static_cast<float>(count) / static_cast<float>(mDaePaths.size()) * 100.0f) << "%]" << endl; 80 ++count; 81 } 82 83 cout << "Processing " << daePath << " (" << Size(Path::GetFileSize(daePath)).str() << ")" << endl; 84 85 Dae dae; 86 dae.readFile(daePath); 87 if (dae) 88 { 89 result |= task(dae); 90 } 91 else 92 { 93 result |= 1; 94 cerr << "Error loading " << daePath << endl; 95 } 96 } 97 return result; 98 } 99 checkAll() const100 int DaeValidator::checkAll() const 101 { 102 return for_each_dae([&](const Dae & dae) { 103 return checkAll(dae); 104 }); 105 } 106 checkAll(const Dae & dae) const107 int DaeValidator::checkAll(const Dae & dae) const 108 { 109 return 110 checkSchema(dae) | 111 checkUniqueIds(dae) | 112 checkUniqueSids(dae) | 113 checkLinks(dae); 114 } 115 checkSchema(const string & schema_uri) const116 int DaeValidator::checkSchema(const string & schema_uri) const 117 { 118 if (schema_uri.empty()) 119 { 120 return for_each_dae([&](const Dae & dae) { 121 return checkSchema(dae); 122 }); 123 } 124 125 XmlSchema schema; 126 schema.readFile(schema_uri); 127 if (schema) 128 { 129 return for_each_dae([&](const Dae & dae) { 130 return ValidateAgainstSchema(dae, schema); 131 }); 132 } 133 134 cerr << "Error loading " << schema_uri << endl; 135 return 1; 136 } 137 checkSchema(const Dae & dae) const138 int DaeValidator::checkSchema(const Dae & dae) const 139 { 140 cout << "Checking schema..." << endl; 141 142 int result = 0; 143 144 Dae::Version version = dae.getVersion(); 145 if (version == Dae::Version::COLLADA14) 146 { 147 result |= ValidateAgainstSchema(dae, Dae::GetColladaSchema141()); 148 } 149 else if (version == Dae::Version::COLLADA15) 150 { 151 //result |= ValidateAgainstSchema(dae, Dae::GetColladaSchema15()); 152 cerr << "COLLADA 1.5 not supported yet." << endl; 153 return 1; 154 } 155 else 156 { 157 cerr << "Can't determine COLLADA version used by input file" << endl; 158 return 1; 159 } 160 161 vector<XmlNode> subDocs; 162 163 // Find xsi:schemaLocation attributes in dae and try to validate against specified xsd documents 164 const auto & elements = dae.root().selectNodes("//*[@xsi:schemaLocation]"); 165 for (const auto & element : elements) 166 { 167 if (auto schemaLocation = element.attribute("schemaLocation")) 168 { 169 vector<string> parts = String::Split(schemaLocation.value()); 170 // Parse pairs of namespace/xsd 171 for (size_t i = 1; i < parts.size(); i += 2) 172 { 173 const string & ns = parts[i - 1]; 174 const string & xsdUri = parts[i]; 175 176 if (ns != Dae::GetColladaNamespace141() && ns != Dae::GetColladaNamespace15()) 177 { 178 // "insert" does nothing if element already exists. 179 mSchemas.insert(pair<string, XmlSchema>(ns, XmlSchema())); 180 mSchemaLocations.insert(pair<string, string>(ns, xsdUri)); 181 } 182 } 183 } 184 } 185 186 // Preload uninitialized .xsd files 187 auto itSchema = mSchemas.begin(); 188 auto itSchemaLocation = mSchemaLocations.begin(); 189 for (; itSchema != mSchemas.end(); ++itSchema, ++itSchemaLocation) 190 { 191 const auto & schemaUri = itSchemaLocation->second; 192 auto & schema = itSchema->second; 193 194 // Don't try to load schemas that already failed in a previous run 195 if (schema.failedToLoad()) 196 { 197 cout << "Error loading " << schemaUri << endl; 198 result |= 1; 199 continue; 200 } 201 202 string uri = schemaUri; 203 204 if (!schema) 205 { 206 schema.readFile(uri); 207 } 208 209 if (!schema) 210 { 211 // Try to find schema document in executable directory 212 Uri xsdUri(schemaUri); 213 if (xsdUri.isValid()) 214 { 215 uri = Path::Join(Path::GetExecutableDirectory(), xsdUri.pathFile()); 216 schema.readFile(uri); 217 } 218 } 219 220 if (!schema) 221 { 222 // Try to find schema document in COLLADA document directory 223 Uri xsdUri(schemaUri); 224 string xsdFile = xsdUri.pathFile(); 225 xsdUri = dae.getURI(); 226 xsdUri.setPathFile(xsdFile); 227 uri = xsdUri.str(); 228 schema.readFile(uri); 229 } 230 231 if (schema && uri != schemaUri) 232 { 233 cout << "Using " << uri << endl; 234 } 235 else if (!schema) 236 { 237 cout << "Error loading " << schemaUri << endl; 238 result |= 1; 239 } 240 } 241 242 // Validate "sub documents" 243 for (const auto & schema : mSchemas) 244 { 245 // Ignore schemas that failed to load 246 if (!schema.second) 247 continue; 248 249 const auto & ns = schema.first; 250 stringstream xpath; 251 xpath << "//*[namespace-uri()='" << ns << "' and not(namespace-uri(./..)='" << ns << "')]"; 252 const auto & nodes = dae.root().selectNodes(xpath.str()); 253 for (auto node : nodes) 254 { 255 auto autoRestoreRoot = dae.setTempRoot(node); 256 result |= ValidateAgainstSchema(dae, schema.second); 257 } 258 } 259 260 return result; 261 } 262 checkUniqueIds() const263 int DaeValidator::checkUniqueIds() const 264 { 265 return for_each_dae([&](const Dae & dae) { 266 return checkUniqueIds(dae); 267 }); 268 } 269 checkUniqueIds(const Dae & dae) const270 int DaeValidator::checkUniqueIds(const Dae & dae) const 271 { 272 cout << "Checking unique ids..." << endl; 273 274 int result = 0; 275 map<string, size_t> ids; 276 const auto & nodes = dae.root().selectNodes("//*[@id]"); 277 for (const auto & node : nodes) 278 { 279 string id = node.attribute("id").value(); 280 size_t line = node.line(); 281 282 int checkEscapeCharResult = CheckEscapeChar(id); 283 if (checkEscapeCharResult != 0) 284 { 285 cerr << dae.getURI() << ":" << line << ": \"" << id << "\" contains non-escaped characters." << endl; 286 result |= checkEscapeCharResult; 287 } 288 289 auto it = ids.find(id); 290 if (it != ids.end()) 291 { 292 cerr << dae.getURI() << ":" << line << ": Duplicated id \"" << id << "\". See first declaration at line " << it->second << "." << endl; 293 result |= 1; 294 } 295 else 296 { 297 ids[id] = line; 298 } 299 } 300 return result; 301 } 302 checkUniqueSids() const303 int DaeValidator::checkUniqueSids() const 304 { 305 return for_each_dae([&](const Dae & dae) { 306 return checkUniqueSids(dae); 307 }); 308 } 309 checkUniqueSids(const Dae & dae) const310 int DaeValidator::checkUniqueSids(const Dae & dae) const 311 { 312 cout << "Checking unique sids..." << endl; 313 314 int result = 0; 315 const auto & parents = dae.root().selectNodes("//*[@sid]/.."); 316 for (auto parent : parents) 317 { 318 const auto & children = parent.selectNodes("*[@sid]"); 319 map<string, size_t> sids; 320 for (auto child : children) 321 { 322 string sid = child.attribute("sid").value(); 323 size_t line = child.line(); 324 325 auto it = sids.find(sid); 326 if (it != sids.end()) 327 { 328 cerr << dae.getURI() << ":" << line << ": Duplicated sid \"" << sid << "\". See first declaration at line " << it->second << "." << endl; 329 result |= 1; 330 } 331 else 332 { 333 sids[sid] = line; 334 } 335 } 336 } 337 return result; 338 } 339 checkLinks() const340 int DaeValidator::checkLinks() const 341 { 342 return for_each_dae([&](const Dae & dae) { 343 return checkLinks(dae); 344 }); 345 } 346 checkLinks(const Dae & dae) const347 int DaeValidator::checkLinks(const Dae & dae) const 348 { 349 cout << "Checking links..." << endl; 350 351 const auto & ids = dae.getIds(); 352 353 int result = 0; 354 for (const auto & t : dae.getAnyURIs()) 355 { 356 const auto & line = get<0>(t); 357 const auto & uri = get<1>(t); 358 if (!Path::Exists(uri.nativePath())) 359 { 360 cerr << dae.getURI() << ":" << line << ": Can't resolve " << uri << endl; 361 result |= 1; 362 } 363 else if (!uri.fragment().empty()) 364 { 365 Uri no_fragment_uri(uri); 366 no_fragment_uri.setFragment(""); 367 if (no_fragment_uri == dae.getURI()) 368 { 369 auto id = ids.find(uri.fragment()); 370 if (id == ids.end()) 371 { 372 cerr << dae.getURI() << ":" << line << ": Can't resolve #" << uri.fragment() << endl; 373 result |= 1; 374 } 375 } 376 else 377 { 378 auto it = dae.getExternalDAEs().find(no_fragment_uri); 379 if (it != dae.getExternalDAEs().end()) 380 { 381 if (it->second) 382 { 383 auto ext_ids = it->second.getIds(); 384 auto id = ext_ids.find(uri.fragment()); 385 if (id == ext_ids.end()) 386 { 387 cerr << dae.getURI() << ":" << line << ": Can't resolve " << uri << endl; 388 result |= 1; 389 } 390 } 391 else 392 { 393 cerr << dae.getURI() << ":" << line << ": " << uri << ": referenced file exists but has not been successfully loaded." << endl; 394 result |= 1; 395 } 396 } 397 } 398 } 399 } 400 401 // IDREF 402 for (const auto & IDREF : dae.getIDREFs()) 403 { 404 const auto & line = get<0>(IDREF); 405 const auto & idref = get<1>(IDREF); 406 407 auto id = ids.find(idref); 408 if (id == ids.end()) 409 { 410 cerr << dae.getURI() << ":" << line << ": Can't resolve #" << idref << endl; 411 result |= 1; 412 } 413 } 414 415 return result; 416 } 417 ValidateAgainstSchema(const Dae & dae,const XmlSchema & schema)418 int DaeValidator::ValidateAgainstSchema(const Dae & dae, const XmlSchema & schema) 419 { 420 return schema.validate(dae) ? 0 : 1; 421 } 422 CheckEscapeChar(const std::string & s)423 int DaeValidator::CheckEscapeChar(const std::string & s) 424 { 425 if (s.find_first_of(" #$%&/:;<=>?@[\\:]^`{|}~") != string::npos) 426 return 1; 427 return 0; 428 } 429 } 430