forked from AcademySoftwareFoundation/MaterialX
-
Notifications
You must be signed in to change notification settings - Fork 0
/
XmlIo.cpp
353 lines (297 loc) · 11.1 KB
/
XmlIo.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
//
// TM & (c) 2017 Lucasfilm Entertainment Company Ltd. and Lucasfilm Ltd.
// All rights reserved. See LICENSE.txt for license.
//
#include <MaterialXFormat/XmlIo.h>
#include <MaterialXFormat/PugiXML/pugixml.hpp>
#include <MaterialXCore/Types.h>
#include <cstring>
#include <fstream>
#include <sstream>
using namespace pugi;
namespace MaterialX
{
const string MTLX_EXTENSION = "mtlx";
namespace {
const string XINCLUDE_TAG = "xi:include";
const string XINCLUDE_NAMESPACE = "xmlns:xi";
const string XINCLUDE_URL = "http://www.w3.org/2001/XInclude";
void elementFromXml(const xml_node& xmlNode, ElementPtr elem, const XmlReadOptions* readOptions)
{
bool skipConflictingElements = readOptions && readOptions->skipConflictingElements;
// Store attributes in element.
for (const xml_attribute& xmlAttr : xmlNode.attributes())
{
if (xmlAttr.name() != Element::NAME_ATTRIBUTE)
{
elem->setAttribute(xmlAttr.name(), xmlAttr.value());
}
}
// Create child elements and recurse.
for (const xml_node& xmlChild : xmlNode.children())
{
string category = xmlChild.name();
string name;
for (const xml_attribute& xmlAttr : xmlChild.attributes())
{
if (xmlAttr.name() == Element::NAME_ATTRIBUTE)
{
name = xmlAttr.value();
break;
}
}
// Check for duplicate elements.
ConstElementPtr previous = elem->getChild(name);
if (previous && skipConflictingElements)
{
continue;
}
// Create the new element.
ElementPtr child = elem->addChildOfCategory(category, name, !previous);
elementFromXml(xmlChild, child, readOptions);
// Check for conflicting elements.
if (previous && *previous != *child)
{
throw Exception("Duplicate element with conflicting content: " + name);
}
}
}
void elementToXml(ConstElementPtr elem, xml_node& xmlNode, const XmlWriteOptions* writeOptions)
{
bool writeXIncludeEnable = writeOptions ? writeOptions->writeXIncludeEnable : true;
ElementPredicate elementPredicate = writeOptions ? writeOptions->elementPredicate : nullptr;
// Store attributes in XML.
if (!elem->getName().empty())
{
xmlNode.append_attribute(Element::NAME_ATTRIBUTE.c_str()) = elem->getName().c_str();
}
for (const string& attrName : elem->getAttributeNames())
{
xml_attribute xmlAttr = xmlNode.append_attribute(attrName.c_str());
xmlAttr.set_value(elem->getAttribute(attrName).c_str());
}
// Create child nodes and recurse.
StringSet writtenSourceFiles;
for (const ConstElementPtr& child : elem->getChildren())
{
if (elementPredicate && !elementPredicate(child))
{
continue;
}
// Write XInclude references if requested.
if (writeXIncludeEnable && child->hasSourceUri())
{
string sourceUri = child->getSourceUri();
if (sourceUri != elem->getDocument()->getSourceUri())
{
if (!writtenSourceFiles.count(sourceUri))
{
if (!xmlNode.attribute(XINCLUDE_NAMESPACE.c_str()))
{
xmlNode.append_attribute(XINCLUDE_NAMESPACE.c_str()) = XINCLUDE_URL.c_str();
}
xml_node includeNode = xmlNode.append_child(XINCLUDE_TAG.c_str());
xml_attribute includeAttr = includeNode.append_attribute("href");
FilePath includePath(sourceUri);
// Write relative include paths in Posix format, and absolute
// include paths in native format.
FilePath::Format includeFormat = includePath.isAbsolute() ?
FilePath::FormatNative : FilePath::FormatPosix;
includeAttr.set_value(includePath.asString(includeFormat).c_str());
writtenSourceFiles.insert(sourceUri);
}
continue;
}
}
xml_node xmlChild = xmlNode.append_child(child->getCategory().c_str());
elementToXml(child, xmlChild, writeOptions);
}
}
void xmlDocumentFromFile(xml_document& xmlDoc, FilePath filename, FileSearchPath searchPath)
{
searchPath.append(getEnvironmentPath());
filename = searchPath.find(filename);
xml_parse_result result = xmlDoc.load_file(filename.asString().c_str());
if (!result)
{
if (result.status == xml_parse_status::status_file_not_found ||
result.status == xml_parse_status::status_io_error ||
result.status == xml_parse_status::status_out_of_memory)
{
throw ExceptionFileMissing("Failed to open file for reading: " + filename.asString());
}
string desc = result.description();
string offset = std::to_string(result.offset);
throw ExceptionParseError("XML parse error in file: " + filename.asString() +
" (" + desc + " at character " + offset + ")");
}
}
void processXIncludes(DocumentPtr doc, xml_node& xmlNode, const FileSearchPath& searchPath, const XmlReadOptions* readOptions)
{
// Search path for includes. Set empty and then evaluated once in the iteration through xml includes.
FileSearchPath includeSearchPath;
XmlReadFunction readXIncludeFunction = readOptions ? readOptions->readXIncludeFunction : readFromXmlFile;
xml_node xmlChild = xmlNode.first_child();
while (xmlChild)
{
if (xmlChild.name() == XINCLUDE_TAG)
{
// Read XInclude references if requested.
if (readXIncludeFunction)
{
string filename = xmlChild.attribute("href").value();
// Check for XInclude cycles.
if (readOptions)
{
const StringVec& parents = readOptions->parentXIncludes;
if (std::find(parents.begin(), parents.end(), filename) != parents.end())
{
throw ExceptionParseError("XInclude cycle detected.");
}
}
// Read the included file into a library document.
DocumentPtr library = createDocument();
XmlReadOptions xiReadOptions = readOptions ? *readOptions : XmlReadOptions();
xiReadOptions.parentXIncludes.push_back(filename);
// Prepend the directory of the parent to accommodate
// includes relative to the parent file location.
if (includeSearchPath.isEmpty())
{
string parentUri = doc->getSourceUri();
if (!parentUri.empty())
{
FilePath filePath = searchPath.find(parentUri);
if (!filePath.isEmpty())
{
// Remove the file name from the path as we want the path to the containing folder.
includeSearchPath = searchPath;
includeSearchPath.prepend(filePath.getParentPath());
}
}
// Set default search path if no parent path found
if (includeSearchPath.isEmpty())
{
includeSearchPath = searchPath;
}
}
readXIncludeFunction(library, filename, includeSearchPath, &xiReadOptions);
// Import the library document.
doc->importLibrary(library, readOptions);
}
// Remove include directive.
xml_node includeNode = xmlChild;
xmlChild = xmlChild.next_sibling();
xmlNode.remove_child(includeNode);
}
else
{
xmlChild = xmlChild.next_sibling();
}
}
}
void documentFromXml(DocumentPtr doc,
const xml_document& xmlDoc,
const FileSearchPath& searchPath = FileSearchPath(),
const XmlReadOptions* readOptions = nullptr)
{
ScopedUpdate update(doc);
doc->onRead();
xml_node xmlRoot = xmlDoc.child(Document::CATEGORY.c_str());
if (xmlRoot)
{
processXIncludes(doc, xmlRoot, searchPath, readOptions);
elementFromXml(xmlRoot, doc, readOptions);
}
bool applyFutureUpdates = readOptions ? readOptions->applyFutureUpdates : false;
doc->upgradeVersion(applyFutureUpdates);
}
} // anonymous namespace
//
// XmlReadOptions methods
//
XmlReadOptions::XmlReadOptions() :
readXIncludeFunction(readFromXmlFile),
applyFutureUpdates(false)
{
}
//
// XmlWriteOptions methods
//
XmlWriteOptions::XmlWriteOptions() :
writeXIncludeEnable(true)
{
}
//
// Reading
//
void readFromXmlBuffer(DocumentPtr doc, const char* buffer, const XmlReadOptions* readOptions)
{
xml_document xmlDoc;
xml_parse_result result = xmlDoc.load_string(buffer);
if (!result)
{
throw ExceptionParseError("Parse error in readFromXmlBuffer");
}
documentFromXml(doc, xmlDoc, EMPTY_STRING, readOptions);
}
void readFromXmlStream(DocumentPtr doc, std::istream& stream, const XmlReadOptions* readOptions)
{
xml_document xmlDoc;
xml_parse_result result = xmlDoc.load(stream);
if (!result)
{
throw ExceptionParseError("Parse error in readFromXmlStream");
}
documentFromXml(doc, xmlDoc, EMPTY_STRING, readOptions);
}
void readFromXmlFile(DocumentPtr doc, const FilePath& filename, const FileSearchPath& searchPath, const XmlReadOptions* readOptions)
{
xml_document xmlDoc;
xmlDocumentFromFile(xmlDoc, filename, searchPath);
// This must be done before parsing the XML as the source URI
// is used for searching for include files.
if (readOptions && !readOptions->parentXIncludes.empty())
{
doc->setSourceUri(readOptions->parentXIncludes[0]);
}
else
{
doc->setSourceUri(filename);
}
documentFromXml(doc, xmlDoc, searchPath, readOptions);
}
void readFromXmlString(DocumentPtr doc, const string& str, const XmlReadOptions* readOptions)
{
std::istringstream stream(str);
readFromXmlStream(doc, stream, readOptions);
}
//
// Writing
//
void writeToXmlStream(DocumentPtr doc, std::ostream& stream, const XmlWriteOptions* writeOptions)
{
ScopedUpdate update(doc);
doc->onWrite();
xml_document xmlDoc;
xml_node xmlRoot = xmlDoc.append_child("materialx");
elementToXml(doc, xmlRoot, writeOptions);
xmlDoc.save(stream, " ");
}
void writeToXmlFile(DocumentPtr doc, const FilePath& filename, const XmlWriteOptions* writeOptions)
{
std::ofstream ofs(filename.asString());
writeToXmlStream(doc, ofs, writeOptions);
}
string writeToXmlString(DocumentPtr doc, const XmlWriteOptions* writeOptions)
{
std::ostringstream stream;
writeToXmlStream(doc, stream, writeOptions);
return stream.str();
}
void prependXInclude(DocumentPtr doc, const FilePath& filename)
{
ElementPtr elem = doc->addChildOfCategory("xinclude");
elem->setSourceUri(filename.asString());
doc->setChildIndex(elem->getName(), 0);
}
} // namespace MaterialX