Skip to content

Commit e6d2633

Browse files
Improvements to XML tree parsing (AcademySoftwareFoundation#2239)
Building upon recent work that refactored XML tree parsing to avoid recursive calls, this changelist contributes additional minor improvements, including the following: - Reduce duplicated logic and computations by merging `processXIncludes` into `documentFromXml`. - Throw an `ExceptionParseError` if a MTLX file has no root MaterialX element.
1 parent db91313 commit e6d2633

File tree

1 file changed

+89
-124
lines changed

1 file changed

+89
-124
lines changed

source/MaterialXFormat/XmlIo.cpp

Lines changed: 89 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -30,75 +30,6 @@ const string XINCLUDE_URL = "http://www.w3.org/2001/XInclude";
3030

3131
using ElementStack = vector<std::pair<ElementPtr, xml_node>>;
3232

33-
void processXIncludes(DocumentPtr doc, xml_node& xmlNode, const FileSearchPath& searchPath, const XmlReadOptions* readOptions)
34-
{
35-
FileSearchPath includeSearchPath;
36-
XmlReadFunction readXIncludeFunction = readOptions ? readOptions->readXIncludeFunction : readFromXmlFile;
37-
xml_node xmlChild = xmlNode.first_child();
38-
while (xmlChild)
39-
{
40-
if (xmlChild.name() == XINCLUDE_TAG)
41-
{
42-
// Read XInclude references if requested.
43-
if (readXIncludeFunction)
44-
{
45-
string filename = xmlChild.attribute("href").value();
46-
const StringVec& parents = readOptions ? readOptions->parentXIncludes : StringVec();
47-
48-
// Validate XInclude state.
49-
if (std::find(parents.begin(), parents.end(), filename) != parents.end())
50-
{
51-
throw ExceptionParseError("XInclude cycle detected.");
52-
}
53-
if (parents.size() >= MAX_XINCLUDE_DEPTH)
54-
{
55-
throw ExceptionParseError("Maximum XInclude depth exceeded.");
56-
}
57-
58-
// Read the included file into a library document.
59-
DocumentPtr library = createDocument();
60-
XmlReadOptions xiReadOptions = readOptions ? *readOptions : XmlReadOptions();
61-
xiReadOptions.parentXIncludes.push_back(filename);
62-
63-
// Prepend the directory of the parent to accommodate
64-
// includes relative to the parent file location.
65-
if (includeSearchPath.isEmpty())
66-
{
67-
string parentUri = doc->getSourceUri();
68-
if (!parentUri.empty())
69-
{
70-
FilePath filePath = searchPath.find(parentUri);
71-
if (!filePath.isEmpty())
72-
{
73-
// Remove the file name from the path as we want the path to the containing folder.
74-
includeSearchPath = searchPath;
75-
includeSearchPath.prepend(filePath.getParentPath());
76-
}
77-
}
78-
// Set default search path if no parent path found
79-
if (includeSearchPath.isEmpty())
80-
{
81-
includeSearchPath = searchPath;
82-
}
83-
}
84-
readXIncludeFunction(library, filename, includeSearchPath, &xiReadOptions);
85-
86-
// Import the library document.
87-
doc->importLibrary(library);
88-
}
89-
90-
// Remove include directive.
91-
xml_node includeNode = xmlChild;
92-
xmlChild = xmlChild.next_sibling();
93-
xmlNode.remove_child(includeNode);
94-
}
95-
else
96-
{
97-
xmlChild = xmlChild.next_sibling();
98-
}
99-
}
100-
}
101-
10233
void documentToXml(DocumentPtr doc, xml_node& xmlRoot, const XmlWriteOptions* writeOptions)
10334
{
10435
ElementStack elemStack;
@@ -183,68 +114,99 @@ void documentToXml(DocumentPtr doc, xml_node& xmlRoot, const XmlWriteOptions* wr
183114
void documentFromXml(DocumentPtr doc, const xml_document& xmlDoc, const FileSearchPath& searchPath, const XmlReadOptions* readOptions)
184115
{
185116
xml_node xmlRoot = xmlDoc.child(Document::CATEGORY.c_str());
186-
if (xmlRoot)
117+
if (!xmlRoot)
187118
{
188-
processXIncludes(doc, xmlRoot, searchPath, readOptions);
189-
ElementStack elemStack;
190-
elemStack.emplace_back(doc, xmlRoot);
119+
throw ExceptionParseError("No root MaterialX element found.");
120+
}
191121

192-
while (!elemStack.empty())
122+
// Process XInclude directives.
123+
XmlReadFunction readXIncludeFunction = readOptions ? readOptions->readXIncludeFunction : readFromXmlFile;
124+
if (readXIncludeFunction)
125+
{
126+
for (const xml_node& xmlChild : xmlRoot.children())
193127
{
194-
ElementPtr elem = elemStack.back().first;
195-
xml_node xmlNode = elemStack.back().second;
196-
elemStack.pop_back();
197-
198-
// Store attributes in element.
199-
for (const xml_attribute& xmlAttr : xmlNode.attributes())
128+
if (xmlChild.name() == XINCLUDE_TAG)
200129
{
201-
if (xmlAttr.name() != Element::NAME_ATTRIBUTE)
130+
string filename = xmlChild.attribute("href").value();
131+
const StringVec& parents = readOptions ? readOptions->parentXIncludes : StringVec();
132+
133+
// Validate XInclude state.
134+
if (std::find(parents.begin(), parents.end(), filename) != parents.end())
135+
{
136+
throw ExceptionParseError("XInclude cycle detected.");
137+
}
138+
if (parents.size() >= MAX_XINCLUDE_DEPTH)
202139
{
203-
elem->setAttribute(xmlAttr.name(), xmlAttr.value());
140+
throw ExceptionParseError("Maximum XInclude depth exceeded.");
204141
}
142+
143+
// Read the included file into a library document.
144+
DocumentPtr library = createDocument();
145+
XmlReadOptions xiReadOptions = readOptions ? *readOptions : XmlReadOptions();
146+
xiReadOptions.parentXIncludes.push_back(filename);
147+
readXIncludeFunction(library, filename, searchPath, &xiReadOptions);
148+
149+
// Import the library document.
150+
doc->importLibrary(library);
205151
}
152+
}
153+
}
206154

207-
// Create child elements and recurse.
208-
for (const xml_node& xmlChild : xmlNode.children())
155+
// Build the element tree.
156+
ElementStack elemStack;
157+
elemStack.emplace_back(doc, xmlRoot);
158+
while (!elemStack.empty())
159+
{
160+
ElementPtr elem = elemStack.back().first;
161+
xml_node xmlNode = elemStack.back().second;
162+
elemStack.pop_back();
163+
164+
// Store attributes in element.
165+
for (const xml_attribute& xmlAttr : xmlNode.attributes())
166+
{
167+
if (xmlAttr.name() != Element::NAME_ATTRIBUTE)
209168
{
210-
string category = xmlChild.name();
211-
string name;
212-
for (const xml_attribute& xmlAttr : xmlChild.attributes())
213-
{
214-
if (xmlAttr.name() == Element::NAME_ATTRIBUTE)
215-
{
216-
name = xmlAttr.value();
217-
break;
218-
}
219-
}
169+
elem->setAttribute(xmlAttr.name(), xmlAttr.value());
170+
}
171+
}
220172

221-
// Check for duplicate elements.
222-
ConstElementPtr previous = elem->getChild(name);
223-
if (previous)
224-
{
225-
continue;
226-
}
173+
// Create child elements and recurse.
174+
for (const xml_node& xmlChild : xmlNode.children())
175+
{
176+
// Get child category.
177+
string category = xmlChild.name();
178+
if (category == XINCLUDE_TAG)
179+
{
180+
continue;
181+
}
227182

228-
// Create the child element.
229-
ElementPtr child = elem->addChildOfCategory(category, name);
183+
// Get child name and skip duplicates.
184+
string name = xmlChild.attribute(Element::NAME_ATTRIBUTE.c_str()).value();
185+
ConstElementPtr previous = elem->getChild(name);
186+
if (previous)
187+
{
188+
continue;
189+
}
230190

231-
// Handle the interpretation of XML comments and newlines.
232-
if (readOptions && category.empty())
191+
// Create the child element.
192+
ElementPtr child = elem->addChildOfCategory(category, name);
193+
194+
// Handle the interpretation of XML comments and newlines.
195+
if (readOptions && category.empty())
196+
{
197+
if (readOptions->readComments && xmlChild.type() == node_comment)
233198
{
234-
if (readOptions->readComments && xmlChild.type() == node_comment)
235-
{
236-
child = elem->changeChildCategory(child, CommentElement::CATEGORY);
237-
child->setDocString(xmlChild.value());
238-
}
239-
else if (readOptions->readNewlines && xmlChild.type() == node_newline)
240-
{
241-
child = elem->changeChildCategory(child, NewlineElement::CATEGORY);
242-
}
199+
child = elem->changeChildCategory(child, CommentElement::CATEGORY);
200+
child->setDocString(xmlChild.value());
201+
}
202+
else if (readOptions->readNewlines && xmlChild.type() == node_newline)
203+
{
204+
child = elem->changeChildCategory(child, NewlineElement::CATEGORY);
243205
}
244-
245-
// Push child data onto the stack.
246-
elemStack.emplace_back(child, xmlChild);
247206
}
207+
208+
// Push child data onto the stack.
209+
elemStack.emplace_back(child, xmlChild);
248210
}
249211
}
250212

@@ -355,16 +317,19 @@ void readFromXmlFile(DocumentPtr doc, FilePath filename, FileSearchPath searchPa
355317
xml_parse_result result = xmlDoc.load_file(filename.asString().c_str(), getParseOptions(readOptions));
356318
validateParseResult(result, filename);
357319

358-
// This must be done before parsing the XML as the source URI
359-
// is used for searching for include files.
360-
if (readOptions && !readOptions->parentXIncludes.empty())
361-
{
362-
doc->setSourceUri(readOptions->parentXIncludes[0]);
363-
}
364-
else
320+
// Store the source URI of the document.
321+
FilePath sourcePath = (readOptions && !readOptions->parentXIncludes.empty()) ?
322+
FilePath(readOptions->parentXIncludes[0]) :
323+
FilePath(filename);
324+
doc->setSourceUri(sourcePath);
325+
326+
// Enable XIncludes that are relative to this document.
327+
if (!sourcePath.isAbsolute())
365328
{
366-
doc->setSourceUri(filename);
329+
sourcePath = searchPath.find(sourcePath);
367330
}
331+
searchPath.prepend(sourcePath.getParentPath());
332+
368333
documentFromXml(doc, xmlDoc, searchPath, readOptions);
369334
}
370335

0 commit comments

Comments
 (0)