2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2015,2016, by the GROMACS development team, led by
5 * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6 * and including many others, as listed in the AUTHORS file in the
7 * top-level source directory and at http://www.gromacs.org.
9 * GROMACS is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public License
11 * as published by the Free Software Foundation; either version 2.1
12 * of the License, or (at your option) any later version.
14 * GROMACS is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with GROMACS; if not, see
21 * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 * If you want to redistribute modifications to GROMACS, please
25 * consider that scientific software is very special. Version
26 * control is crucial - bugs must be traceable. We will be happy to
27 * consider code for inclusion in the official distribution, but
28 * derived work must not be called official GROMACS. Details are found
29 * in the README & COPYING files - if they are missing, get the
30 * official version at http://www.gromacs.org.
32 * To help us fund GROMACS development, we humbly ask that you cite
33 * the research papers on the package. Check out http://www.gromacs.org.
37 * Implements reference data XML persistence.
39 * \author Teemu Murtola <teemu.murtola@gmail.com>
40 * \author Mark Abraham <mark.j.abraham@gmail.com>
41 * \ingroup module_testutils
45 #include "refdata-xml.h"
49 #include "gromacs/utility/exceptions.h"
51 #include "testutils/refdata-impl.h"
52 #include "testutils/testexceptions.h"
62 //! XML version declaration used when writing the reference data.
63 const char *const c_VersionDeclarationString
= "xml version=\"1.0\"";
64 //! XML stylesheet declaration used for writing the reference data.
65 const char *const c_StyleSheetDeclarationString
= "xml-stylesheet type=\"text/xsl\" href=\"referencedata.xsl\"";
66 //! Name of the root element in reference data XML files.
67 const char *const c_RootNodeName
= "ReferenceData";
68 //! Name of the XML attribute used to store identifying strings for reference data elements.
69 const char *const c_IdAttrName
= "Name";
73 /********************************************************************
80 //! Convenience typedef
81 typedef tinyxml2::XMLDocument
*XMLDocumentPtr
;
82 //! Convenience typedef
83 typedef tinyxml2::XMLNode
*XMLNodePtr
;
84 //! Convenience typedef
85 typedef tinyxml2::XMLElement
*XMLElementPtr
;
86 //! Convenience typedef
87 typedef tinyxml2::XMLText
*XMLTextPtr
;
89 //! \name Helper functions for XML reading
92 void readEntry(XMLNodePtr node
, ReferenceDataEntry
*entry
);
94 XMLNodePtr
getCDataChildNode(XMLNodePtr node
)
96 XMLNodePtr cdata
= node
->FirstChild();
97 while (cdata
!= nullptr &&
98 cdata
->ToText() != nullptr &&
99 !cdata
->ToText()->CData())
101 cdata
= cdata
->NextSibling();
106 bool hasCDataContent(XMLNodePtr node
)
108 return getCDataChildNode(node
) != nullptr;
111 //! Return a node convertible to text, either \c childNode or its first such sibling.
112 XMLNodePtr
getNextTextChildNode(XMLNodePtr childNode
)
114 // Note that when reading, we don't have to care if it is in a
115 // CDATA section, or not.
116 while (childNode
!= nullptr)
118 if (childNode
->ToText() != nullptr)
122 childNode
= childNode
->NextSibling();
127 //! Return the concatenation of all the text children of \c node, including multiple CDATA children.
128 std::string
getValueFromLeafElement(XMLNodePtr node
)
132 XMLNodePtr childNode
= getNextTextChildNode(node
->FirstChild());
133 while (childNode
!= nullptr)
135 value
+= std::string(childNode
->Value());
137 childNode
= getNextTextChildNode(childNode
->NextSibling());
140 if (hasCDataContent(node
))
142 // Prepare to strip the convenience newline added in
143 // createElementContents, when writing CDATA content for
145 if (value
.empty() || value
[0] != '\n')
147 GMX_THROW(TestException("Invalid string block in reference data"));
155 //! Make a new entry from \c element.
156 ReferenceDataEntry::EntryPointer
createEntry(XMLElementPtr element
)
158 const char *id
= element
->Attribute(c_IdAttrName
);
159 ReferenceDataEntry::EntryPointer
entry(new ReferenceDataEntry(element
->Value(), id
));
163 //! Read the child entries of \c parentElement and transfer the contents to \c entry
164 void readChildEntries(XMLNodePtr parentElement
, ReferenceDataEntry
*entry
)
166 XMLElementPtr childElement
= parentElement
->FirstChildElement();
167 while (childElement
!= nullptr)
169 ReferenceDataEntry::EntryPointer
child(createEntry(childElement
));
170 readEntry(childElement
, child
.get());
171 entry
->addChild(move(child
));
172 childElement
= childElement
->NextSiblingElement();
176 //! Return whether \c node has child XML elements (rather than text content).
177 bool isCompoundElement(XMLNodePtr node
)
179 return node
->FirstChildElement() != nullptr;
182 //! Read \c element and transfer the contents to \c entry
183 void readEntry(XMLNodePtr element
, ReferenceDataEntry
*entry
)
185 if (isCompoundElement(element
))
187 readChildEntries(element
, entry
);
189 else if (hasCDataContent(element
))
191 entry
->setTextBlockValue(getValueFromLeafElement(element
));
195 entry
->setValue(getValueFromLeafElement(element
));
204 ReferenceDataEntry::EntryPointer
205 readReferenceDataFile(const std::string
&path
)
207 tinyxml2::XMLDocument document
;
208 document
.LoadFile(path
.c_str());
209 if (document
.Error())
211 std::string
errorString("Error was ");
212 errorString
+= document
.GetErrorStr1();
213 errorString
+= document
.GetErrorStr2();
214 GMX_THROW(TestException("Reference data not parsed successfully: " + path
+ "\n." + errorString
+ "\n"));
216 XMLElementPtr rootNode
= document
.RootElement();
217 if (rootNode
== nullptr)
219 GMX_THROW(TestException("Reference data is empty: " + path
));
221 if (std::strcmp(rootNode
->Value(), c_RootNodeName
) != 0)
223 GMX_THROW(TestException("Invalid root node type in " + path
));
226 ReferenceDataEntry::EntryPointer
rootEntry(ReferenceDataEntry::createRoot());
227 readEntry(rootNode
, rootEntry
.get());
232 /********************************************************************
239 //! \name Helper functions for XML writing
242 void createElementAndContents(XMLElementPtr parentElement
,
243 const ReferenceDataEntry
&entry
);
245 void setIdAttribute(XMLElementPtr element
, const std::string
&id
)
249 element
->SetAttribute(c_IdAttrName
, id
.c_str()); // If this fails, it throws std::bad_alloc
253 XMLElementPtr
createElement(XMLElementPtr parentElement
, const ReferenceDataEntry
&entry
)
255 XMLElementPtr element
= parentElement
->GetDocument()->NewElement(entry
.type().c_str());
256 parentElement
->InsertEndChild(element
);
257 setIdAttribute(element
, entry
.id()); // If this fails, it throws std::bad_alloc
261 void createChildElements(XMLElementPtr parentElement
, const ReferenceDataEntry
&entry
)
263 const ReferenceDataEntry::ChildList
&children(entry
.children());
264 ReferenceDataEntry::ChildIterator child
;
265 for (child
= children
.begin(); child
!= children
.end(); ++child
)
267 createElementAndContents(parentElement
, **child
);
271 /*! \brief Handle \c input intended to be written as CDATA
273 * This method searches for any ']]>' sequences embedded in \c input,
274 * because this must always end a CDATA field. If any are found, it
275 * breaks the string so that instead multiple CDATA fields will be
276 * written with that token sequence split across the fields. Note that
277 * tinyxml2 does not handle such things itself.
279 * This is an edge case that is unimportant for GROMACS refdata, but
280 * it is preferable to know that the infrastructure won't break.
282 std::vector
<std::string
> breakUpAnyCdataEndTags(const std::string
&input
)
284 std::vector
<std::string
> strings
;
285 std::size_t startPos
= 0;
290 endPos
= input
.find("]]>", startPos
);
291 if (endPos
!= std::string::npos
)
293 // We found an embedded CDATA end tag, so arrange to split it into multiple CDATA blocks
296 strings
.push_back(input
.substr(startPos
, endPos
));
299 while (endPos
!= std::string::npos
);
304 void createElementContents(XMLElementPtr element
, const ReferenceDataEntry
&entry
)
306 // TODO: Figure out if \r and \r\n can be handled without them
307 // changing to \n in the roundtrip.
308 if (entry
.isCompound())
310 createChildElements(element
, entry
);
312 else if (entry
.isTextBlock())
314 // An extra newline is written in the beginning to make lines align
315 // in the output xml (otherwise, the first line would be off by the length
316 // of the starting CDATA tag).
317 const std::string adjustedValue
= "\n" + entry
.value();
318 std::vector
<std::string
> cdataStrings
= breakUpAnyCdataEndTags(adjustedValue
);
319 for (auto const &s
: cdataStrings
)
321 XMLTextPtr textNode
= element
->GetDocument()->NewText(s
.c_str());
322 textNode
->SetCData(true);
323 element
->InsertEndChild(textNode
);
328 XMLTextPtr textNode
= element
->GetDocument()->NewText(entry
.value().c_str());
329 element
->InsertEndChild(textNode
);
333 void createElementAndContents(XMLElementPtr parentElement
, const ReferenceDataEntry
&entry
)
335 XMLElementPtr element
= createElement(parentElement
, entry
);
336 createElementContents(element
, entry
);
339 XMLElementPtr
createRootElement(XMLDocumentPtr document
)
341 XMLElementPtr rootElement
= document
->NewElement(c_RootNodeName
);
342 document
->InsertEndChild(rootElement
);
351 void writeReferenceDataFile(const std::string
&path
,
352 const ReferenceDataEntry
&rootEntry
)
354 // TODO: Error checking
355 tinyxml2::XMLDocument document
;
357 tinyxml2::XMLDeclaration
*declaration
= document
.NewDeclaration(c_VersionDeclarationString
);
358 document
.InsertEndChild(declaration
);
360 declaration
= document
.NewDeclaration(c_StyleSheetDeclarationString
);
361 document
.InsertEndChild(declaration
);
363 XMLElementPtr rootElement
= createRootElement(&document
);
364 createChildElements(rootElement
, rootEntry
);
366 if (document
.SaveFile(path
.c_str()) != tinyxml2::XML_NO_ERROR
)
368 GMX_THROW(TestException("Reference data saving failed in " + path
));