WoW Model Viewer
Your premiere tool for viewing, equipping and animating World of Warcraft models.
Loading...
Searching...
No Matches
DBDFile.cpp
Go to the documentation of this file.
1#include "DBDFile.h"
2
3#include <algorithm>
4#include <filesystem>
5#include <sstream>
6
7#include "Logger.h"
8#include "string_utils.h"
9
10// --- DBDBuild ---
11
13{
14 DBDBuild b;
15 auto parts = core::split(s, '.');
16 if (parts.size() >= 1) b.major = std::stoi(parts[0]);
17 if (parts.size() >= 2) b.minor = std::stoi(parts[1]);
18 if (parts.size() >= 3) b.patch = std::stoi(parts[2]);
19 if (parts.size() >= 4) b.build = std::stoi(parts[3]);
20 return b;
21}
22
24{
25 if (major != o.major) return major < o.major;
26 if (minor != o.minor) return minor < o.minor;
27 if (patch != o.patch) return patch < o.patch;
28 return build < o.build;
29}
30
31bool core::DBDBuild::operator<=(const DBDBuild& o) const { return !(o < *this); }
33{
34 return major == o.major && minor == o.minor && patch == o.patch && build == o.build;
35}
36bool core::DBDBuild::operator>=(const DBDBuild& o) const { return !(*this < o); }
37
38// --- DBDVersionDef ---
39
41{
42 for (const auto& b : builds)
43 {
44 if (b == target)
45 return true;
46 }
47 for (const auto& [lo, hi] : buildRanges)
48 {
49 if (target >= lo && target <= hi)
50 return true;
51 }
52 return false;
53}
54
55bool core::DBDVersionDef::matchesLayoutHash(const std::string& layoutHash) const
56{
57 if (layoutHash.empty())
58 return false;
59
60 for (const auto& h : layoutHashes)
61 {
62 if (h == layoutHash)
63 return true;
64 }
65 return false;
66}
67
68// --- DBDFile ---
69
70static std::string trimWhitespace(const std::string& s)
71{
72 const auto start = s.find_first_not_of(" \t\r\n");
73 if (start == std::string::npos) return "";
74 const auto end = s.find_last_not_of(" \t\r\n");
75 return s.substr(start, end - start + 1);
76}
77
78bool core::DBDFile::parse(const std::string& filepath)
79{
80 std::ifstream file(filepath);
81 if (!file.is_open())
82 {
83 LOG_ERROR << "Failed to open DBD file:" << filepath;
84 return false;
85 }
86
87 // Extract table name from filename (e.g. "Map.dbd" -> "Map")
88 std::filesystem::path p(filepath);
89 m_tableName = p.stem().string();
90
91 return parse(file);
92}
93
94bool core::DBDFile::parse(std::istream& stream)
95{
96 m_columns.clear();
97 m_versions.clear();
98
99 std::string line;
100
101 // Skip to COLUMNS section
102 while (std::getline(stream, line))
103 {
104 line = trimWhitespace(line);
105 if (line == "COLUMNS")
106 break;
107 }
108
109 // Parse column definitions
110 std::string nextLine;
111 if (!parseColumns(stream, nextLine))
112 return false;
113
114 // Parse version definitions
115 // nextLine might already contain the first BUILD or LAYOUT line
116 if (!nextLine.empty())
117 {
118 std::string afterBlock;
119 if (!parseVersionDef(stream, nextLine, afterBlock))
120 return false;
121
122 while (!afterBlock.empty())
123 {
124 std::string next;
125 if (!parseVersionDef(stream, afterBlock, next))
126 return false;
127 afterBlock = next;
128 }
129 }
130
131 // Continue reading remaining version definitions
132 while (std::getline(stream, line))
133 {
134 line = trimWhitespace(line);
135 if (line.empty())
136 continue;
137
138 std::string next;
139 if (!parseVersionDef(stream, line, next))
140 return false;
141
142 while (!next.empty())
143 {
144 std::string afterBlock;
145 if (!parseVersionDef(stream, next, afterBlock))
146 return false;
147 next = afterBlock;
148 }
149 }
150
151 return true;
152}
153
154bool core::DBDFile::parseColumns(std::istream& stream, std::string& nextLine)
155{
156 nextLine.clear();
157 std::string line;
158
159 while (std::getline(stream, line))
160 {
161 line = trimWhitespace(line);
162
163 // Empty line marks end of COLUMNS section
164 if (line.empty())
165 continue;
166
167 // If we hit a BUILD or LAYOUT line, we're past the columns section
168 if (line.starts_with("BUILD") || line.starts_with("LAYOUT"))
169 {
170 nextLine = line;
171 return true;
172 }
173
174 // Strip comments
175 std::string comment;
176 auto commentPos = line.find("//");
177 if (commentPos != std::string::npos)
178 {
179 comment = trimWhitespace(line.substr(commentPos + 2));
180 line = trimWhitespace(line.substr(0, commentPos));
181 }
182
183 if (line.empty())
184 continue;
185
186 DBDColumnDef col;
187
188 // Check for foreign key: type<ForeignDB::ForeignCol> ColName
189 auto angleBracketStart = line.find('<');
190 auto angleBracketEnd = line.find('>');
191
192 if (angleBracketStart != std::string::npos && angleBracketEnd != std::string::npos
193 && angleBracketEnd > angleBracketStart)
194 {
195 col.type = trimWhitespace(line.substr(0, angleBracketStart));
196 std::string foreignRef = line.substr(angleBracketStart + 1, angleBracketEnd - angleBracketStart - 1);
197
198 auto colonPos = foreignRef.find("::");
199 if (colonPos != std::string::npos)
200 {
201 col.foreignDb = foreignRef.substr(0, colonPos);
202 col.foreignCol = foreignRef.substr(colonPos + 2);
203 }
204
205 std::string remainder = trimWhitespace(line.substr(angleBracketEnd + 1));
206 col.name = remainder;
207 }
208 else
209 {
210 // Regular: type ColName
211 auto spacePos = line.find(' ');
212 if (spacePos == std::string::npos)
213 continue;
214
215 col.type = line.substr(0, spacePos);
216 col.name = trimWhitespace(line.substr(spacePos + 1));
217 }
218
219 // Check for unverified marker
220 if (!col.name.empty() && col.name.back() == '?')
221 {
222 col.verified = false;
223 col.name.pop_back();
224 }
225
226 m_columns.push_back(col);
227 }
228
229 return true;
230}
231
232bool core::DBDFile::parseVersionDef(std::istream& stream, const std::string& firstLine, std::string& nextLine)
233{
234 nextLine.clear();
235 DBDVersionDef verDef;
236
237 auto processHeaderLine = [&](const std::string& line)
238 {
239 if (line.starts_with("LAYOUT "))
240 {
241 std::string hashList = trimWhitespace(line.substr(7));
242 auto hashes = core::split(hashList, ',');
243 for (auto& h : hashes)
244 verDef.layoutHashes.push_back(trimWhitespace(h));
245 }
246 else if (line.starts_with("BUILD "))
247 {
248 std::string buildStr = trimWhitespace(line.substr(6));
249
250 // Split by comma first, then check each token for range or exact build.
251 // This correctly handles mixed lines like:
252 // BUILD 0.5.3.3368-0.5.5.3494, 0.6.0.3592
253 auto buildList = core::split(buildStr, ',');
254 for (auto& token : buildList)
255 {
256 token = trimWhitespace(token);
257 if (token.empty())
258 continue;
259
260 auto dashPos = token.find('-');
261 if (dashPos != std::string::npos)
262 {
263 auto lo = DBDBuild::fromString(token.substr(0, dashPos));
264 auto hi = DBDBuild::fromString(token.substr(dashPos + 1));
265 verDef.buildRanges.emplace_back(lo, hi);
266 }
267 else
268 {
269 verDef.builds.push_back(DBDBuild::fromString(token));
270 }
271 }
272 }
273 else if (line.starts_with("COMMENT "))
274 {
275 verDef.comment = trimWhitespace(line.substr(8));
276 }
277 };
278
279 // Process the first line
280 processHeaderLine(firstLine);
281
282 std::string line;
283 bool inHeader = true;
284
285 while (std::getline(stream, line))
286 {
287 line = trimWhitespace(line);
288
289 // Empty line indicates end of this version block
290 if (line.empty())
291 {
292 if (!verDef.fields.empty() || !verDef.builds.empty() || !verDef.buildRanges.empty())
293 break;
294 continue;
295 }
296
297 // Check if this is a header line (BUILD, LAYOUT, COMMENT)
298 if (inHeader && (line.starts_with("BUILD ") || line.starts_with("LAYOUT ") || line.starts_with("COMMENT ")))
299 {
300 processHeaderLine(line);
301 continue;
302 }
303
304 inHeader = false;
305
306 // This must be a field definition line
307 DBDVersionField field;
308
309 // Strip comments
310 auto commentPos = line.find("//");
311 if (commentPos != std::string::npos)
312 {
313 field.comment = trimWhitespace(line.substr(commentPos + 2));
314 line = trimWhitespace(line.substr(0, commentPos));
315 }
316
317 if (line.empty())
318 continue;
319
320 // Parse annotations: $annotation1,annotation2$
321 if (line.starts_with("$"))
322 {
323 auto endAnnotation = line.find('$', 1);
324 if (endAnnotation != std::string::npos)
325 {
326 std::string annotations = line.substr(1, endAnnotation - 1);
327 auto parts = core::split(annotations, ',');
328 for (const auto& p : parts)
329 {
330 std::string trimmed = trimWhitespace(p);
331 if (trimmed == "id")
332 field.isID = true;
333 else if (trimmed == "relation")
334 field.isRelation = true;
335 else if (trimmed == "noninline")
336 field.isNonInline = true;
337 }
338 line = trimWhitespace(line.substr(endAnnotation + 1));
339 }
340 }
341
342 // Parse name, size, and array length
343 // Possible formats: ColName, ColName<32>, ColName[5], ColName<32>[5]
344 std::string fieldStr = line;
345
346 // Extract array size [N]
347 auto bracketStart = fieldStr.find('[');
348 auto bracketEnd = fieldStr.find(']');
349 if (bracketStart != std::string::npos && bracketEnd != std::string::npos)
350 {
351 std::string arraySizeStr = fieldStr.substr(bracketStart + 1, bracketEnd - bracketStart - 1);
352 field.arraySize = static_cast<unsigned int>(std::stoul(arraySizeStr));
353 fieldStr = fieldStr.substr(0, bracketStart);
354 }
355
356 // Extract size <N>
357 auto angleStart = fieldStr.find('<');
358 auto angleEnd = fieldStr.find('>');
359 if (angleStart != std::string::npos && angleEnd != std::string::npos)
360 {
361 field.sizeStr = fieldStr.substr(angleStart + 1, angleEnd - angleStart - 1);
362 fieldStr = fieldStr.substr(0, angleStart);
363 }
364
365 field.name = trimWhitespace(fieldStr);
366 verDef.fields.push_back(field);
367 }
368
369 if (!verDef.fields.empty() || !verDef.builds.empty() || !verDef.buildRanges.empty())
370 m_versions.push_back(verDef);
371
372 return true;
373}
374
376{
377 for (const auto& v : m_versions)
378 {
379 if (v.matchesBuild(build))
380 return &v;
381 }
382 return nullptr;
383}
384
385const core::DBDVersionDef* core::DBDFile::findVersion(const DBDBuild& build, const std::string& layoutHash) const
386{
387 // Check layout hash first (most reliable match)
388 if (!layoutHash.empty())
389 {
390 for (const auto& v : m_versions)
391 {
392 if (v.matchesLayoutHash(layoutHash))
393 return &v;
394 }
395 }
396
397 // Fall back to build matching
398 return findVersion(build);
399}
400
401const core::DBDColumnDef* core::DBDFile::findColumn(const std::string& name) const
402{
403 for (const auto& c : m_columns)
404 {
405 if (c.name == name)
406 return &c;
407 }
408 return nullptr;
409}
static std::string trimWhitespace(const std::string &s)
Definition DBDFile.cpp:70
#define LOG_ERROR
Definition Logger.h:11
bool parseVersionDef(std::istream &stream, const std::string &firstLine, std::string &nextLine)
Definition DBDFile.cpp:232
bool parse(const std::string &filepath)
Definition DBDFile.cpp:78
const DBDVersionDef * findVersion(const DBDBuild &build) const
Definition DBDFile.cpp:375
const DBDColumnDef * findColumn(const std::string &name) const
Definition DBDFile.cpp:401
bool parseColumns(std::istream &stream, std::string &nextLine)
Definition DBDFile.cpp:154
std::vector< std::string > split(const std::string &s, char delimiter)
Split a string by a single-character delimiter.
bool operator>=(const DBDBuild &o) const
Definition DBDFile.cpp:36
bool operator<=(const DBDBuild &o) const
Definition DBDFile.cpp:31
bool operator==(const DBDBuild &o) const
Definition DBDFile.cpp:32
bool operator<(const DBDBuild &o) const
Definition DBDFile.cpp:23
static DBDBuild fromString(const std::string &s)
Definition DBDFile.cpp:12
std::string foreignDb
Definition DBDFile.h:31
std::string type
Definition DBDFile.h:29
std::string foreignCol
Definition DBDFile.h:32
std::string name
Definition DBDFile.h:30
bool matchesLayoutHash(const std::string &layoutHash) const
Definition DBDFile.cpp:55
std::vector< DBDVersionField > fields
Definition DBDFile.h:55
bool matchesBuild(const DBDBuild &target) const
Definition DBDFile.cpp:40
std::vector< DBDBuild > builds
Definition DBDFile.h:52
std::string comment
Definition DBDFile.h:54
std::vector< std::string > layoutHashes
Definition DBDFile.h:51
std::vector< std::pair< DBDBuild, DBDBuild > > buildRanges
Definition DBDFile.h:53
std::string comment
Definition DBDFile.h:45
std::string name
Definition DBDFile.h:39
std::string sizeStr
Definition DBDFile.h:40
unsigned int arraySize
Definition DBDFile.h:41